Mit ArcGIS Runtime können native Apps für alle gängigen Plattformen erstellt werden. Native Apps sind besonders geeignet für mobile Szenarien für den Außendienst. Da im Feld eine stabile Internetverbindung oftmals nicht garantiert ist, sollten die Maps inklusive aller zugehörigen Daten lokal auf dem Gerät gespeichert sein. Es gibt bisher schon mehrere Möglichkeiten, dies mit ArcGIS Runtime zu tun:
Diese Möglichkeiten werden auch in ArcGIS Apps wie Collector, Navigator oder Explorer genutzt. Seit der ArcGIS Runtime Version 100.2 gibt es nun zusätzlich noch den Preplanned Workflow für die durchgängige und automatisierbare Planung von mobilen offline Szenarien.
Der Preplanned Workflow
In einem üblichen Außendienstszenario haben mehrere Außendienstmitarbeiter den gleichen Auftrag, aber in verschiedenen Arbeitsgebieten. Ein Beispiel ist die Inspektion von Straßen in einer Stadt, in der verschiedene Mitarbeiter in unterschiedlichen Stadtgebieten unterwegs sind. Ein Mitarbeiter benötigt in der Regel nur die mobilen Daten von seinem Gebiet und nicht die Daten der kompletten Map. Oftmals darf er auch nur die Daten seines eigenen Gebietes sehen.

Der Preplanned Workflow ist genau für solche Szenarien konzipiert. Er besteht im Wesentlichen aus zwei Teilen – die Planung von Arbeitsgebieten im Büro und die Nutzung der „Teil-Maps“ in ArcGIS Runtime Apps im Außendienst.

Planung von Arbeitsgebieten im Büro
Voraussetzung ist eine offlinefähige WebMap. Von dieser WebMap wird die Portal Item ID benötigt (die Zeichenfolge in der URL).

