|
| 1 | +{ |
| 2 | + "cells": [ |
| 3 | + { |
| 4 | + "cell_type": "markdown", |
| 5 | + "id": "eff75b76", |
| 6 | + "metadata": {}, |
| 7 | + "source": [ |
| 8 | + "# Mergin Maps Synchronisation\n", |
| 9 | + "\n", |
| 10 | + "Mergin Maps synchronisation operates using a push/pull mechanism for your project. \n", |
| 11 | + "\n", |
| 12 | + "- **Push**: Synchronise your local project changes to the Mergin Maps server\n", |
| 13 | + "- **Pull**: Updates from the server are synchronised to the local device\n", |
| 14 | + "\n", |
| 15 | + "## Example project\n", |
| 16 | + "\n", |
| 17 | + "Imagine you are preparing a project for tree surveyors in Vienna.\n", |
| 18 | + "\n", |
| 19 | + "The task for the surveyors is to collect data about existing trees in the city. They are focusing on the health of the trees. In this example, we will use the Mergin Maps Python API client to automatically synchronise data to the Mergin Maps server. We will import CSV data into a GeoPackage and synchronize it. This GeoPackage can then be used for further data collection in the field." |
| 20 | + ] |
| 21 | + }, |
| 22 | + { |
| 23 | + "cell_type": "markdown", |
| 24 | + "id": "8eb25fff", |
| 25 | + "metadata": {}, |
| 26 | + "source": [ |
| 27 | + "Let's install Mergin Maps client and necessary libraries for this example." |
| 28 | + ] |
| 29 | + }, |
| 30 | + { |
| 31 | + "cell_type": "code", |
| 32 | + "execution_count": null, |
| 33 | + "id": "33ac4583", |
| 34 | + "metadata": {}, |
| 35 | + "outputs": [], |
| 36 | + "source": [ |
| 37 | + "!pip install mergin-client" |
| 38 | + ] |
| 39 | + }, |
| 40 | + { |
| 41 | + "cell_type": "markdown", |
| 42 | + "id": "611a93c1", |
| 43 | + "metadata": {}, |
| 44 | + "source": [ |
| 45 | + "Fill the following variables with your Mergin Maps credentials (username / email and password)." |
| 46 | + ] |
| 47 | + }, |
| 48 | + { |
| 49 | + "cell_type": "code", |
| 50 | + "execution_count": null, |
| 51 | + "id": "1bd4f48d", |
| 52 | + "metadata": {}, |
| 53 | + "outputs": [], |
| 54 | + "source": [ |
| 55 | + "LOGIN=\"...\"\n", |
| 56 | + "PASSWORD=\"...\"" |
| 57 | + ] |
| 58 | + }, |
| 59 | + { |
| 60 | + "cell_type": "markdown", |
| 61 | + "id": "8a68900f", |
| 62 | + "metadata": {}, |
| 63 | + "source": [ |
| 64 | + "Let's login to your account to be able to use the `MerginClient` class methods to automate your workflows." |
| 65 | + ] |
| 66 | + }, |
| 67 | + { |
| 68 | + "cell_type": "code", |
| 69 | + "execution_count": null, |
| 70 | + "id": "c332f11f", |
| 71 | + "metadata": {}, |
| 72 | + "outputs": [], |
| 73 | + "source": [ |
| 74 | + "import mergin\n", |
| 75 | + "\n", |
| 76 | + "client = mergin.MerginClient(\n", |
| 77 | + " login=LOGIN,\n", |
| 78 | + " password=PASSWORD\n", |
| 79 | + ")" |
| 80 | + ] |
| 81 | + }, |
| 82 | + { |
| 83 | + "cell_type": "markdown", |
| 84 | + "id": "3b3b55cd", |
| 85 | + "metadata": {}, |
| 86 | + "source": [ |
| 87 | + "Now you can use the client to call the API. Let's try to clone the project available for this example (`lutraconsulting/Vienna trees example`) to your Mergin Maps project. You need to specify to which project our sample project will be cloned to (edit the `PROJECT` variable in the form `{WORKSPACE NAME}/{PROJECT NAME}` in Mergin Maps cloud)." |
| 88 | + ] |
| 89 | + }, |
| 90 | + { |
| 91 | + "cell_type": "code", |
| 92 | + "execution_count": null, |
| 93 | + "id": "70f17d60", |
| 94 | + "metadata": {}, |
| 95 | + "outputs": [], |
| 96 | + "source": [ |
| 97 | + "PROJECT=\".../...\"\n", |
| 98 | + "\n", |
| 99 | + "client.clone_project(\"lutraconsulting/Vienna trees example\", PROJECT)" |
| 100 | + ] |
| 101 | + }, |
| 102 | + { |
| 103 | + "cell_type": "markdown", |
| 104 | + "id": "ff9dd71b", |
| 105 | + "metadata": {}, |
| 106 | + "source": [ |
| 107 | + "Project contains GeoPackage `Ready to survey trees` where surveyors can collect trees health and `vienna_trees_gansehauffel.csv` file with all trees from Gänsehäufel in Vienna. Let's download project to your computer using `download_project` method. " |
| 108 | + ] |
| 109 | + }, |
| 110 | + { |
| 111 | + "cell_type": "code", |
| 112 | + "execution_count": null, |
| 113 | + "id": "08fc0642", |
| 114 | + "metadata": {}, |
| 115 | + "outputs": [], |
| 116 | + "source": [ |
| 117 | + "# download project to local folder.\n", |
| 118 | + "LOCAL_FOLDER=\"/tmp/project\"\n", |
| 119 | + "\n", |
| 120 | + "client.download_project(PROJECT, LOCAL_FOLDER)" |
| 121 | + ] |
| 122 | + }, |
| 123 | + { |
| 124 | + "cell_type": "markdown", |
| 125 | + "id": "400194dc", |
| 126 | + "metadata": {}, |
| 127 | + "source": [ |
| 128 | + "We can now add sample points from the `.csv` file to the GeoPackage. These points within the GeoPackage will then be available to surveyors in the field for editing the health column using the Mergin Maps mobile app." |
| 129 | + ] |
| 130 | + }, |
| 131 | + { |
| 132 | + "cell_type": "code", |
| 133 | + "execution_count": null, |
| 134 | + "id": "23469139", |
| 135 | + "metadata": {}, |
| 136 | + "outputs": [], |
| 137 | + "source": [ |
| 138 | + "# Install geopandas to export csv to geopackage\n", |
| 139 | + "!pip install geopandas" |
| 140 | + ] |
| 141 | + }, |
| 142 | + { |
| 143 | + "cell_type": "code", |
| 144 | + "execution_count": null, |
| 145 | + "id": "f3002ce3", |
| 146 | + "metadata": {}, |
| 147 | + "outputs": [], |
| 148 | + "source": [ |
| 149 | + "import pandas as pd\n", |
| 150 | + "import geopandas as gpd\n", |
| 151 | + "import os\n", |
| 152 | + "\n", |
| 153 | + "# Get the data from the CSV (use just sample of data)\n", |
| 154 | + "csv_file = os.path.join(LOCAL_FOLDER, \"vienna_trees_gansehauffel.csv\")\n", |
| 155 | + "csv_df = pd.read_csv(csv_file, nrows=20, dtype={\"health\": str})\n", |
| 156 | + "# Convert geometry in WKT format to GeoDataFrame\n", |
| 157 | + "gdf = gpd.GeoDataFrame(csv_df, geometry=gpd.GeoSeries.from_wkt(csv_df.geometry))\n", |
| 158 | + "print(gdf.head())\n", |
| 159 | + "# Save the GeoDataFrame to a Geopackage\n", |
| 160 | + "gdf.to_file(\n", |
| 161 | + " os.path.join(LOCAL_FOLDER, \"Ready_to_survey_trees.gpkg\"), \n", |
| 162 | + " layer=\"Ready_to_survey_trees\", driver=\"GPKG\",\n", |
| 163 | + ")\n" |
| 164 | + ] |
| 165 | + }, |
| 166 | + { |
| 167 | + "cell_type": "markdown", |
| 168 | + "id": "d440ee5d", |
| 169 | + "metadata": {}, |
| 170 | + "source": [ |
| 171 | + "You can now see changes in GeoPackage file using `project_status` method. " |
| 172 | + ] |
| 173 | + }, |
| 174 | + { |
| 175 | + "cell_type": "code", |
| 176 | + "execution_count": null, |
| 177 | + "id": "d1a67839", |
| 178 | + "metadata": {}, |
| 179 | + "outputs": [ |
| 180 | + { |
| 181 | + "name": "stdout", |
| 182 | + "output_type": "stream", |
| 183 | + "text": [ |
| 184 | + "[{'path': 'Ready_to_survey_trees.gpkg', 'checksum': '3ba7658d231fefe30d9410f41c75f37d1ba5e614', 'size': 98304, 'mtime': datetime.datetime(2025, 5, 27, 16, 24, 30, 122463, tzinfo=tzlocal()), 'origin_checksum': '19b3331abc515a955691401918804d8bcf397ee4', 'chunks': ['be489067-d078-4862-bae1-c1cb222f680a']}]\n" |
| 185 | + ] |
| 186 | + } |
| 187 | + ], |
| 188 | + "source": [ |
| 189 | + "_, push_changes, __ = client.project_status(LOCAL_FOLDER)\n", |
| 190 | + "print(push_changes.get(\"updated\"))" |
| 191 | + ] |
| 192 | + }, |
| 193 | + { |
| 194 | + "cell_type": "markdown", |
| 195 | + "id": "506cfa68", |
| 196 | + "metadata": {}, |
| 197 | + "source": [ |
| 198 | + "You can now use `push_project` method to push data to the server." |
| 199 | + ] |
| 200 | + }, |
| 201 | + { |
| 202 | + "cell_type": "code", |
| 203 | + "execution_count": null, |
| 204 | + "id": "1a167b17", |
| 205 | + "metadata": {}, |
| 206 | + "outputs": [], |
| 207 | + "source": [ |
| 208 | + "client.push_project(LOCAL_FOLDER)" |
| 209 | + ] |
| 210 | + }, |
| 211 | + { |
| 212 | + "cell_type": "markdown", |
| 213 | + "id": "caccc6da", |
| 214 | + "metadata": {}, |
| 215 | + "source": [ |
| 216 | + "To pull the latest version of the project, use `pull_project` method." |
| 217 | + ] |
| 218 | + }, |
| 219 | + { |
| 220 | + "cell_type": "code", |
| 221 | + "execution_count": null, |
| 222 | + "id": "90e5e64a", |
| 223 | + "metadata": {}, |
| 224 | + "outputs": [], |
| 225 | + "source": [ |
| 226 | + "client.pull_project(LOCAL_FOLDER)" |
| 227 | + ] |
| 228 | + }, |
| 229 | + { |
| 230 | + "cell_type": "markdown", |
| 231 | + "id": "e65bb7c9", |
| 232 | + "metadata": {}, |
| 233 | + "source": [ |
| 234 | + "Mobile app users are now enabled to perform updates to the imported tree data directly in the field.\n", |
| 235 | + "\n", |
| 236 | + "<img src=\"./02_sync_assets/synchronized_trees.jpg\" alt=\"drawing\" width=\"250\"/>\n", |
| 237 | + "\n", |
| 238 | + "Editing tree health with predefined values.\n", |
| 239 | + "\n", |
| 240 | + "<img src=\"./02_sync_assets/edit_tree_health.jpg\" alt=\"drawing\" width=\"250\"/>" |
| 241 | + ] |
| 242 | + } |
| 243 | + ], |
| 244 | + "metadata": { |
| 245 | + "kernelspec": { |
| 246 | + "display_name": "python-api-client", |
| 247 | + "language": "python", |
| 248 | + "name": "python3" |
| 249 | + }, |
| 250 | + "language_info": { |
| 251 | + "codemirror_mode": { |
| 252 | + "name": "ipython", |
| 253 | + "version": 3 |
| 254 | + }, |
| 255 | + "file_extension": ".py", |
| 256 | + "mimetype": "text/x-python", |
| 257 | + "name": "python", |
| 258 | + "nbconvert_exporter": "python", |
| 259 | + "pygments_lexer": "ipython3", |
| 260 | + "version": "3.10.14" |
| 261 | + } |
| 262 | + }, |
| 263 | + "nbformat": 4, |
| 264 | + "nbformat_minor": 5 |
| 265 | +} |
0 commit comments