ArcGIS Runtime ist eine Familie aus nativen SDKs zur Entwicklung von Geo-Apps für alle populären Mobile und Desktop Plattformen. Für Desktops gibt es mit dem Runtime Local Server ein zusätzliches SDK zur Implementierung von erweiterter Geo-Funktionalität. Mit diesen wollen wir uns in dem Blog näher beschäftigen:
Was ist der ArcGIS Runtime Local Server?
Wie der Name schon andeutet ist der Runtime Local Server ein lokal auf dem Gerät laufender Web GIS Server. Dieser wird aber nicht installiert, sondern zur Laufzeit der ArcGIS Runtime Anwendung als Prozess erzeugt und verwaltet. Wird die Anwendung beendet, beendet sich damit auch der Local Server. Pro ausgeführte Anwendung kann nur ein Local Server gestartet werden (Singleton Pattern), es können aber mehrere Anwendungen mit jeweils eigenem Local Server nebeneinander laufen.

Die lokalen GIS Services für den Local Server werden auch zur Laufzeit erzeugt und verwaltet. Diese basieren auf lokalen Datenpaketen, welche zuvor mit ArcGIS Desktop erzeugt werden müssen. Genau wie in ArcGIS Online und ArcGIS Enterprise werden diese GIS Services über die ArcGIS Server REST API, also über URLs angesprochen. In ArcGIS Runtime ist es somit egal aus welcher Quelle ein GIS Service stammt, es können die gleichen Tasks und Layer auch dafür genutzt werden.
Der Schwerpunkt des Runtime Local Server liegt ganz klar auf dem lokalen Geoprocessing. Hunderte Analyse Tools aus der ArcToolbox, mächtige Model Builder Analyse Modelle und auch eigene Python Skripte in ArcGIS Desktop können zu Geoprocessing Packages gekapselt und diese durch lokale Geoprocessing Services genutzt werden. Das ist der Schlüssel für hochspezialisierten ArcGIS Runtime Anwendungen auch für GIS-Experten.
Es können noch weitere Typen lokaler Services erzeugt werden, dazu später mehr.
Unterstütze Plattformen
Folgende ArcGIS Runtime SDKs unterstützen den Runtime Local Server:
ArcGIS Runtime SDK for | Local Server Unterstützung für: | Anmerkungen |
.NET | · Windows (32/64 Bit) | Es wird nur die WPF API unterstützt. |
Java | · Windows (32/64 Bit) · Linux (64 Bit) | MacOS wird nicht unterstützt. |
Qt | · Windows (32/64 Bit) · Linux (64 Bit) | Es wird nur die Qt/C++ API unterstützt. |
Weitere mögliche Zielplattformen der SDKs in der Tabelle und auch die ArcGIS Runtime SDKs for Android, iOS und macOS werden nicht unterstützt. Dies lässt sich auch nicht mit Cross-Platform Technologie (z.B. Xamarin oder QML API) umgehen.
Download und Lizensierung
ArcGIS Entwickler können mit dem Local Server ab der kostenlosen ArcGIS Developer Subscription „Essentials“ entwickeln und testen. Das ArcGIS Runtime Local Server SDK gibt es für Windows und Linux und wird im Downloadbereich des ArcGIS Developers Portals heruntergeladen.