Dann werden die relevanten Arbeitsgebiete (MapAreas) in der WebMap definiert. Der einfachste Weg ist Bookmarks zu erstellen. Es können aber auch Extents der Gebiete ermittelt und im JSON Format genutzt werden, z.B. so:
{
"xmin": -13184700,
"ymin": 3988556,
"xmax": -13004945,
"ymax": 4061479,
"spatialReference": {
"wkid": 102100
}}<SPAN class="line-numbers-rows"><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN></SPAN>Mithilfe des neuen Offline Packaging Geoprocessing Service für den Preplanned Workflow können dann folgende Tasks erledigt werden:
Für eine WebMap können bis zu 16 MapAreas erstellt werden. Die mobilen Daten der jeweiligen MapAreas werden direkt im offline nutzbaren Format im Portal gespeichert:
- Features als SQLite Datenbank
- Vector Tile Layer als Vector Tile Packages (.vtpk)
- Rasterdaten als Tile Packages (.tpk)
Die mobilen Daten haben eine Referenz zu der zugehörigen MapArea. Und alle MapAreas haben eine Referenz zu der zugehörigen WebMap. Das bedeutet: wird eine MapArea gelöscht, werden auch die zugehörigen mobilen Daten im Portal gelöscht. Und wird die WebMap gelöscht, sind alle MapAreas plus mobile Daten auch weg.
MapAreas und zugehörige mobile Daten werden nicht in der Standard-Oberfläche des Portals angezeigt. Im Catalog in ArcGIS Pro können diese Items angezeigt oder gelöscht werden. Eine weitere Möglichkeit ist mit dem AGOL Assistant.
Wie wird es in der Praxis umgesetzt?
Geoprocessing Services können in Web Apps mit der ArcGIS API for JavaScript und in nativen Apps mit ArcGIS Runtime genutzt werden. Mit beiden wäre es z.B. möglich, Konfigurator-Apps für Planer im Büro zu erstellen. Eine weitere Option ist die Nutzung des Offline Packaging Geoprocessing Service direkt in ArcGIS Desktop. Mein persönlicher Favorit ist allerdings die Implementierung mit der ArcGIS API for Python und dem OfflineMapAreaManager. Mit diesen paar Zeilen Python Code werden MapAreas von allen Bookmarks einer WebMap erzeugt und auch gleich die zugehörigen mobilen Daten generiert:
<SPAN class="comment token"># Import modules</SPAN>
<SPAN class="keyword token">from</SPAN> arcgis<SPAN class="punctuation token">.</SPAN>gis <SPAN class="keyword token">import</SPAN> GIS
<SPAN class="keyword token">from</SPAN> arcgis<SPAN class="punctuation token">.</SPAN>mapping <SPAN class="keyword token">import</SPAN> WebMap
<SPAN class="keyword token">from</SPAN> getpass <SPAN class="keyword token">import</SPAN> getpass
<SPAN class="comment token"># Setup the organization and user information</SPAN>
password<SPAN class="operator token">=</SPAN>getpass<SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN>
gis <SPAN class="operator token">=</SPAN> GIS<SPAN class="punctuation token">(</SPAN><SPAN class="string token"><SPAN>'</SPAN><A class="jive-link-external-small" href="https://community.esri.com/external-link.jspa?url=https%3A%2F%2Fwww.arcgis.com" target="_blank">https://www.arcgis.com</A><SPAN>'</SPAN></SPAN><SPAN class="punctuation token">,</SPAN> <SPAN class="string token">'my named user'</SPAN><SPAN class="punctuation token">,</SPAN> password<SPAN class="punctuation token">)</SPAN>
<SPAN class="comment token"># get the webmap from the portal item id</SPAN>
offline_map_item <SPAN class="operator token">=</SPAN> gis<SPAN class="punctuation token">.</SPAN>content<SPAN class="punctuation token">.</SPAN>get<SPAN class="punctuation token">(</SPAN><SPAN class="string token">"my webmap id"</SPAN><SPAN class="punctuation token">)</SPAN>
offline_webmap <SPAN class="operator token">=</SPAN> WebMap<SPAN class="punctuation token">(</SPAN>offline_map_item<SPAN class="punctuation token">)</SPAN>
<SPAN class="comment token"># Loop through the webmap's bookmarks and create a map area for each one bookmark</SPAN>
<SPAN class="keyword token">for</SPAN> bookmark <SPAN class="keyword token">in</SPAN> offline_webmap<SPAN class="punctuation token">.</SPAN>definition<SPAN class="punctuation token">.</SPAN>bookmarks<SPAN class="punctuation token">:</SPAN>
bookmark_name <SPAN class="operator token">=</SPAN> bookmark<SPAN class="punctuation token">.</SPAN>name
item_prop <SPAN class="operator token">=</SPAN> <SPAN class="punctuation token">{</SPAN><SPAN class="string token">'title'</SPAN><SPAN class="punctuation token">:</SPAN> offline_map_item<SPAN class="punctuation token">.</SPAN>title <SPAN class="operator token">+</SPAN> <SPAN class="string token">'_'</SPAN> <SPAN class="operator token">+</SPAN> bookmark_name <SPAN class="operator token">+</SPAN> <SPAN class="string token">'_MapArea'</SPAN><SPAN class="punctuation token">,</SPAN>
<SPAN class="string token">'snippet'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="string token">'my snippet'</SPAN><SPAN class="punctuation token">,</SPAN>
<SPAN class="string token">'description'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="string token">'my description'</SPAN><SPAN class="punctuation token">,</SPAN>
<SPAN class="string token">'tags'</SPAN><SPAN class="punctuation token">:</SPAN> <SPAN class="punctuation token">[</SPAN><SPAN class="string token">'python api'</SPAN><SPAN class="punctuation token">,</SPAN> <SPAN class="string token">'MapArea'</SPAN><SPAN class="punctuation token">,</SPAN> <SPAN class="string token">'my tag'</SPAN><SPAN class="punctuation token">]</SPAN><SPAN class="punctuation token">}</SPAN>
offline_area <SPAN class="operator token">=</SPAN> offline_webmap<SPAN class="punctuation token">.</SPAN>offline_areas<SPAN class="punctuation token">.</SPAN>create<SPAN class="punctuation token">(</SPAN>area<SPAN class="operator token">=</SPAN>bookmark_name<SPAN class="punctuation token">,</SPAN> folder<SPAN class="operator token">=</SPAN><SPAN class="string token">'my portal content folder'</SPAN><SPAN class="punctuation token">,</SPAN> item_properties<SPAN class="operator token">=</SPAN>item_prop<SPAN class="punctuation token">)</SPAN> <SPAN class="line-numbers-rows"><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN></SPAN>Für einen schnellen Test kann das Python Notebook im Anhang dieses Blogs auf notebooks.esri.com genutzt werden (Upload).
Nutzen von MapAreas in ArcGIS Runtime Apps im Außendienst
Die MapAreas einer WebMap und die zugehörigen mobilen Daten sind nun im Portal erstellt worden. Jetzt müssen sie nur noch mit ArcGIS Runtime heruntergeladen und genutzt werden. Die Preplanned Workflow Funktionen in den SDKs sind erst ab ArcGIS Runtime Version 100.2 verfügbar. Hier sind die groben Schritte für die Implementierung am Beispiel des .NET SDKs:
1. Laden der WebMap
<SPAN class="keyword token">var</SPAN> portal <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">await</SPAN> ArcGISPortal<SPAN class="punctuation token">.</SPAN><SPAN class="token function">CreateAsync</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="keyword token">var</SPAN> webmapItem <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">await</SPAN> PortalItem<SPAN class="punctuation token">.</SPAN><SPAN class="token function">CreateAsync</SPAN><SPAN class="punctuation token">(</SPAN>portal<SPAN class="punctuation token">,</SPAN> <SPAN class="string token">"WebMap Portal ID"</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="keyword token">var</SPAN> myMap <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">new</SPAN> <SPAN class="token class-name">Map</SPAN><SPAN class="punctuation token">(</SPAN>webmapItem<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN><SPAN class="line-numbers-rows"><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN></SPAN>
2. Abfragen der MapAreas einer WebMap
<SPAN class="keyword token">var</SPAN> offlineMapTask <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">await</SPAN> OfflineMapTask<SPAN class="punctuation token">.</SPAN><SPAN class="token function">CreateAsync</SPAN><SPAN class="punctuation token">(</SPAN>myMap<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="keyword token">var</SPAN> preplannedMapAreas <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">await</SPAN> offlineMapTask<SPAN class="punctuation token">.</SPAN><SPAN class="token function">GetPreplannedMapAreasAsync</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN><SPAN class="line-numbers-rows"><SPAN></SPAN><SPAN></SPAN></SPAN>
3. Download der Infos und Daten einer MapArea als exploded Mobile Map Package
<SPAN class="keyword token">var</SPAN> myArea <SPAN class="operator token">=</SPAN> preplannedMapAreas<SPAN class="punctuation token">.</SPAN><SPAN class="token function">FirstOrDefault</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="keyword token">var</SPAN> downloadJob <SPAN class="operator token">=</SPAN> offlineMapTask<SPAN class="punctuation token">.</SPAN><SPAN class="token function">DownloadPreplannedOfflineMap</SPAN><SPAN class="punctuation token">(</SPAN>myArea<SPAN class="punctuation token">,</SPAN> <SPAN class="string token">"Speicherpfad"</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="keyword token">var</SPAN> results <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">await</SPAN> downloadJob<SPAN class="punctuation token">.</SPAN><SPAN class="token function">GetResultAsync</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN><SPAN class="line-numbers-rows"><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN></SPAN>
4. Laden und Anzeigen der lokal gespeicherten MapArea in der Map
<SPAN class="keyword token">var</SPAN> offlineMapArea <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">await</SPAN> MobileMapPackage<SPAN class="punctuation token">.</SPAN><SPAN class="token function">OpenAsync</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="string token">"Speicherpfad"</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
myMap <SPAN class="operator token">=</SPAN> offlineMapArea<SPAN class="punctuation token">.</SPAN>Maps<SPAN class="punctuation token">[</SPAN><SPAN class="number token">0</SPAN><SPAN class="punctuation token">]</SPAN><SPAN class="punctuation token">;</SPAN><SPAN class="line-numbers-rows"><SPAN></SPAN><SPAN></SPAN></SPAN>
Synchronisierung von Edits mit dem Feature Service
Für die Synchronisierung von Feature Edits in beide Richtungen wird die lokale SQLite Geodatabase automatisch durch den OfflineMapTask beim Feature Service registriert. Darum muss sich der Entwickler nicht kümmern. So wird synchronisiert:
<SPAN class="keyword token">var</SPAN> editLayer <SPAN class="operator token">=</SPAN> <SPAN class="punctuation token">(</SPAN>FeatureLayer<SPAN class="punctuation token">)</SPAN>myMap<SPAN class="punctuation token">.</SPAN>OperationalLayers<SPAN class="punctuation token">[</SPAN><SPAN class="number token">0</SPAN><SPAN class="punctuation token">]</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="keyword token">var</SPAN> featureTable <SPAN class="operator token">=</SPAN> <SPAN class="punctuation token">(</SPAN>GeodatabaseFeatureTable<SPAN class="punctuation token">)</SPAN>editLayer<SPAN class="punctuation token">.</SPAN>FeatureTable<SPAN class="punctuation token">;</SPAN>
<SPAN class="keyword token">var</SPAN> syncTask <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">await</SPAN> GeodatabaseSyncTask<SPAN class="punctuation token">.</SPAN><SPAN class="token function">CreateAsync</SPAN><SPAN class="punctuation token">(</SPAN>featureTable<SPAN class="punctuation token">.</SPAN>Geodatabase<SPAN class="punctuation token">.</SPAN>Source<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="comment token">// sync geodatabase</SPAN>
<SPAN class="keyword token">var</SPAN> taskParameters <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">await</SPAN> syncTask<SPAN class="punctuation token">.</SPAN><SPAN class="token function">CreateDefaultSyncGeodatabaseParametersAsync</SPAN><SPAN class="punctuation token">(</SPAN>featureTable<SPAN class="punctuation token">.</SPAN>Geodatabase<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="keyword token">var</SPAN> syncJob <SPAN class="operator token">=</SPAN> syncTask<SPAN class="punctuation token">.</SPAN><SPAN class="token function">SyncGeodatabase</SPAN><SPAN class="punctuation token">(</SPAN>taskParameters<SPAN class="punctuation token">,</SPAN> featureTable<SPAN class="punctuation token">.</SPAN>Geodatabase<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="keyword token">var</SPAN> result <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">await</SPAN> syncJob<SPAN class="punctuation token">.</SPAN><SPAN class="token function">GetResultAsync</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="line-numbers-rows"><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN></SPAN>
Bevor eine Geodatabase auf dem Gerät gelöscht werden soll, muss sie vorher beim Feature Service auch wieder deregistriert werden (sonst wird er irgendwann mit Unmengen Replicas zugemüllt). Weiterführende Informationen dazu stehen in der Dokumentation. So kann es umgesetzt werden:
<SPAN class="keyword token">var</SPAN> editLayer <SPAN class="operator token">=</SPAN> <SPAN class="punctuation token">(</SPAN>FeatureLayer<SPAN class="punctuation token">)</SPAN>myMap<SPAN class="punctuation token">.</SPAN>OperationalLayers<SPAN class="punctuation token">[</SPAN><SPAN class="number token">0</SPAN><SPAN class="punctuation token">]</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="keyword token">var</SPAN> featureTable <SPAN class="operator token">=</SPAN> <SPAN class="punctuation token">(</SPAN>GeodatabaseFeatureTable<SPAN class="punctuation token">)</SPAN>editLayer<SPAN class="punctuation token">.</SPAN>FeatureTable<SPAN class="punctuation token">;</SPAN>
<SPAN class="keyword token">var</SPAN> syncTask <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">await</SPAN> GeodatabaseSyncTask<SPAN class="punctuation token">.</SPAN><SPAN class="token function">CreateAsync</SPAN><SPAN class="punctuation token">(</SPAN>featureTable<SPAN class="punctuation token">.</SPAN>Geodatabase<SPAN class="punctuation token">.</SPAN>Source<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="comment token">// unregister geodatabase</SPAN>
<SPAN class="keyword token">await</SPAN> syncTask<SPAN class="punctuation token">.</SPAN><SPAN class="token function">UnregisterGeodatabaseAsync</SPAN><SPAN class="punctuation token">(</SPAN>featureTable<SPAN class="punctuation token">.</SPAN>Geodatabase<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="comment token">// delete geodatabase</SPAN><SPAN class="line-numbers-rows"><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN><SPAN></SPAN></SPAN>
Abschließende Worte
Mit dem Preplanned Workflow wurde das Thema mobile offline Szenarien in ArcGIS zu Ende gedacht. Das Ganze ist jetzt eine runde Sache, hat aber noch an der einen oder anderen Stelle Optimierungspotential, z.B. wären Shapes als MapAreas nützlicher statt nur rechteckiger Flächen (welches Arbeitsgebiet ist schon exakt rechteckig). Wir werden noch Verbesserungen sehen und insgesamt ist er schon sehr hilfreich. Den Preplanned Workflow werden wir demnächst auch in einigen ArcGIS Apps (Collector, Explorer) wiederfinden. Momentan ist er nur in ArcGIS Online verfügbar, in ArcGIS Enterprise wird er in einer späteren Version implementiert.
P.s.: Die Abschnitte zum Erzeugen von MapAreas mit der Python API (Im Anhang ist ein neues Python Notebook) und Synchronisieren von Feature Edits wurden leicht überarbeitet. Vielen Dank an Antti Kajanus vom ArcGIS Runtime Team für den Tipp mit der smarten Python API Implementierung und seine Korrektur von Register/Unregister von Geodatenbanken. 