Display maps and use locators to enable search and routing offline using a Mobile Map Package.
Use case
Mobile map packages make it easy to transmit and store the necessary components for an offline map experience including: transportation networks (for routing/navigation), locators (address search, forward and reverse geocoding), and maps.
A field worker might download a mobile map package to support their operations while working offline.
How to use the sample
A list of maps from a mobile map package will be displayed. If the map contains transportation networks, the list item will have a navigation icon. Click on a map in the list to open it. If a locator task is available, click on the map to place a point. Click it again to reverse geocode the location's address. If transportation networks are available, click the route icon in the top right corner a route will be calculated between geocode locations.
How it works
- Create a
MobileMapPackage
passing in the path to the constructor. - Get a list model of maps inside the package using the
maps
property. - If the package has a locator, access it using the
LocatorTask
property. - To see if a map contains transportation networks, check each map's
TransportationNetworks
property.
Relevant API
- GeocodeResult
- MobileMapPackage
- ReverseGeocodeParameters
- Route
- RouteParameters
- RouteResult
- RouteTask
- TransportationNetworkDataset
Offline data
Read more about how to set up the sample's offline data here.
Link | Local Location |
---|---|
Yellowstone mmpk File | <userhome> /ArcGIS/Runtime/Data/mmpk/Yellowstone.mmpk |
SanFrancisco mmpk File | <userhome> /ArcGIS/Runtime/Data/mmpk/SanFrancisco.mmpk |
Tags
disconnected, field mobility, geocode, network, network analysis, offline, routing, search, transportation
Sample Code
// [WriteFile Name=MobileMap_SearchAndRoute, Category=Maps] // [Legal] // Copyright 2016 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 "MobileMap_SearchAndRoute.h" // ArcGIS Maps SDK headers #include "AttributeListModel.h" #include "CalloutData.h" #include "Error.h" #include "GeocodeResult.h" #include "Graphic.h" #include "GraphicListModel.h" #include "GraphicsOverlay.h" #include "GraphicsOverlayListModel.h" #include "IdentifyGraphicsOverlayResult.h" #include "Item.h" #include "LocatorTask.h" #include "Map.h" #include "MapQuickView.h" #include "MapTypes.h" #include "MapViewTypes.h" #include "MobileMapPackage.h" #include "PictureMarkerSymbol.h" #include "Polyline.h" #include "ReverseGeocodeParameters.h" #include "Route.h" #include "RouteParameters.h" #include "RouteResult.h" #include "RouteTask.h" #include "SimpleLineSymbol.h" #include "SimpleRenderer.h" #include "Stop.h" #include "SymbolTypes.h" #include "TextSymbol.h" // Qt headers #include <QDir> #include <QFile> #include <QFileInfoList> #include <QFuture> #include <QStandardPaths> #include <QUuid> #include <QtCore/qglobal.h> using namespace Esri::ArcGISRuntime; // helper method to get cross platform data path namespace { QString defaultDataPath() { QString dataPath; #ifdef Q_OS_IOS dataPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); #else dataPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); #endif return dataPath; } } // namespace MobileMap_SearchAndRoute::MobileMap_SearchAndRoute(QQuickItem* parent): QQuickItem(parent), m_selectedMmpkIndex(0), m_canRoute(false), m_canClear(false), m_isGeocodeInProgress(false), m_dataPath(defaultDataPath() + "/ArcGIS/Runtime/Data/mmpk"), m_fileInfoList(QDir(m_dataPath).entryInfoList()) { } MobileMap_SearchAndRoute::~MobileMap_SearchAndRoute() = default; void MobileMap_SearchAndRoute::init() { qmlRegisterType<MapQuickView>("Esri.Samples", 1, 0, "MapView"); qmlRegisterType<MobileMap_SearchAndRoute>("Esri.Samples", 1, 0, "MobileMap_SearchAndRouteSample"); qmlRegisterUncreatableType<CalloutData>("Esri.Samples", 1, 0, "CalloutData", "CalloutData is an uncreatable type"); } void MobileMap_SearchAndRoute::componentComplete() { QQuickItem::componentComplete(); // find QML MapView component m_mapView = findChild<MapQuickView*>("mapView"); m_mapView->setWrapAroundMode(WrapAroundMode::Disabled); // initialize Callout m_mapView->calloutData()->setTitle("Address"); // set reverse geocoding parameters m_reverseGeocodeParameters.setMaxResults(1); // identify and create MobileMapPackages using mmpk files in datapath createMobileMapPackages(0); // create graphics overlays to visually display geocoding and routing results m_stopsGraphicsOverlay = new GraphicsOverlay(this); m_routeGraphicsOverlay = new GraphicsOverlay(this); m_routeGraphicsOverlay->setRenderer(new SimpleRenderer(new SimpleLineSymbol(SimpleLineSymbolStyle::Solid, QColor("#2196F3"), 4, this), this)); m_mapView->graphicsOverlays()->append(m_routeGraphicsOverlay); m_mapView->graphicsOverlays()->append(m_stopsGraphicsOverlay); // create a pin symbol m_bluePinSymbol = new PictureMarkerSymbol(QUrl("qrc:/Samples/Maps/MobileMap_SearchAndRoute/bluePinSymbol.png"), this); m_bluePinSymbol->setHeight(36); m_bluePinSymbol->setWidth(36); m_bluePinSymbol->setOffsetY(m_bluePinSymbol->height() / 2); connectSignals(); } void MobileMap_SearchAndRoute::createMobileMapPackages(int index) { if (index < m_fileInfoList.length()) { // check if file is a .mmpk file if (m_fileInfoList[index].completeSuffix() == "mmpk") { // create a new MobileMapPackage MobileMapPackage* mobileMapPackage = new MobileMapPackage(m_fileInfoList[index].absoluteFilePath(), this); // once MMPK is finished loading, add it and its information to lists connect(mobileMapPackage, &MobileMapPackage::doneLoading, this, [mobileMapPackage, this](const Error& error) { if (error.isEmpty()) { // QList of MobileMapPackages m_mobileMapPackages.append(mobileMapPackage); // QStringList of MobileMapPackage names. Used as a ListModel in QML m_mobileMapPackageList << mobileMapPackage->item()->title(); emit mmpkListChanged(); } }); // load the new MMPK mobileMapPackage->load(); } createMobileMapPackages(++index); } else return; } void MobileMap_SearchAndRoute::connectSignals() { connect(m_mapView, &MapQuickView::mouseClicked, this, [this](QMouseEvent& mouseEvent) { if (!m_currentLocatorTask) return; \ m_clickedPoint = Point(m_mapView->screenToLocation(mouseEvent.position().x(), mouseEvent.position().y())); // determine if user clicked on a graphic m_mapView->identifyGraphicsOverlayAsync(m_stopsGraphicsOverlay, mouseEvent.position(), 5, false, 2).then(this, [this](const IdentifyGraphicsOverlayResult* identifyResult) { if (!identifyResult) return; // get graphics list QList<Graphic*> graphics = identifyResult->graphics(); if (!graphics.isEmpty()) { // use the blue pin graphic instead of text graphic to as calloutData's geoElement Graphic* graphic = graphics[0]; if (graphic->symbol()->symbolType() != SymbolType::PictureMarkerSymbol && graphics.count() > 1) graphic = graphics[1]; m_mapView->calloutData()->setGeoElement(graphic); m_mapView->calloutData()->setDetail(graphic->attributes()->attributeValue("AddressLabel").toString()); m_mapView->calloutData()->setVisible(true); } // if clicked a point with no graphic on it, reverse geocode else { m_currentLocatorTask->reverseGeocodeWithParametersAsync(m_clickedPoint, m_reverseGeocodeParameters).then(this, [this](const QList<GeocodeResult>& geocodeResults) { // make busy indicator invisible m_isGeocodeInProgress = false; emit isGeocodeInProgressChanged(); if (geocodeResults.isEmpty()) return; // create parent for the graphic if (!m_stopGraphicParent) m_stopGraphicParent = new QObject(this); // create a blue pin graphic to display location Graphic* bluePinGraphic = new Graphic(geocodeResults[0].displayLocation(), m_bluePinSymbol, m_stopGraphicParent); bluePinGraphic->attributes()->insertAttribute("AddressLabel", geocodeResults[0].label()); m_stopsGraphicsOverlay->graphics()->append(bluePinGraphic); // make clear graphics overlay button visible m_canClear = true; emit canClearChanged(); // if routing is not enabled in map, return if (!m_currentRouteTask) return; //Set up the Stops // create a stop based on added graphic m_stops << Stop(geometry_cast<Point>(bluePinGraphic->geometry())); // create a Text Symbol to display stop number TextSymbol* textSymbol = new TextSymbol(m_stopGraphicParent); textSymbol->setText(QString::number(m_stops.count())); textSymbol->setColor(QColor("white")); textSymbol->setSize(18); textSymbol->setOffsetY(m_bluePinSymbol->height() / 2); // create a Graphic using the textSymbol Graphic* stopNumberGraphic = new Graphic(bluePinGraphic->geometry(), textSymbol, m_stopGraphicParent); stopNumberGraphic->setZIndex(bluePinGraphic->zIndex() + 1); m_stopsGraphicsOverlay->graphics()->append(stopNumberGraphic); if (m_stops.count() > 1) { m_canRoute = true; emit canRouteChanged(); } }); m_isGeocodeInProgress = true; emit isGeocodeInProgressChanged(); } }); }); } void MobileMap_SearchAndRoute::resetMapView() { // dismiss mapView controls m_canClear = false; emit canClearChanged(); m_canRoute = false; emit canRouteChanged(); // dimiss callout m_mapView->calloutData()->setVisible(false); // clear the graphics overlays and stops m_stopsGraphicsOverlay->graphics()->clear(); if (m_stopGraphicParent) { delete m_stopGraphicParent; m_stopGraphicParent = nullptr; } m_routeGraphicsOverlay->graphics()->clear(); if (m_routeGraphicParent) { delete m_routeGraphicParent; m_routeGraphicParent = nullptr; } m_stops.clear(); } void MobileMap_SearchAndRoute::createMapList(int index) { m_mapList.clear(); m_selectedMmpkIndex = index; int counter = 1; const auto maps = m_mobileMapPackages[index]->maps(); for (const Map* map : maps) { QVariantMap mapList; mapList["name"] = map->item()->title() + " " + QString::number(counter); mapList["geocoding"] = m_mobileMapPackages[index]->locatorTask() != nullptr; mapList["routing"] = map->transportationNetworks().count() > 0; m_mapList << mapList; ++counter; } emit mapListChanged(); } void MobileMap_SearchAndRoute::selectMap(int index) { resetMapView(); // set the locatorTask //! [MobileMap_SearchAndRoute create LocatorTask] m_currentLocatorTask = m_mobileMapPackages[m_selectedMmpkIndex]->locatorTask(); //! [MobileMap_SearchAndRoute create LocatorTask] // set the MapView m_mapView->setMap(m_mobileMapPackages[m_selectedMmpkIndex]->maps().at(index)); // create a RouteTask with selected map's transportation network if available if (m_mobileMapPackages[m_selectedMmpkIndex]->maps().at(index)->transportationNetworks().count() > 0) { m_currentRouteTask = new RouteTask(m_mobileMapPackages[m_selectedMmpkIndex]->maps().at(index)->transportationNetworks().at(0), this); m_currentRouteTask->load(); // create default parameters after the RouteTask is loaded connect(m_currentRouteTask, &RouteTask::loadStatusChanged, this, [this](LoadStatus loadStatus) { if (loadStatus == LoadStatus::Loaded) m_currentRouteTask->createDefaultParametersAsync().then(this, [this](const RouteParameters& routeParameters) { m_currentRouteParameters = routeParameters; }); }); } else m_currentRouteTask = nullptr; } void MobileMap_SearchAndRoute::solveRoute() { // clear previously displayed routes m_routeGraphicsOverlay->graphics()->clear(); if (m_routeGraphicParent) { delete m_routeGraphicParent; m_routeGraphicParent = nullptr; } // set stops and solve route m_currentRouteParameters.setStops(m_stops); // create a graphic using the RouteResult m_currentRouteTask->solveRouteAsync(m_currentRouteParameters).then(this, [this](const RouteResult& routeResult) { if (!m_routeGraphicParent) m_routeGraphicParent = new QObject(this); if (!routeResult.isEmpty()) { const auto routes = routeResult.routes(); Graphic* routeGraphic = new Graphic(routes[0].routeGeometry(), m_routeGraphicParent); m_routeGraphicsOverlay->graphics()->append(routeGraphic); } }); } QStringList MobileMap_SearchAndRoute::mmpkList() const { return m_mobileMapPackageList; } QVariantList MobileMap_SearchAndRoute::mapList() const { return m_mapList; } bool MobileMap_SearchAndRoute::isGeocodeInProgress() const { return m_isGeocodeInProgress; } bool MobileMap_SearchAndRoute::canRoute() const { return m_canRoute; } bool MobileMap_SearchAndRoute::canClear() const { return m_canClear; }