Skip to content

Commit f892757

Browse files
committed
Add scenario for projects management
1 parent 103e36c commit f892757

File tree

2 files changed

+184
-1
lines changed

2 files changed

+184
-1
lines changed

examples/01_users.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@
134134
},
135135
{
136136
"cell_type": "code",
137-
"source": "from os.path import join\nimport csv\nimport string\nimport random\n\nfrom mergin.common import WorkspaceRole\nfrom mergin.common import ClientError\n\npermissions = [WorkspaceRole.EDITOR,\n WorkspaceRole.READER,\n WorkspaceRole.WRITER]\n\n\nfilename = 'users.csv'\n\ntry:\n with open(filename, mode='r', newline='', encoding='utf-8') as csvfile:\n reader = csv.reader(csvfile)\n header = next(reader) # Skip header\n for row in reader:\n username = row[0]\n email = row[1]\n # add new mergin maps user\n password = ''.join(random.choices(string.ascii_uppercase + string.digits + string.ascii_lowercase, k=12))\n print (f\"Password for {username} is {password}\")\n try:\n client.create_user(username=username, password=password, email=email, workspace_id=WORKSPACE_ID, workspace_role=random.choice(permissions))\n print(f\"User '{username}' created successfully.\")\n except ClientError as e:\n if e.http_error == 400 and 'exists' in e.detail :\n print(f\"User '{username}' already exists.\") \nexcept FileNotFoundError:\n print(f\"File '{filename}' not found for processing example.\")\nexcept StopIteration:\n print(f\"File '{filename}' does not contain enough data rows for processing example.\")\nexcept Exception as e:\n print(f\"An error occurred during processing example: {e}\")\n",
137+
"source": "from os.path import join\nimport csv\nimport string\nimport random\n\nfrom mergin.common import WorkspaceRole\nfrom mergin.common import ClientError\n\npermissions = [WorkspaceRole.EDITOR,\n WorkspaceRole.READER,\n WorkspaceRole.WRITER]\n\n\nfilename = '01_users_assets/users.csv'\n\ntry:\n with open(filename, mode='r', newline='', encoding='utf-8') as csvfile:\n reader = csv.reader(csvfile)\n header = next(reader) # Skip header\n for row in reader:\n username = row[0]\n email = row[1]\n # add new mergin maps user\n password = ''.join(random.choices(string.ascii_uppercase + string.digits + string.ascii_lowercase, k=12))\n print (f\"Password for {username} is {password}\")\n try:\n client.create_user(username=username, password=password, email=email, workspace_id=WORKSPACE_ID, workspace_role=random.choice(permissions))\n print(f\"User '{username}' created successfully.\")\n except ClientError as e:\n if e.http_error == 400 and 'exists' in e.detail :\n print(f\"User '{username}' already exists.\") \nexcept FileNotFoundError:\n print(f\"File '{filename}' not found for processing example.\")\nexcept StopIteration:\n print(f\"File '{filename}' does not contain enough data rows for processing example.\")\nexcept Exception as e:\n print(f\"An error occurred during processing example: {e}\")\n",
138138
"metadata": {
139139
"id": "Lp351dFYquVs"
140140
},

examples/03_projects.ipynb

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
{
2+
"metadata": {
3+
"kernelspec": {
4+
"name": "python",
5+
"display_name": "Python (Pyodide)",
6+
"language": "python"
7+
},
8+
"language_info": {
9+
"codemirror_mode": {
10+
"name": "python",
11+
"version": 3
12+
},
13+
"file_extension": ".py",
14+
"mimetype": "text/x-python",
15+
"name": "python",
16+
"nbconvert_exporter": "python",
17+
"pygments_lexer": "ipython3",
18+
"version": "3.8"
19+
}
20+
},
21+
"nbformat_minor": 5,
22+
"nbformat": 4,
23+
"cells": [
24+
{
25+
"id": "a561c6a3-366a-4ef2-8acd-3eae49ad629d",
26+
"cell_type": "code",
27+
"source": "# Mergin Maps Projects Management",
28+
"metadata": {
29+
"trusted": true
30+
},
31+
"outputs": [],
32+
"execution_count": null
33+
},
34+
{
35+
"id": "8c4330a7-a7ad-441d-9f2d-16d5e0dfc8f7",
36+
"cell_type": "markdown",
37+
"source": "Mergin Maps API allows you to manage your projects in a simple and effective way.\n\nFirst let's install mergin maps client (if not installed yet)",
38+
"metadata": {}
39+
},
40+
{
41+
"id": "fb42bae4-f313-4196-9299-2c8d2dacca11",
42+
"cell_type": "code",
43+
"source": "!pip install mergin-client",
44+
"metadata": {
45+
"trusted": true
46+
},
47+
"outputs": [],
48+
"execution_count": null
49+
},
50+
{
51+
"id": "f7517ef0-7b3f-47f6-8140-c3076ac02215",
52+
"cell_type": "markdown",
53+
"source": "Login to Mergin Maps using your an existing user",
54+
"metadata": {}
55+
},
56+
{
57+
"id": "c380887c-271b-48b6-b435-ed8628dd0a81",
58+
"cell_type": "code",
59+
"source": "# Use here your login username and password\nLOGIN=\"\"\nPASSW=\"\"\n\nimport mergin\n\nclient = mergin.MerginClient(login=LOGIN, password=PASSW)",
60+
"metadata": {
61+
"trusted": true
62+
},
63+
"outputs": [],
64+
"execution_count": null
65+
},
66+
{
67+
"id": "46bcada4-5dac-44fe-8c63-5f35932404c4",
68+
"cell_type": "markdown",
69+
"source": "Let's create a new project (empty) on an existing workspace (fill `WORKSPACE` with an existing workspace)",
70+
"metadata": {}
71+
},
72+
{
73+
"id": "1dfb7e3c-bf38-4867-b47e-b895c4672212",
74+
"cell_type": "code",
75+
"source": "WORKSPACE=\"\"\n\nclient.create_project(project_name='prague-demo', namespace=WORKSPACE, is_public=False)",
76+
"metadata": {
77+
"trusted": true
78+
},
79+
"outputs": [],
80+
"execution_count": null
81+
},
82+
{
83+
"id": "75b88a5e-efc3-4569-a15e-6f49c591180f",
84+
"cell_type": "markdown",
85+
"source": "Download locally your newly created Mergin Maps project to add some content",
86+
"metadata": {}
87+
},
88+
{
89+
"id": "34f8279f-76b9-452b-b067-20923f0783b6",
90+
"cell_type": "code",
91+
"source": "client.download_project(project_path='{workspace}/prague-demo'.format(workspace=WORKSPACE) , directory='/tmp/demo-prague')",
92+
"metadata": {
93+
"trusted": true
94+
},
95+
"outputs": [],
96+
"execution_count": null
97+
},
98+
{
99+
"id": "10ea77c1-98b7-4d22-ac2c-a2b051db400e",
100+
"cell_type": "markdown",
101+
"source": "Let's add some random points over Prague city and save it as geopackage. Push the changes on our demo project.",
102+
"metadata": {}
103+
},
104+
{
105+
"id": "11c18fc4-3202-498f-a733-7bf76f069926",
106+
"cell_type": "code",
107+
"source": "# This is needed to compute the geopackage\n!pip install pandas geopandas ",
108+
"metadata": {
109+
"trusted": true
110+
},
111+
"outputs": [],
112+
"execution_count": null
113+
},
114+
{
115+
"id": "dbf579be-286d-4453-910a-9ab0d30f4739",
116+
"cell_type": "code",
117+
"source": "import geopandas\nfrom shapely.geometry import Point\nimport random\nimport time # For potential delays if making many requests, though geocode handles some.\n\ndef create_random_pois_in_city_gpkg(city_name, num_pois=100, output_gpkg_path=\"city_random_pois.gpkg\", layer_name=\"random_pois\"):\n \"\"\"\n Generates a specified number of random Points of Interest (POIs) within the\n boundaries of a given city and saves them to a GeoPackage file.\n\n Args:\n city_name (str): The name of the city and country (e.g., \"Prague, Czech Republic\").\n num_pois (int): The number of random POIs to generate.\n output_gpkg_path (str): The file path for the output GeoPackage.\n layer_name (str): The name for the layer within the GeoPackage.\n \"\"\"\n try:\n # Step 1: Geocode the city to get its boundary\n print(f\"Attempting to geocode '{city_name}' to fetch its boundary...\")\n # Nominatim requires a user_agent. geopandas.tools.geocode passes it to geopy.\n # If you encounter issues, ensure geopy is installed and up-to-date.\n # A unique user_agent is good practice for repeated use.\n city_gdf = geopandas.tools.geocode(city_name, provider=\"nominatim\", user_agent=f\"random_poi_generator_{random.randint(1000,9999)}\")\n\n if city_gdf.empty:\n print(f\"Error: Could not geocode '{city_name}'. Please check the city name or your internet connection.\")\n return\n\n # Assuming the first result is the correct boundary for the city\n city_boundary_polygon = city_gdf.geometry.iloc[0]\n print(f\"Successfully obtained boundary for '{city_name}'. CRS: {city_gdf.crs}\")\n\n # Step 2: Get the bounding box of the city to generate random points\n min_lon, min_lat, max_lon, max_lat = city_boundary_polygon.bounds\n\n generated_points = []\n poi_attributes = {'id': [], 'name': [], 'category': []}\n\n print(f\"Generating {num_pois} random POIs within '{city_name}'...\")\n\n # Safety counter to prevent potential infinite loops for complex geometries\n max_attempts_per_poi = 1000\n total_attempts = 0\n\n while len(generated_points) < num_pois:\n if total_attempts > num_pois * max_attempts_per_poi:\n print(f\"Warning: Exceeded maximum attempts. Generated {len(generated_points)} out of {num_pois} POIs.\")\n break\n\n # Generate a random point within the bounding box\n random_longitude = random.uniform(min_lon, max_lon)\n random_latitude = random.uniform(min_lat, max_lat)\n candidate_point = Point(random_longitude, random_latitude)\n\n # Check if the generated point is actually within the city's polygon (not just its bounding box)\n if candidate_point.within(city_boundary_polygon):\n generated_points.append(candidate_point)\n poi_id = len(generated_points)\n poi_attributes['id'].append(poi_id)\n poi_attributes['name'].append(f\"POI_{city_name.split(',')[0].replace(' ','_')}_{poi_id}\")\n poi_attributes['category'].append(random.choice([\"Attraction\", \"Restaurant\", \"Shop\", \"Park\", \"Service\"]))\n\n if len(generated_points) % 10 == 0:\n print(f\"Generated {len(generated_points)}/{num_pois} POIs...\")\n\n total_attempts += 1\n\n if not generated_points:\n print(\"No POIs could be generated within the city boundary. Exiting.\")\n return\n\n # Step 3: Create a GeoDataFrame from the generated points and attributes\n # The CRS of the points should match the CRS of the geocoded city boundary\n pois_gdf = geopandas.GeoDataFrame(poi_attributes, geometry=generated_points, crs=city_gdf.crs)\n print(f\"Created GeoDataFrame with {len(pois_gdf)} POIs.\")\n\n # Step 4: Save the GeoDataFrame to a GeoPackage file\n print(f\"Saving POIs to '{output_gpkg_path}' in layer '{layer_name}'...\")\n pois_gdf.to_file(output_gpkg_path, layer=layer_name, driver=\"GPKG\")\n print(f\"Successfully created GeoPackage: '{output_gpkg_path}'\")\n\n except ImportError:\n print(\"Error: One or more required libraries (geopandas, shapely, geopy) are not installed.\")\n print(\"Please install them using: pip install geopandas shapely geopy\")\n except Exception as e:\n print(f\"An unexpected error occurred: {e}\")\n print(\"Please ensure you have an active internet connection for geocoding.\")\n\nif __name__ == \"__main__\":\n # Define the city and parameters\n target_city = \"Prague, Czech Republic\"\n number_of_pois_to_generate = 500\n output_file_name = \"/tmp/demo-prague/prague_random_points.gpkg\"\n output_layer_name = \"prague_pois\"\n\n create_random_pois_in_city_gpkg(\n city_name=target_city,\n num_pois=number_of_pois_to_generate,\n output_gpkg_path=output_file_name,\n layer_name=output_layer_name\n )",
118+
"metadata": {
119+
"trusted": true
120+
},
121+
"outputs": [],
122+
"execution_count": null
123+
},
124+
{
125+
"id": "ce7aafe2-4e9d-4990-89f4-3654d9e15987",
126+
"cell_type": "code",
127+
"source": "client.push_project(directory='/tmp/demo-prague')",
128+
"metadata": {
129+
"trusted": true
130+
},
131+
"outputs": [],
132+
"execution_count": null
133+
},
134+
{
135+
"id": "b0c474ca-7a02-40a3-9938-2d3eb6c53bf1",
136+
"cell_type": "markdown",
137+
"source": "Check current project status",
138+
"metadata": {}
139+
},
140+
{
141+
"id": "63fb44be-319b-407c-ad25-027804bae96d",
142+
"cell_type": "code",
143+
"source": "client.project_status('/tmp/demo-prague/prague-demo')",
144+
"metadata": {
145+
"trusted": true
146+
},
147+
"outputs": [],
148+
"execution_count": null
149+
},
150+
{
151+
"id": "d392f72c-a437-4af1-b6be-dfaef36323e0",
152+
"cell_type": "markdown",
153+
"source": "Let's say, now your project is on a 'template' state and you want to create many projects from here. Simply clone the project using the API!",
154+
"metadata": {}
155+
},
156+
{
157+
"id": "dce1b1a3-9199-4d30-9b70-b645e3198a6d",
158+
"cell_type": "code",
159+
"source": "client.clone_project(source_project_path='{workspace}/prague-demo'.format(workspace=WORKSPACE), cloned_project_name='{workspace}/prague-demo1'.format(workspace=WORKSPACE))\nclient.clone_project(source_project_path='{workspace}/prague-demo'.format(workspace=WORKSPACE), cloned_project_name='{workspace}/prague-demo2'.format(workspace=WORKSPACE))",
160+
"metadata": {
161+
"trusted": true
162+
},
163+
"outputs": [],
164+
"execution_count": null
165+
},
166+
{
167+
"id": "51879308-c8aa-480e-9f8c-530f282f4ec6",
168+
"cell_type": "markdown",
169+
"source": "Let's delete one of the cloned projects. \n\nNOTE: using `delete_project_now` will bypass the default value `DELETED_PROJECT_EXPIRATION`. See: https://merginmaps.com/docs/server/environment/#data-synchronisation-and-management",
170+
"metadata": {}
171+
},
172+
{
173+
"id": "62abc678-75fe-4f7e-9f71-8dddee05d81e",
174+
"cell_type": "code",
175+
"source": "client.delete_project_now(project_path='{workspace}/prague-demo1'.format(workspace=WORKSPACE))",
176+
"metadata": {
177+
"trusted": true
178+
},
179+
"outputs": [],
180+
"execution_count": null
181+
}
182+
]
183+
}

0 commit comments

Comments
 (0)