Find a route to the closest facility from a location.

Use case
Quickly and accurately determining the most efficient route between a location and a facility is a frequently encountered task. For example, a paramedic may need to know which hospital in the vicinity offers the possibility of getting an ambulance patient critical medical care in the shortest amount of time. Solving for the closest hospital to the ambulance’s location using an impedance of “travel time” would provide this information.
How to use the sample
Click near any of the hospitals and a route will be displayed from that clicked location to the nearest hospital.
How it works
- Create a
ClosestFacilityTaskusing an Url from an online service. - Get a
ClosestFacilityParametersfrom this task, usingcreateDefaultParametersAsync. - Add a list of facilities to the task parameters, using
ClosestFacilityParameters::setFacilities(const QList<Facility>& facilities);. - Add an incident to the parameters, using
ClosestFacilityParameters::setIncidents(const QList<Incident>& incidents);. - Get the
ClosestFacilityResultfrom solving the task with parameters: ,ClosestFacilityTask::solveClosestFacilityAsync(const ClosestFacilityParameters& closestFacilityParameters);. - Get the ranked list of the indices of the closest facilities to the incident,
ClosestFacilityResult::rankedFacilities(int incidentIndex) const;. - Get the index of the closest facility (e.g. the first index in the ranked list).
- Find the closest facility route,
ClosestFacilityResult::route(int facilityIndex, int incidentIndex) const;. - Display the route on the
MapView:
- create a
Graphicfrom the route geometry,new Graphic(route.routeGeometry(), this). - add graphic to
GraphicsOverlaywhich is attached to the mapview.
Relevant API
- ClosestFacilityParameters
- ClosestFacilityResult
- ClosestFacilityRoute
- ClosestFacilityTask
- Facility
- Graphic
- GraphicsOverlay
- Incident
- MapView
Tags
incident, network analysis, route, search
Sample Code
// [WriteFile Name=FindClosestFacilityToAnIncidentInteractive, Category=Routing]// [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 "ClosestFacility.h"
// ArcGIS Maps SDK headers#include "ClosestFacilityParameters.h"#include "ClosestFacilityResult.h"#include "ClosestFacilityRoute.h"#include "ClosestFacilityTask.h"#include "Error.h"#include "Facility.h"#include "Graphic.h"#include "GraphicListModel.h"#include "GraphicsOverlay.h"#include "GraphicsOverlayListModel.h"#include "Incident.h"#include "Map.h"#include "MapQuickView.h"#include "MapTypes.h"#include "MapViewTypes.h"#include "PictureMarkerSymbol.h"#include "Point.h"#include "Polyline.h"#include "SimpleLineSymbol.h"#include "SimpleMarkerSymbol.h"#include "SimpleRenderer.h"#include "SpatialReference.h"#include "SymbolTypes.h"#include "Viewpoint.h"
// Qt headers#include <QFuture>#include <QUuid>
using namespace Esri::ArcGISRuntime;
const QUrl ClosestFacility::facilityImageUrl( QStringLiteral("https://static.arcgis.com/images/Symbols/SafetyHealth/Hospital.png"));
const QUrl ClosestFacility::sanDiegoRegion( QStringLiteral("https://sampleserver6.arcgisonline.com/arcgis/rest/services/NetworkAnalysis/SanDiego/NAServer/ClosestFacility"));
ClosestFacility::ClosestFacility(QQuickItem* parent /* = nullptr */): QQuickItem(parent), m_task(new ClosestFacilityTask(sanDiegoRegion, this)){}
ClosestFacility::~ClosestFacility() = default;
void ClosestFacility::init(){ qmlRegisterType<MapQuickView>("Esri.Samples", 1, 0, "MapView"); qmlRegisterType<ClosestFacility>("Esri.Samples", 1, 0, "ClosestFacilitySample");}
void ClosestFacility::componentComplete(){ QQuickItem::componentComplete();
// find QML MapView component m_mapView = findChild<MapQuickView*>("mapView"); m_mapView->setWrapAroundMode(WrapAroundMode::Disabled);
// Create a map using the streets basemap m_map = new Map(BasemapStyle::ArcGISStreets, this);
// set view to be over San Diego m_map->setInitialViewpoint(Viewpoint(Point(-13041154, 3858170, SpatialReference(3857)), 1e5));
// Set map to map view m_mapView->setMap(m_map);
createFacilities(); createGraphics();
connect(m_task, &ClosestFacilityTask::doneLoading, this, [this] (const Error& loadError) { if (!loadError.isEmpty()) return;
if (m_task->loadStatus() != LoadStatus::Loaded) return;
setupRouting(); });
m_task->load();}
bool ClosestFacility::busy() const{ return m_busy;}
QString ClosestFacility::message() const{ return m_message;}
void ClosestFacility::setBusy(bool val){ if (m_busy == val) return;
m_message.clear(); m_busy = val; emit busyChanged(); emit messageChanged();}
void ClosestFacility::createFacilities(){ // List of facilities to be placed around San Diego area m_facilities.append(Facility(Point(-1.3042129900625112E7, 3860127.9479775648, SpatialReference::webMercator()))); m_facilities.append(Facility(Point(-1.3042193400557665E7, 3862448.873041752, SpatialReference::webMercator()))); m_facilities.append(Facility(Point(-1.3046882875518233E7, 3862704.9896770366, SpatialReference::webMercator()))); m_facilities.append(Facility(Point(-1.3040539754780494E7, 3862924.5938606677, SpatialReference::webMercator()))); m_facilities.append(Facility(Point(-1.3042571225655518E7, 3858981.773018156, SpatialReference::webMercator()))); m_facilities.append(Facility(Point(-1.3039784633928463E7, 3856692.5980474586, SpatialReference::webMercator()))); m_facilities.append(Facility(Point(-1.3049023883956768E7, 3861993.789732541, SpatialReference::webMercator())));}
void ClosestFacility::createGraphics(){ // create a symbol for the graphics PictureMarkerSymbol* facilitySymbol = new PictureMarkerSymbol(facilityImageUrl, this); facilitySymbol->setHeight(30); facilitySymbol->setWidth(30);
// create a graphics overlay for the facilities and add a renderer for the symbol m_facilityOverlay = new GraphicsOverlay(this); SimpleRenderer* facilityRenderer = new SimpleRenderer(facilitySymbol, this); m_facilityOverlay->setRenderer(facilityRenderer);
// add a graphic at the location of each of the facilities for (const Facility& facility : std::as_const(m_facilities)) { Graphic* facilityGraphic = new Graphic(facility.geometry(), this); m_facilityOverlay->graphics()->append(facilityGraphic); }
// add the overlay to the map view m_mapView->graphicsOverlays()->append(m_facilityOverlay);
// create a symbol for the route graphic float lineWidth = 2.0f; SimpleLineSymbol* routeSymbol = new SimpleLineSymbol(SimpleLineSymbolStyle::Solid, Qt::blue, lineWidth, this);
// create a graphics overlay for the routes and add a renderer for the symbol m_resultsOverlay = new GraphicsOverlay(this); SimpleRenderer* routeRenderer = new SimpleRenderer(routeSymbol, this); m_resultsOverlay->setRenderer(routeRenderer);
// add the overlay to the map view m_mapView->graphicsOverlays()->append(m_resultsOverlay);
// add a graphic to the route overlay m_routeGraphic = new Graphic(this); m_resultsOverlay->graphics()->append(m_routeGraphic);
// create a symbol for the incident graphics SimpleMarkerSymbol* incidentSymbol = new SimpleMarkerSymbol(SimpleMarkerSymbolStyle::Cross, Qt::black, 20, this);
// create a graphics overlay for the incidents and add a renderer for the symbol m_incidentsOverlay = new GraphicsOverlay(this); SimpleRenderer* incidentRenderer = new SimpleRenderer(incidentSymbol, this); m_incidentsOverlay->setRenderer(incidentRenderer);
// add the overlay to the map view m_mapView->graphicsOverlays()->append(m_incidentsOverlay);
// add a graphic to the incident overlay m_incidentGraphic = new Graphic(this); m_incidentsOverlay->graphics()->append(m_incidentGraphic);}
void ClosestFacility::setupRouting(){ connect(m_mapView, &MapQuickView::mouseClicked, this, [this](QMouseEvent& mouseEvent) { if (busy()) return;
Point mapPoint = m_mapView->screenToLocation(mouseEvent.position().x(), mouseEvent.position().y()); Point incidentPoint(mapPoint.x(), mapPoint.y(), SpatialReference::webMercator());
m_incidentGraphic->setGeometry(incidentPoint);
solveRoute(incidentPoint); });
setBusy(true); m_task->createDefaultParametersAsync().then(this, [this](const ClosestFacilityParameters& defaultParameters) { setBusy(false);
m_facilityParams = defaultParameters; m_facilityParams.setFacilities(m_facilities); });
}
void ClosestFacility::solveRoute(const Point& incidentPoint){ m_facilityParams.clearIncidents(); m_facilityParams.setIncidents(QList<Incident> {Incident(incidentPoint)});
setBusy(true); // find the closest facility to the incident m_task->solveClosestFacilityAsync(m_facilityParams).then(this, [this](const ClosestFacilityResult& closestFacilityResult) { setBusy(false);
if (closestFacilityResult.isEmpty()) { m_message = "Incident not within San Diego Area!"; emit messageChanged(); }
QList<int> rankedList = closestFacilityResult.rankedFacilityIndexes(0); int closestFacilityIdx = rankedList.first();
int incidentIndex = 0; // 0 since there is just 1 incident at a time ClosestFacilityRoute route = closestFacilityResult.route(closestFacilityIdx, incidentIndex);
m_routeGraphic->setGeometry(route.routeGeometry()); });}// [WriteFile Name=FindClosestFacilityToAnIncidentInteractive, Category=Routing]// [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 CLOSESTFACILITY_H#define CLOSESTFACILITY_H
// ArcGIS Maps SDK headers#include "ClosestFacilityParameters.h"
// Qt headers#include <QList>#include <QQuickItem>#include <QUrl>
namespace Esri::ArcGISRuntime{ class ClosestFacilityTask; class Facility; class GraphicsOverlay; class Graphic; class Map; class MapQuickView; class Point;}
class ClosestFacility : public QQuickItem{ Q_OBJECT
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) Q_PROPERTY(QString message READ message NOTIFY messageChanged)
public: explicit ClosestFacility(QQuickItem* parent = nullptr); ~ClosestFacility() override;
void componentComplete() override; static void init();
signals: void busyChanged(); void messageChanged();
private: bool busy() const; QString message() const;
void setBusy(bool val); void createFacilities(); void createGraphics(); void setupRouting(); void solveRoute(const Esri::ArcGISRuntime::Point& incidentPoint);
Esri::ArcGISRuntime::Map* m_map = nullptr; Esri::ArcGISRuntime::MapQuickView* m_mapView = nullptr; QList<Esri::ArcGISRuntime::Facility> m_facilities; Esri::ArcGISRuntime::ClosestFacilityTask* m_task = nullptr; Esri::ArcGISRuntime::ClosestFacilityParameters m_facilityParams; Esri::ArcGISRuntime::GraphicsOverlay* m_facilityOverlay = nullptr; Esri::ArcGISRuntime::GraphicsOverlay* m_incidentsOverlay = nullptr; Esri::ArcGISRuntime::GraphicsOverlay* m_resultsOverlay = nullptr; Esri::ArcGISRuntime::Graphic* m_routeGraphic = nullptr; Esri::ArcGISRuntime::Graphic* m_incidentGraphic = nullptr; bool m_busy = false; QString m_message;
static const QUrl facilityImageUrl; static const QUrl sanDiegoRegion;};
#endif // CLOSESTFACILITY_H// [WriteFile Name=FindClosestFacilityToAnIncidentInteractive, Category=Routing]// [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 Esri.Samples
ClosestFacilitySample { id: rootRectangle clip: true width: 800 height: 600
// add a mapView component MapView { anchors.fill: parent objectName: "mapView"
Component.onCompleted: { // Set the focus on MapView to initially enable keyboard navigation forceActiveFocus(); } }
BusyIndicator { anchors.centerIn: parent running: busy }
Dialog { modal: true x: Math.round(parent.width - width) / 2 y: Math.round(parent.height - height) / 2 standardButtons: Dialog.Ok title: "Route Error" visible: text.length > 0 property alias text : textLabel.text Text { id: textLabel text: message } }}// [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 "ClosestFacility.h"
// ArcGIS Maps SDK headers#include "ArcGISRuntimeEnvironment.h"
// Qt headers#include <QCommandLineParser>#include <QDir>#include <QGuiApplication>#include <QQmlEngine>#include <QQuickView>
// 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("ClosestFacility"));
// 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 ClosestFacility::init();
// Initialize application view QQuickView view; view.setResizeMode(QQuickView::SizeRootObjectToView);
// Add the import Path view.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 view.engine()->addImportPath(arcGISRuntimeImportPath);
// Set the source view.setSource(QUrl("qrc:/Samples/Routing/ClosestFacility/ClosestFacility.qml"));
view.show();
return app.exec();}