Die Lizenzstufe einer fertigen Anwendung mit Runtime Local Server Technologie hängt von der implementierten Funktionalität ab. Die Mindeststufe ist dabei ArcGIS Runtime License Level Standard. Für manche Funktionen wird zusätzlich noch die Analysis Extension benötigt. Für Geoprocessing Tools werden die benötigten Lizenzstufenstufen hier aufgeschlüsselt.
Datenquellen und Funktionalität
Map- und Geoprocessing Packages können im Moment nur in ArcMap erzeugt werden. Dabei ist folgendes zu beachten:
Folgendes ist möglich (Auszug):
Implementierung
In der Dokumentation der ArcGIS Runtime SDKs for .NET/WPF, Java und Qt/C++ steht die Implementierung des ArcGIS Runtime Local Server ausführlich beschrieben. Hier die Kurzfassung:
- Download und Installation
- Erstellen eines Local Server Deployments für das Projekt (Welche Funktionalität soll genutzt werden!)
- Local Server Deployment Builder tool (Java und Qt)
- Deployment manifest file im Visual Studio Projekt (.NET)
- Im Code:
- Starten des Runtime Local Server
- Erzeugen und Starten von lokalen Services auf Basis von Packages
- Nutzen der lokalen Services in den entsprechenden ArcGIS Runtime Tasks und Layer
Mit dem ArcGIS Runtime SDK for .Net am Beispiel eines einfachen Buffer Tools (Download) sieht es so aus:
(a) Starten des Runtime Local Server
<SPAN class="keyword token">private</SPAN> LocalServer _localServer<SPAN class="punctuation token">;</SPAN>
<SPAN class="keyword token">private</SPAN> LocalGeoprocessingService _localGpService<SPAN class="punctuation token">;</SPAN>
<SPAN class="keyword token">private</SPAN> <SPAN class="keyword token">async</SPAN> <SPAN class="keyword token">void</SPAN> <SPAN class="token function">StartLocalServer</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="punctuation token">{</SPAN>
<SPAN class="keyword token">try</SPAN>
<SPAN class="punctuation token">{</SPAN>
<SPAN class="comment token">// Get the singleton LocalServer object using the static "Instance" property</SPAN>
_localServer <SPAN class="operator token">=</SPAN> LocalServer<SPAN class="punctuation token">.</SPAN>Instance<SPAN class="punctuation token">;</SPAN>
_localServer<SPAN class="punctuation token">.</SPAN>AppDataPath <SPAN class="operator token">=</SPAN> <SPAN class="string token">@"D:\Temp\LocalServerData"</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="comment token">// Handle the StatusChanged event to react when the server is started</SPAN>
_localServer<SPAN class="punctuation token">.</SPAN>StatusChanged <SPAN class="operator token">+</SPAN><SPAN class="operator token">=</SPAN> <SPAN class="punctuation token">(</SPAN>sender<SPAN class="punctuation token">,</SPAN> statusChangedEventArgs<SPAN class="punctuation token">)</SPAN> <SPAN class="operator token">=</SPAN><SPAN class="operator token">></SPAN>
<SPAN class="punctuation token">{</SPAN>
<SPAN class="comment token">// Check if the server started successfully</SPAN>
<SPAN class="keyword token">if</SPAN> <SPAN class="punctuation token">(</SPAN>statusChangedEventArgs<SPAN class="punctuation token">.</SPAN>Status <SPAN class="operator token">==</SPAN> LocalServerStatus<SPAN class="punctuation token">.</SPAN>Started<SPAN class="punctuation token">)</SPAN>
<SPAN class="punctuation token">{</SPAN>
<SPAN class="comment token">// Start specific local services here ...</SPAN>
<SPAN class="token function">InitializeGpService</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="punctuation token">}</SPAN>
<SPAN class="punctuation token">}</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="comment token">// Start the local server instance</SPAN>
<SPAN class="keyword token">await</SPAN> _localServer<SPAN class="punctuation token">.</SPAN><SPAN class="token function">StartAsync</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="punctuation token">}</SPAN>
<SPAN class="keyword token">catch</SPAN> <SPAN class="punctuation token">(</SPAN><SPAN class="token class-name">Exception</SPAN> e<SPAN class="punctuation token">)</SPAN>
<SPAN class="punctuation token">{</SPAN>
Console<SPAN class="punctuation token">.</SPAN><SPAN class="token function">WriteLine</SPAN><SPAN class="punctuation token">(</SPAN>e<SPAN class="punctuation token">)</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></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>(b) Erzeugen und Starten eines lokalen Services auf Basis eines Geoprocessing Packages (Download)
<SPAN class="keyword token">private</SPAN> <SPAN class="keyword token">async</SPAN> <SPAN class="keyword token">void</SPAN> <SPAN class="token function">InitializeGpService</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN>
<SPAN class="punctuation token">{</SPAN>
<SPAN class="keyword token">try</SPAN>
<SPAN class="punctuation token">{</SPAN>
<SPAN class="comment token">// Create a local GP service from a geoprocessing package on disk</SPAN>
_localGpService <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">new</SPAN> <SPAN class="token class-name">LocalGeoprocessingService</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="string token">@"data\simple-buffer.gpk"</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="comment token">// Handle the status changed event to check when it's loaded</SPAN>
_localGpService<SPAN class="punctuation token">.</SPAN>StatusChanged <SPAN class="operator token">+</SPAN><SPAN class="operator token">=</SPAN> <SPAN class="punctuation token">(</SPAN>svc<SPAN class="punctuation token">,</SPAN> args<SPAN class="punctuation token">)</SPAN> <SPAN class="operator token">=</SPAN><SPAN class="operator token">></SPAN>
<SPAN class="punctuation token">{</SPAN>
<SPAN class="comment token">// If service started successfully, create a gp task</SPAN>
<SPAN class="keyword token">if</SPAN> <SPAN class="punctuation token">(</SPAN>args<SPAN class="punctuation token">.</SPAN>Status <SPAN class="operator token">==</SPAN> LocalServerStatus<SPAN class="punctuation token">.</SPAN>Started<SPAN class="punctuation token">)</SPAN>
<SPAN class="punctuation token">{</SPAN>
<SPAN class="comment token">//do something, if needed</SPAN>
<SPAN class="punctuation token">}</SPAN>
<SPAN class="punctuation token">}</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="comment token">// Start the local geoprocessing service</SPAN>
<SPAN class="keyword token">await</SPAN> _localGpService<SPAN class="punctuation token">.</SPAN><SPAN class="token function">StartAsync</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="punctuation token">}</SPAN>
<SPAN class="keyword token">catch</SPAN> <SPAN class="punctuation token">(</SPAN><SPAN class="token class-name">Exception</SPAN> e<SPAN class="punctuation token">)</SPAN>
<SPAN class="punctuation token">{</SPAN>
Console<SPAN class="punctuation token">.</SPAN><SPAN class="token function">WriteLine</SPAN><SPAN class="punctuation token">(</SPAN>e<SPAN class="punctuation token">)</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></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>(c) Nutzen des lokalen Services mit einem Geoprocessing Tasks
<SPAN class="keyword token">private</SPAN> <SPAN class="keyword token">async</SPAN> <SPAN class="keyword token">void</SPAN> <SPAN class="token function">MyMapViewOnGeoViewTapped</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="keyword token">object</SPAN> sender<SPAN class="punctuation token">,</SPAN> GeoViewInputEventArgs e<SPAN class="punctuation token">)</SPAN>
<SPAN class="punctuation token">{</SPAN>
<SPAN class="keyword token">var</SPAN> gpUri <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">new</SPAN> <SPAN class="token class-name">Uri</SPAN><SPAN class="punctuation token">(</SPAN>_localServer<SPAN class="punctuation token">.</SPAN>Services<SPAN class="punctuation token">[</SPAN><SPAN class="number token">0</SPAN><SPAN class="punctuation token">]</SPAN><SPAN class="punctuation token">.</SPAN>Url<SPAN class="punctuation token">.</SPAN>AbsoluteUri <SPAN class="operator token">+</SPAN> <SPAN class="string token">"/SimpleBuffer"</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="comment token">// Create parameters, run the task, process results, etc.</SPAN>
<SPAN class="comment token">// ...</SPAN>
<SPAN class="keyword token">var</SPAN> geoprocessorTask <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">await</SPAN> GeoprocessingTask<SPAN class="punctuation token">.</SPAN><SPAN class="token function">CreateAsync</SPAN><SPAN class="punctuation token">(</SPAN>gpUri<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="keyword token">var</SPAN> parameter <SPAN class="operator token">=</SPAN>
<SPAN class="keyword token">new</SPAN> <SPAN class="token class-name">GeoprocessingParameters</SPAN><SPAN class="punctuation token">(</SPAN>GeoprocessingExecutionType<SPAN class="punctuation token">.</SPAN>AsynchronousSubmit<SPAN class="punctuation token">)</SPAN>
<SPAN class="punctuation token">{</SPAN>
OutputSpatialReference <SPAN class="operator token">=</SPAN> MyMapView<SPAN class="punctuation token">.</SPAN>SpatialReference
<SPAN class="punctuation token">}</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="keyword token">var</SPAN> myInputFeatures <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">new</SPAN> <SPAN class="token class-name">FeatureCollectionTable</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="keyword token">new</SPAN> <SPAN class="token class-name">List</SPAN><SPAN class="operator token"><</SPAN>Field<SPAN class="operator token">></SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">,</SPAN> GeometryType<SPAN class="punctuation token">.</SPAN>Point<SPAN class="punctuation token">,</SPAN> MyMapView<SPAN class="punctuation token">.</SPAN>SpatialReference<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="comment token">// Create a new feature from the feature collection table. It will not have a coordinate location (x,y) yet</SPAN>
<SPAN class="keyword token">var</SPAN> myInputFeature <SPAN class="operator token">=</SPAN> myInputFeatures<SPAN class="punctuation token">.</SPAN><SPAN class="token function">CreateFeature</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="comment token">// Assign a physical location to the new point feature based upon where the user clicked in the map view</SPAN>
myInputFeature<SPAN class="punctuation token">.</SPAN>Geometry <SPAN class="operator token">=</SPAN> e<SPAN class="punctuation token">.</SPAN>Location<SPAN class="punctuation token">;</SPAN>
<SPAN class="comment token">// Add the new feature with (x,y) location to the feature collection table</SPAN>
<SPAN class="keyword token">await</SPAN> myInputFeatures<SPAN class="punctuation token">.</SPAN><SPAN class="token function">AddFeatureAsync</SPAN><SPAN class="punctuation token">(</SPAN>myInputFeature<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
parameter<SPAN class="punctuation token">.</SPAN>Inputs<SPAN class="punctuation token">.</SPAN><SPAN class="token function">Add</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="string token">"InputFeatures"</SPAN><SPAN class="punctuation token">,</SPAN> <SPAN class="keyword token">new</SPAN> <SPAN class="token class-name">GeoprocessingFeatures</SPAN><SPAN class="punctuation token">(</SPAN>myInputFeatures<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
parameter<SPAN class="punctuation token">.</SPAN>Inputs<SPAN class="punctuation token">.</SPAN><SPAN class="token function">Add</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="string token">"Distance"</SPAN><SPAN class="punctuation token">,</SPAN> <SPAN class="keyword token">new</SPAN> <SPAN class="token class-name">GeoprocessingLinearUnit</SPAN><SPAN class="punctuation token">(</SPAN><SPAN class="number token">1000</SPAN><SPAN class="punctuation token">,</SPAN> GeoprocessingLinearUnits<SPAN class="punctuation token">.</SPAN>Kilometer<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
GeoprocessingJob job <SPAN class="operator token">=</SPAN> geoprocessorTask<SPAN class="punctuation token">.</SPAN><SPAN class="token function">CreateJob</SPAN><SPAN class="punctuation token">(</SPAN>parameter<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="keyword token">try</SPAN>
<SPAN class="punctuation token">{</SPAN>
<SPAN class="comment token">// Execute analysis and wait for the results</SPAN>
<SPAN class="keyword token">var</SPAN> analysisResult <SPAN class="operator token">=</SPAN> <SPAN class="keyword token">await</SPAN> job<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="keyword token">var</SPAN> outputFeatures <SPAN class="operator token">=</SPAN> analysisResult<SPAN class="punctuation token">.</SPAN>Outputs<SPAN class="punctuation token">[</SPAN><SPAN class="string token">"BufferOutput"</SPAN><SPAN class="punctuation token">]</SPAN> <SPAN class="keyword token">as</SPAN> GeoprocessingFeatures<SPAN class="punctuation token">;</SPAN>
<SPAN class="punctuation token">}</SPAN>
<SPAN class="keyword token">catch</SPAN> <SPAN class="punctuation token">(</SPAN><SPAN class="token class-name">TaskCanceledException</SPAN> cancelledException<SPAN class="punctuation token">)</SPAN>
<SPAN class="punctuation token">{</SPAN>
<SPAN class="comment token">// This is thrown if the task is cancelled.</SPAN>
MessageBox<SPAN class="punctuation token">.</SPAN><SPAN class="token function">Show</SPAN><SPAN class="punctuation token">(</SPAN>cancelledException<SPAN class="punctuation token">.</SPAN>Message<SPAN class="punctuation token">)</SPAN><SPAN class="punctuation token">;</SPAN>
<SPAN class="punctuation token">}</SPAN>
<SPAN class="keyword token">catch</SPAN> <SPAN class="punctuation token">(</SPAN><SPAN class="token class-name">Exception</SPAN> exception<SPAN class="punctuation token">)</SPAN>
<SPAN class="punctuation token">{</SPAN>
MessageBox<SPAN class="punctuation token">.</SPAN><SPAN class="token function">Show</SPAN><SPAN class="punctuation token">(</SPAN>exception<SPAN class="punctuation token">.</SPAN>Message<SPAN class="punctuation token">)</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></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><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>
Ergebnis
Resümee
Mit der Local Server Technologie können fokussierte ArcGIS Runtime Anwendungen mit Werkzeugen aus ArcGIS Desktop für den GIS-Expertenbereich erstellt werden. Weiterhin gibt es die Möglichkeit, direkt auf Daten einer SDE zuzugreifen, viele verschiedene lokale Datentypen zu nutzen und vieles mehr. Und das mit modernster Technologie (64 Bit, Asynchron usw.) für verschiedene Desktop Plattformen. Wenn das nicht Grund genug für ArcGIS Engine/ArcObjects Entwickler ist … 