Demonstrates how to start and stop the Local Server and start and stop a local map, feature, and geoprocessing service running on the Local Server.

Use case
For executing offline geoprocessing tasks in your apps via an offline (local) server.
How to use the sample
Click Start Local Server to start the Local Server. Click Stop Local Server to stop the Local Server.
The Map Service combo box lets you to pick a local service that is available.
After browsing for the desired file, click Start Service to start the selected service.
When the running service’s url appears, select it and click Open Url. To stop this running service, click Stop Service.
How it works
- Create it with
LocalServer::instanceand useLocalServer::start()to start the server asynchronously. LocalServer::statusChanged()fires whenever the running status of the Local Server changes. Wait for the server to be in theLocalServerStatus::Startedstate.- Create and run a local service. Here is an example of running a
LocalMapService:new LocalMapService(Url)creates a local map service with the given URL path to map package (mpkxfile).LocalMapService::start()starts the service asynchronously.- The service is added to the
LocalServerautomatically.
To stop a LocalServer and stop any LocalServices that are added to it:
- Get any services that are currently running on the local server with
LocalServer::services(). - Loop through all services and stop the selected service (from the dropdown) if it is started.
- Use
LocalService::status()to check if the service’s status isLocalServerStatus::Started. LocalService::stop()stops the service asynchronously.LocalService::statusChanged()fires whenever the running status of the local service has changed. Wait for all services to be in theLocalServerStatus::Stoppedstate.- Stop the local server with
LocalServer::stop().
Relevant API
- LocalFeatureService
- LocalGeoprocessingService
- LocalMapService
- LocalServer
- LocalServerStatus
- LocalService
Offline Data
Read more about how to set up the sample’s offline data here.
| Link | Local Location |
|---|---|
| PointsOfInterest map package | <userhome>/ArcGIS/Runtime/Data/mpkx/PointsofInterest.mpkx |
| Contour geoprocessing package | <userhome>/ArcGIS/Runtime/Data/gpkx/Contour.gpkx |
Additional information
Local Server can be downloaded for Windows and Linux platforms. Local Server is not supported on macOS.
Tags
feature, geoprocessing, local services, map, server, service
Sample Code
// [WriteFile Name=LocalServerServices, Category=LocalServer]// [Legal]// Copyright 2017 Esri.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// [Legal]
#ifdef PCH_BUILD#include "pch.hpp"#endif // PCH_BUILD
// sample headers#include "LocalServerServices.h"
// ArcGIS Maps SDK headers#include "LocalFeatureService.h"#include "LocalGeoprocessingService.h"#include "LocalMapService.h"#include "LocalServer.h"#include "LocalServerTypes.h"
// Qt headers#include <QDesktopServices>#include <QDir>#include <QTemporaryDir>
using namespace Esri::ArcGISRuntime;
LocalServerServices::LocalServerServices(QQuickItem* parent) : QQuickItem(parent), m_dataPath(QDir::homePath() + "/ArcGIS/Runtime/Data"){ // Create a temporary directory for the local server if one has not already been created if (!LocalServer::appDataPath().isEmpty() && !LocalServer::tempDataPath().isEmpty()) return;
// create temp/data path const QString tempPath = LocalServerServices::shortestTempPath() + "/EsriQtSample";
// create the directory m_tempDir = std::make_unique<QTemporaryDir>(tempPath);
// set the temp & app data path for the local server LocalServer::instance()->setTempDataPath(m_tempDir->path()); LocalServer::instance()->setAppDataPath(m_tempDir->path());}
LocalServerServices::~LocalServerServices() = default;
void LocalServerServices::init(){ qmlRegisterType<LocalServerServices>("Esri.Samples", 1, 0, "LocalServerServicesSample");}
void LocalServerServices::componentComplete(){ QQuickItem::componentComplete();
QString dataPath = QDir::homePath() + "/ArcGIS/Runtime/Data"; // create a map service m_localMapService = new LocalMapService(dataPath + "/mpkx/PointsofInterest.mpkx", this); // create a feature service m_localFeatureService = new LocalFeatureService(dataPath + "/mpkx/PointsofInterest.mpkx", this); // create a gp service m_localGPService = new LocalGeoprocessingService(dataPath + "/gpkx/Contour.gpkx", this);
if (LocalServer::instance()->isInstallValid()) { if (LocalServer::status() == LocalServerStatus::Started) LocalServer::stop();
connectSignals(); }}
void LocalServerServices::connectSignals(){ // local server status connect(LocalServer::instance(), &LocalServer::statusChanged, this, [this]() { // append to the status string switch (LocalServer::status()) { case LocalServerStatus::Starting: { m_serverStatus.append("Server Status: STARTING\n"); break; } case LocalServerStatus::Started: { m_serverStatus.append("Server Status: STARTED\n"); m_isServerRunning = true; emit isServerRunningChanged(); break; } case LocalServerStatus::Stopping: { m_serverStatus.append("Server Status: STOPPING\n"); break; } case LocalServerStatus::Stopped: { m_serverStatus.append("Server Status: STOPPED\n"); m_isServerRunning = false; emit isServerRunningChanged(); break; } case LocalServerStatus::Failed: { m_serverStatus.append("Server Status: FAILED\n"); break; } default: break; } emit serverStatusChanged(); });
connect(m_localMapService, &LocalMapService::statusChanged, this, [this]() { updateStatus(m_localMapService, "Map"); });
connect(m_localFeatureService, &LocalFeatureService::statusChanged, this, [this]() { updateStatus(m_localFeatureService, "Feature"); });
connect(m_localGPService, &LocalGeoprocessingService::statusChanged, this, [this]() { updateStatus(m_localGPService, "Geoprocessing"); });}
// start the servervoid LocalServerServices::startLocalServer(){ if (m_isServerRunning) return;
// clear all the status messages m_serverStatus.clear(); // start local server LocalServer::start();}
// stop the servervoid LocalServerServices::stopLocalServer(){ LocalServer::stop();}
// start a servicevoid LocalServerServices::startService(const QString& serviceName, const QUrl& filePath){ QString path; if (!filePath.isEmpty()) path = QUrl(filePath).toLocalFile();
if (serviceName == "Map Service") { if (path.isEmpty()) { if (m_localMapService->status() == LocalServerStatus::Started) return;
m_localMapService->start(); } else { LocalMapService* mapService = new LocalMapService(path, this); connect(mapService, &LocalMapService::statusChanged, this, [this, mapService]() { updateStatus(mapService, "Map"); }); mapService->start(); } } else if (serviceName == "Feature Service") { if (path.isEmpty()) { if (m_localFeatureService->status() == LocalServerStatus::Started) return;
m_localFeatureService->start(); } else { LocalFeatureService* featureService = new LocalFeatureService(path, this); connect(featureService, &LocalFeatureService::statusChanged, this, [this, featureService]() { updateStatus(featureService, "Feature"); }); featureService->start(); } } else if (serviceName == "Geoprocessing Service") { if (path.isEmpty()) { if (m_localGPService->status() == LocalServerStatus::Started) return;
m_localGPService->start(); } else { LocalGeoprocessingService* gpService = new LocalGeoprocessingService(path, this); connect(gpService, &LocalGeoprocessingService::statusChanged, this, [this, gpService]() { updateStatus(gpService, "Geoprocessing"); }); gpService->start(); } }}
// stop any servicevoid LocalServerServices::stopService(const QUrl& serviceUrl){ if (serviceUrl.isEmpty()) return;
LocalService* serviceToStop = m_servicesHash[serviceUrl]; serviceToStop->stop();}
// open the link in a browservoid LocalServerServices::openURL(const QString& serviceURL){ QDesktopServices::openUrl(QUrl(serviceURL));}
// check if any one of the services is runningbool LocalServerServices::isAnyServiceRunning(){ return LocalServer::services().size() > 0;}
// get the current list of services from local servervoid LocalServerServices::getCurrentServices(){ // get the service names by looping through the services m_services.clear(); for (const LocalService* service : LocalServer::services()) { m_services << service->url().toString(); } emit servicesChanged();}
// get the current status of any servicevoid LocalServerServices::updateStatus(LocalService* service, const QString& serviceName){ switch (service->status()) { case LocalServerStatus::Starting: { m_serverStatus.append(serviceName + " Service Status: STARTING\n"); break; } case LocalServerStatus::Started: { m_serverStatus.append(serviceName + " Service Status: STARTED\n"); m_isServiceRunning = true; emit isServiceRunningChanged();
getCurrentServices(); m_servicesHash.insert(service->url(), service); break; } case LocalServerStatus::Stopping: { m_serverStatus.append(serviceName + " Service Status: STOPPING\n"); break; } case LocalServerStatus::Stopped: { m_serverStatus.append(serviceName + " Service Status: STOPPED\n"); if (!isAnyServiceRunning()) { m_isServiceRunning = false; emit isServiceRunningChanged(); }
getCurrentServices(); m_servicesHash.remove(service->url()); break; } default: break; } emit serverStatusChanged();}
QString LocalServerServices::shortestTempPath(){ // get tmp and home paths const QString tmpPath = QDir::tempPath(); const QString homePath = QDir::homePath();
// return whichever is shorter, temp or home path if (homePath.length() > tmpPath.length()) return tmpPath; else return homePath;}// [WriteFile Name=LocalServerServices, Category=LocalServer]// [Legal]// Copyright 2017 Esri.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// [Legal]
#ifndef LOCAL_SERVER_SERVICES_H#define LOCAL_SERVER_SERVICES_H
// Qt headers#include <QQuickItem>
// STL headers#include <memory>
namespace Esri::ArcGISRuntime{ class LocalServer; class LocalMapService; class LocalFeatureService; class LocalGeoprocessingService; class LocalService;}
class QTemporaryDir;
class LocalServerServices : public QQuickItem{ Q_OBJECT
Q_PROPERTY(QStringList servicesList MEMBER m_services NOTIFY servicesChanged) Q_PROPERTY(QString serverStatus MEMBER m_serverStatus NOTIFY serverStatusChanged) Q_PROPERTY(bool isServerRunning MEMBER m_isServerRunning NOTIFY isServerRunningChanged) Q_PROPERTY(bool isServiceRunning MEMBER m_isServiceRunning NOTIFY isServiceRunningChanged) Q_PROPERTY(QString dataPath MEMBER m_dataPath NOTIFY dataPathChanged)
public: explicit LocalServerServices(QQuickItem* parent = nullptr); ~LocalServerServices() override;
void componentComplete() override; static void init(); Q_INVOKABLE void startLocalServer(); Q_INVOKABLE void stopLocalServer(); Q_INVOKABLE void startService(const QString& serviceName, const QUrl& filePath = QUrl("")); Q_INVOKABLE void stopService(const QUrl& serviceUrl); Q_INVOKABLE void openURL(const QString& serviceURL);
signals: void servicesChanged(); void serverStatusChanged(); void isServerRunningChanged(); void isServiceRunningChanged(); void dataPathChanged();
private: void connectSignals(); bool isAnyServiceRunning(); void updateStatus(Esri::ArcGISRuntime::LocalService* service, const QString& serviceName); void getCurrentServices(); static QString shortestTempPath();
private: Esri::ArcGISRuntime::LocalMapService* m_localMapService = nullptr; Esri::ArcGISRuntime::LocalFeatureService* m_localFeatureService = nullptr; Esri::ArcGISRuntime::LocalGeoprocessingService* m_localGPService = nullptr; QStringList m_services; QString m_serverStatus; QString m_dataPath; bool m_isServerRunning = false; bool m_isServiceRunning = false; QHash<QUrl, Esri::ArcGISRuntime::LocalService*> m_servicesHash; std::unique_ptr<QTemporaryDir> m_tempDir;};
#endif // LOCAL_SERVER_SERVICES_H// [WriteFile Name=LocalServerServices, Category=LocalServer]// [Legal]// Copyright 2017 Esri.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// [Legal]
import QtQuickimport QtQuick.Controlsimport Qt.labs.platformimport Esri.Samples
LocalServerServicesSample { id: localServerServicesSample
Column { spacing: 10 anchors.fill: parent anchors.margins: 15
Row { id: topRow spacing: 10
Button { id: startButton text: "Start Local Server" width: localServerServicesSample.width * 0.475
onClicked: { startLocalServer(); } }
Button { id: stopButton text: "Stop Local Server" width: localServerServicesSample.width * 0.475 enabled: isServerRunning & !isServiceRunning
onClicked: { stopLocalServer(); } } }
Row { spacing: 10
ComboBox { id: servicesCombo property int modelWidth: 0 width: modelWidth + leftPadding + rightPadding + (indicator ? indicator.width : 10)
enabled: isServerRunning model: ["Map Service", "Feature Service", "Geoprocessing Service"]
onCurrentIndexChanged: { filePathText.text = ""; }
Component.onCompleted : { for (let i = 0; i < model.length; ++i) { metrics.text = model[i]; modelWidth = Math.max(modelWidth, metrics.width); } }
TextMetrics { id: metrics font: servicesCombo.font } }
TextField { id: filePathText placeholderText: "Browse for a file." selectByMouse: true width: startServiceButton.width - (40) }
Button { id: fileDialogButton text: "..." enabled: isServerRunning
onClicked: { fileDialog.visible = true; } } }
Row { spacing: 10
Button { id: startServiceButton text: "Start Service" width: localServerServicesSample.width * 0.475 enabled: isServerRunning
onClicked: { startService(servicesCombo.currentText, filePathText.text); } }
Button { id: stopServiceButton text: "Stop Service" width: localServerServicesSample.width * 0.475 enabled: isServiceRunning
onClicked: { if (servicesList.length === 1) stopService(servicesList[0]); else stopService(servicesView.currentValue); } } }
TextArea { id: serverStatusTextArea width: startButton.width + servicesCombo.width + (10) height: 200 text: serverStatus }
Text { text: "List of Running Services" }
ListView { id: servicesView width: startButton.width + servicesCombo.width + (10) height: 200 model: servicesList.length delegate: servicesDelegate property string currentValue: "" onCurrentIndexChanged: currentValue = servicesList[currentIndex] } }
Button { anchors { right: parent.right bottom: parent.bottom margins: 10 } text: "Open Url" visible: servicesView.model > 0
onClicked: { openURL(servicesView.currentValue); } }
Component { id: servicesDelegate
Rectangle { id: rect width: parent.width height: 35 color: ListView.isCurrentItem ? "lightsteelblue" : "white"
Text { text: servicesList[index] anchors { left: parent.left right: parent.right verticalCenter: parent.verticalCenter leftMargin: 5 } elide: Text.ElideLeft font.pixelSize: 14
MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: servicesView.currentIndex = index; } } } }
FileDialog { id: fileDialog title: "Please choose a file" folder: dataPath nameFilters: servicesCombo.currentIndex === 0 || servicesCombo.currentIndex === 1 ? ["Map Packages (*.mpk *.mpkx)", "All files (*)"] : ["Geoprocessing Packages (*gpk *.gpkx)", "All files (*)"] onAccepted: { filePathText.text = file; } }}// [Legal]// Copyright 2017 Esri.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// [Legal]
// sample headers#include "LocalServerServices.h"
// ArcGIS Maps SDK headers#include "ArcGISRuntimeEnvironment.h"
// Qt headers#include <QCommandLineParser>#include <QDir>#include <QGuiApplication>#include <QQmlApplicationEngine>#include <QQmlEngine>#include <QQuickView>#include <QSettings>
// Platform specific headers#ifdef Q_OS_WIN#include <Windows.h>#endif
#define STRINGIZE(x) #x#define QUOTE(x) STRINGIZE(x)
int main(int argc, char *argv[]){ Esri::ArcGISRuntime::ArcGISRuntimeEnvironment::setUseLegacyAuthentication(false); QGuiApplication app(argc, argv); app.setApplicationName(QString("LocalServerServices"));
// Use of ArcGIS location services, such as basemap styles, geocoding, and routing services, // requires an access token. For more information see // https://links.esri.com/arcgis-runtime-security-auth.
// The following methods grant an access token:
// 1. User authentication: Grants a temporary access token associated with a user's ArcGIS account. // To generate a token, a user logs in to the app with an ArcGIS account that is part of an // organization in ArcGIS Online or ArcGIS Enterprise.
// 2. API key authentication: Get a long-lived access token that gives your application access to // ArcGIS location services. Go to the tutorial at https://links.esri.com/create-an-api-key. // Copy the API Key access token.
const QString accessToken = QString("");
if (accessToken.isEmpty()) { qWarning() << "Use of ArcGIS location services, such as the basemap styles service, requires" << "you to authenticate with an ArcGIS account or set the API Key property."; } else { Esri::ArcGISRuntime::ArcGISRuntimeEnvironment::setApiKey(accessToken); }
// Initialize the sample LocalServerServices::init();
// Initialize application view QQmlApplicationEngine engine;
// Add the import Path engine.addImportPath(QDir(QCoreApplication::applicationDirPath()).filePath("qml"));
QString arcGISRuntimeImportPath = QUOTE(ARCGIS_RUNTIME_IMPORT_PATH);
#if defined(LINUX_PLATFORM_REPLACEMENT) // on some linux platforms the string 'linux' is replaced with 1 // fix the replacement paths which were created QString replaceString = QUOTE(LINUX_PLATFORM_REPLACEMENT); arcGISRuntimeImportPath = arcGISRuntimeImportPath.replace(replaceString, "linux", Qt::CaseSensitive); #endif
// Add the Runtime and Extras path engine.addImportPath(arcGISRuntimeImportPath);
// Set the source engine.load(QUrl("qrc:/Samples/LocalServer/LocalServerServices/main.qml"));
return app.exec();}import QtQuick.Controlsimport Esri.Samples
ApplicationWindow { visible: true width: 800 height: 600
LocalServerServices { anchors.fill: parent }}