Geocode addresses to locations and reverse geocode locations to addresses offline.

Use case
You can use an address locator file to geocode addresses and locations. For example, you could provide offline geocoding capabilities to field workers repairing critical infrastructure in a disaster when network availability is limited.
How to use the sample
Type the address in the Search menu option or select from the list to Geocode the address and view the result on the map. Tap the location you want to reverse geocode. Select the pin to highlight the PictureMarkerSymbol (i.e. single tap on the pin) and then tap-hold and drag on the map to get real-time geocoding.
How it works
- Use the path of a .loc file to create a
LocatorTaskobject. - Set up
GeocodeParametersand callgeocodeto get geocode results.
Relevant API
- GeocodeParameters
- GeocodeResult
- LocatorTask
- ReverseGeocodeParameters
Offline Data
Read more about how to set up the sample’s offline data here.
| Link | Local Location |
|---|---|
| SanDiego tpkx File | <userhome>/ArcGIS/Runtime/Data/tpkx/streetmap_SD.tpkx |
| SanDiego loc Files | <userhome>/ArcGIS/Runtime/Data/Locators/SanDiegoStreetAddressLocator/SanDiego_StreetAddress.loc |
Tags
geocode, geocoder, locator, offline, package, query, search
Sample Code
// [WriteFile Name=OfflineGeocode, Category=Search]// [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 "OfflineGeocode.h"
// ArcGIS Maps SDK headers#include "ArcGISTiledLayer.h"#include "Basemap.h"#include "CalloutData.h"#include "Error.h"#include "ErrorException.h"#include "GeocodeParameters.h"#include "GeocodeResult.h"#include "Graphic.h"#include "GraphicListModel.h"#include "GraphicsOverlay.h"#include "GraphicsOverlayListModel.h"#include "IdentifyGraphicsOverlayResult.h"#include "LayerListModel.h"#include "LocatorTask.h"#include "Map.h"#include "MapQuickView.h"#include "MapViewTypes.h"#include "PictureMarkerSymbol.h"#include "Point.h"#include "SpatialReference.h"#include "SuggestListModel.h"#include "TileCache.h"#include "Viewpoint.h"
// Qt headers#include <QFuture>#include <QScopedPointer>#include <QStandardPaths>#include <QUuid>#include <QtCore/qglobal.h>
// STL headers#include <memory>
using namespace Esri::ArcGISRuntime;
// helper method to get cross platform data pathnamespace{ QString defaultDataPath() { QString dataPath;
#ifdef Q_OS_IOS dataPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); #else dataPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); #endif
return dataPath; }} // namespace
using namespace Esri::ArcGISRuntime;
OfflineGeocode::OfflineGeocode(QQuickItem* parent): QQuickItem(parent), m_dataPath(defaultDataPath() + "/ArcGIS/Runtime/Data/"){}
OfflineGeocode::~OfflineGeocode() = default;
void OfflineGeocode::init(){ qmlRegisterType<MapQuickView>("Esri.Samples", 1, 0, "MapView"); qmlRegisterType<OfflineGeocode>("Esri.Samples", 1, 0, "OfflineGeocodeSample"); qmlRegisterUncreatableType<CalloutData>("Esri.Samples", 1, 0, "CalloutData", "CalloutData is an uncreatable type"); qmlRegisterUncreatableType<SuggestListModel>("Esri.Samples", 1, 0, "SuggestListModel", "SuggestionListModel is an uncreatable type");}
void OfflineGeocode::componentComplete(){ QQuickItem::componentComplete();
// find QML MapView component m_mapView = findChild<MapQuickView*>("mapView"); m_mapView->setWrapAroundMode(WrapAroundMode::Disabled);
// create a tiled layer using a local .tpkx file TileCache* tileCache = new TileCache(m_dataPath + "tpkx/streetmap_SD.tpkx", this); connect(tileCache, &TileCache::errorOccurred, this, &OfflineGeocode::logError);
m_tiledLayer = new ArcGISTiledLayer(tileCache, this);
// create basemap and add tiled layer Basemap* basemap = new Basemap(this); basemap->baseLayers()->append(m_tiledLayer);
// create map using basemap m_map = new Map(basemap, this); m_map->setInitialViewpoint(Viewpoint(Point(-13042254.715252, 3857970.236806, SpatialReference(3857)), 2e4));
// Set map to map view m_mapView->setMap(m_map);
// create locator task //! [OfflineGeocode create LocatorTask] m_locatorTask = new LocatorTask(m_dataPath + "Locators/SanDiegoStreetAddressLocator/SanDiego_StreetAddress.loc", this); //! [OfflineGeocode create LocatorTask]
// set the suggestions Q_PROPERTY m_suggestListModel = m_locatorTask->suggestions(); emit suggestionsChanged();
// set geocode parameters m_geocodeParameters.setMinScore(75); m_geocodeParameters.setMaxResults(1); m_reverseGeocodeParameters.setMaxResults(1);
// create graphics overlay and add pin graphic m_graphicsOverlay = new GraphicsOverlay(this); PictureMarkerSymbol* pinSymbol = new PictureMarkerSymbol(QUrl("qrc:/Samples/Search/OfflineGeocode/red_pin.png"), this); pinSymbol->setHeight(36); pinSymbol->setWidth(19); pinSymbol->setOffsetY(pinSymbol->height() / 2); m_pinGraphic = new Graphic(Point(-13042254.715252, 3857970.236806, SpatialReference(3857)), pinSymbol, this); m_pinGraphic->setVisible(false); m_graphicsOverlay->graphics()->append(m_pinGraphic); m_mapView->graphicsOverlays()->append(m_graphicsOverlay);
// initialize callout m_mapView->calloutData()->setVisible(false); m_mapView->calloutData()->setTitle("Address"); m_calloutData = m_mapView->calloutData();
connectSignals();}
void OfflineGeocode::geocodeWithText(const QString& address){ m_locatorTask->geocodeWithParametersAsync(address, m_geocodeParameters).then(this, [this](const QList<GeocodeResult>& geocodeResults) { onGeocodingCompleted_(geocodeResults); }).onFailed(this, [this](const ErrorException& e) { logException(e); });}
void OfflineGeocode::geocodeWithSuggestion(int index){ m_locatorTask->geocodeWithSuggestResultAndParametersAsync(m_suggestListModel->suggestResults().at(index), m_geocodeParameters) .then(this, [this](const QList<GeocodeResult>& geocodeResults) { onGeocodingCompleted_(geocodeResults); }).onFailed(this, [this](const ErrorException& e) { logException(e); });}
void OfflineGeocode::setSuggestionsText(const QString& searchText){ m_suggestListModel->setSearchText(searchText);}
void OfflineGeocode::logError(const Error& error){ setErrorMessage( QString("%1: %2").arg(error.message(), error.additionalMessage()));}
void OfflineGeocode::logException(const ErrorException& exception){ logError(exception.error());}
SuggestListModel* OfflineGeocode::suggestions() const{ return m_suggestListModel;}
bool OfflineGeocode::geocodeInProgress() const{ return m_geocodeInProgress;}
bool OfflineGeocode::noResults() const{ return m_noResults;}
bool OfflineGeocode::suggestInProgress() const{ return m_suggestInProgress;}
void OfflineGeocode::connectSignals(){ connect(m_map, &Map::errorOccurred, this, &OfflineGeocode::logError); connect(m_mapView, &MapQuickView::mouseClicked, this, [this](QMouseEvent& mouseEvent) { m_clickedPoint = m_mapView->screenToLocation(mouseEvent.position().x(), mouseEvent.position().y()); m_mapView->identifyGraphicsOverlayAsync(m_graphicsOverlay, mouseEvent.position(), 5, false, 1) .then(this, [this](IdentifyGraphicsOverlayResult* rawIdentifyResult) { // Delete rawIdentifyResult when we leave scope. auto identifyResult = std::unique_ptr<IdentifyGraphicsOverlayResult>(rawIdentifyResult);
if (!identifyResult) return;
// if user clicked on pin, display callout const QList<Graphic*> graphics = identifyResult->graphics(); if (graphics.count() > 0) m_calloutData->setVisible(true);
// otherwise, reverse geocode at that point else { m_isReverseGeocode = true; m_locatorTask->reverseGeocodeWithParametersAsync(m_clickedPoint, m_reverseGeocodeParameters) .then(this, [this](const QList<GeocodeResult>& geocodeResults) { onGeocodingCompleted_(geocodeResults); }).onFailed(this, [this](const ErrorException& e) { logException(e); });
m_geocodeInProgress = true; emit geocodeInProgressChanged(); } }).onFailed(this, [this](const ErrorException& e) { logException(e); }); });
connect(m_mapView, &MapQuickView::mousePressedAndHeld, this, [this](QMouseEvent& mouseEvent) { m_isPressAndHold = true; m_isReverseGeocode = true;
// reverse geocode m_locatorTask->reverseGeocodeWithParametersAsync(Point(m_mapView->screenToLocation(mouseEvent.position().x(), mouseEvent.position().y())), m_reverseGeocodeParameters) .then(this, [this](const QList<GeocodeResult>& geocodeResults) { onGeocodingCompleted_(geocodeResults); }).onFailed(this, [this](const ErrorException& e) { logException(e); });
// make busy indicator visible m_geocodeInProgress = true; emit geocodeInProgressChanged(); });
connect(m_mapView, &MapQuickView::mouseMoved, this, [this](QMouseEvent& mouseEvent) { // if user is dragging mouse hold, realtime reverse geocode if (m_isPressAndHold) { m_locatorTask->reverseGeocodeWithParametersAsync(Point(m_mapView->screenToLocation(mouseEvent.position().x(), mouseEvent.position().y())), m_reverseGeocodeParameters) .then(this, [this](const QList<GeocodeResult>& geocodeResults) { onGeocodingCompleted_(geocodeResults); }).onFailed(this, [this](const ErrorException& e) { logException(e); });
m_geocodeInProgress = true; emit geocodeInProgressChanged(); } });
// reset after user stops holding down mouse connect(m_mapView, &MapQuickView::mouseReleased, this, [this]() { m_isPressAndHold = false; m_isReverseGeocode = false; });
// dismiss no results notification and suggestions on mouse press connect(m_mapView, &MapQuickView::mousePressed, this, [this]() { m_noResults = false; emit noResultsChanged(); emit dismissSuggestions(); });
connect(m_suggestListModel, &SuggestListModel::errorOccurred, this, &OfflineGeocode::logError); connect(m_suggestListModel, &SuggestListModel::suggestInProgressChanged, this, [this]() { m_suggestInProgress = m_suggestListModel->suggestInProgress(); emit suggestInProgressChanged(); });}
void OfflineGeocode::onGeocodingCompleted_(const QList<GeocodeResult>& geocodeResults){ // dismiss busy indicator m_geocodeInProgress = false; emit geocodeInProgressChanged();
// dismiss callouts m_calloutData->setVisible(false); m_pinGraphic->setVisible(false);
// if there are no matching results, notify user and stop processing if (geocodeResults.isEmpty()) { m_noResults = true; emit noResultsChanged(); return; }
// dismiss no results notification m_noResults = false; emit noResultsChanged();
// zoom to result's extent m_mapView->setViewpointCenterAsync(geocodeResults.at(0).displayLocation());
// set pin graphic's location m_pinGraphic->setGeometry(geocodeResults.at(0).displayLocation()); m_pinGraphic->setVisible(true);
// set callout location and detail m_calloutData->setDetail(geocodeResults.at(0).label()); m_calloutData->setGeoElement(m_pinGraphic);
if (m_isReverseGeocode) m_calloutData->setVisible(true);
// continue reverse geocoding if user is pressing and holding if (!m_isPressAndHold) m_isReverseGeocode = false;}
QString OfflineGeocode::errorMessage() const{ return m_errorMsg;}
void OfflineGeocode::setErrorMessage(const QString& msg){ m_errorMsg = msg; qDebug() << m_errorMsg; emit errorMessageChanged();}// [WriteFile Name=OfflineGeocode, Category=Search]// [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]
#ifndef OFFLINEGEOCODE_H#define OFFLINEGEOCODE_H
// ArcGIS Maps SDK headers#include "Error.h"#include "GeocodeParameters.h"#include "Point.h"#include "ReverseGeocodeParameters.h"#include "SuggestResult.h"
// Qt headers#include <QQuickItem>
namespace Esri::ArcGISRuntime{ class ErrorException; class Map; class GeocodeResult; class Graphic; class LocatorTask; class CalloutData; class MapQuickView; class GraphicsOverlay; class ArcGISTiledLayer; class SuggestListModel;}
Q_MOC_INCLUDE("SuggestListModel.h")
class OfflineGeocode : public QQuickItem{ Q_OBJECT
Q_PROPERTY(Esri::ArcGISRuntime::SuggestListModel* suggestions READ suggestions NOTIFY suggestionsChanged) Q_PROPERTY(bool geocodeInProgress READ geocodeInProgress NOTIFY geocodeInProgressChanged) Q_PROPERTY(bool suggestInProgress READ suggestInProgress NOTIFY suggestInProgressChanged) Q_PROPERTY(bool noResults READ noResults NOTIFY noResultsChanged) Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged)
public: explicit OfflineGeocode(QQuickItem* parent = nullptr); ~OfflineGeocode() override;
void componentComplete() override; static void init(); Q_INVOKABLE void geocodeWithSuggestion(int index); Q_INVOKABLE void geocodeWithText(const QString& address); Q_INVOKABLE void setSuggestionsText(const QString& searchText);
signals: void noResultsChanged(); void suggestionsChanged(); void suggestInProgressChanged(); void geocodeInProgressChanged(); void dismissSuggestions(); void errorMessageChanged();
private slots: void logError(const Esri::ArcGISRuntime::Error& error);
private: Esri::ArcGISRuntime::SuggestListModel* suggestions() const; bool geocodeInProgress() const; bool noResults() const; bool suggestInProgress() const; void connectSignals(); QString errorMessage() const; void setErrorMessage(const QString& msg); void onGeocodingCompleted_(const QList<Esri::ArcGISRuntime::GeocodeResult>& results); void logException(const Esri::ArcGISRuntime::ErrorException& exception);
bool m_isReverseGeocode = false; bool m_geocodeInProgress = false; bool m_isPressAndHold = false; bool m_noResults = false; bool m_suggestInProgress = false; QString m_dataPath; QString m_errorMsg; Esri::ArcGISRuntime::Map* m_map = nullptr; Esri::ArcGISRuntime::Point m_clickedPoint; Esri::ArcGISRuntime::Graphic* m_pinGraphic = nullptr; Esri::ArcGISRuntime::MapQuickView* m_mapView = nullptr; Esri::ArcGISRuntime::CalloutData* m_calloutData = nullptr; Esri::ArcGISRuntime::LocatorTask* m_locatorTask = nullptr; Esri::ArcGISRuntime::ArcGISTiledLayer* m_tiledLayer = nullptr; Esri::ArcGISRuntime::GraphicsOverlay* m_graphicsOverlay = nullptr; Esri::ArcGISRuntime::SuggestListModel* m_suggestListModel = nullptr; Esri::ArcGISRuntime::GeocodeParameters m_geocodeParameters; Esri::ArcGISRuntime::ReverseGeocodeParameters m_reverseGeocodeParameters;};
#endif // OFFLINEGEOCODE_H// [WriteFile Name=OfflineGeocode, Category=Search]// [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]
import QtQuickimport Esri.Samplesimport QtQuick.Controlsimport QtQuick.Layoutsimport Esri.ArcGISRuntime.Toolkit
OfflineGeocodeSample { id: offlineGeocodeSample clip: true width: 800 height: 600
// add a mapView component MapView { id: mapView anchors.fill: parent objectName: "mapView"
Component.onCompleted: { // Set the focus on MapView to initially enable keyboard navigation forceActiveFocus(); }
Callout { id: callout leaderPosition: Callout.LeaderPosition.Automatic calloutData: mapView.calloutData screenOffsetY: -19 accessoryButtonVisible: false } }
// address bar and suggestions list Column { anchors { fill: parent margins: 10 }
Rectangle { id: addressSearchRect anchors { left: parent.left right: parent.right } height: childrenRect.height
color: "#f7f8fa" border { color: "#7B7C7D" width: 1 } radius: 2
RowLayout { width: parent.width height: childrenRect.height
// search bar for geocoding TextField { id: textField Layout.fillWidth: true leftPadding: 5 selectByMouse: true
placeholderText: "Enter an Address"
// set the SuggestListModel searchText whenever text is changed onTextChanged: { offlineGeocodeSample.setSuggestionsText(text); }
// when enter or return is pressed, geocode with the inputted text onAccepted: { suggestionRect.visible = false; offlineGeocodeSample.geocodeWithText(text); }
// initial text Component.onCompleted: { text = "910 N Harbor Dr, San Diego, CA 92101"; suggestionRect.visible = false; } }
// button to close and open suggestions Rectangle { Layout.margins: 5 width: height height: textField.contentHeight color: "#f7f8fa" radius: 2
Image { anchors.fill: parent source: suggestionRect.visible ? "qrc:/Samples/Search/OfflineGeocode/ic_menu_closeclear_light_d.png" : "qrc:/Samples/Search/OfflineGeocode/ic_menu_collapsedencircled_light_d.png"
MouseArea { anchors.fill: parent onClicked: { suggestionRect.visible = !suggestionRect.visible; } } } } } }
Rectangle { id: suggestionRect width: addressSearchRect.width height: 20 * suggestView.count color: "#f7f8fa" opacity: 0.85 visible: false
ListView { id: suggestView model: offlineGeocodeSample.suggestions height: parent.height delegate: Component {
Rectangle { width: addressSearchRect.width height: 20 color: "#f7f8fa" border.color: "darkgray"
Text { anchors { verticalCenter: parent.verticalCenter margins: 10 }
font { weight: Font.Black pixelSize: 12 }
text: label elide: Text.ElideRight leftPadding: 5 color: "black" }
// when user clicks suggestion, geocode with the selected address MouseArea { anchors.fill: parent onClicked: { offlineGeocodeSample.geocodeWithSuggestion(index); textField.text = label; suggestionRect.visible = false; } } } } } } }
BusyIndicator { id: busyIndicator anchors.centerIn: parent visible: offlineGeocodeSample.geocodeInProgress }
Rectangle { id: noResultsRect anchors.centerIn: parent height: 50 width: 200 color: "#f7f8fa" visible: offlineGeocodeSample.noResults radius: 2 opacity: 0.85 border.color: "black"
Text { anchors.centerIn: parent text: "No matching address" font.pixelSize: 18 } }
// signal handler to dismiss suggestions when user clicks away onDismissSuggestions: { suggestionRect.visible = false; }
// if suggestions changed, make them visible onSuggestInProgressChanged: { if(offlineGeocodeSample.suggestInProgress) suggestionRect.visible = true; }
Dialog { modal: true x: Math.round(parent.width - width) / 2 y: Math.round(parent.height - height) / 2 standardButtons: Dialog.Ok visible: text.length > 0 title: "Error" property alias text : textLabel.text property alias informativeText : detailsLabel.text ColumnLayout { Text { id: textLabel text: errorMessage } Text { id: detailsLabel text: "please consult README.md" } } }}// [Legal]// Copyright 2015 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 "OfflineGeocode.h"
// ArcGIS Maps SDK headers#include "ArcGISRuntimeEnvironment.h"
// Qt headers#include <QCommandLineParser>#include <QDir>#include <QGuiApplication>#include <QQmlEngine>#include <QQuickView>
// Other headers#include "Esri/ArcGISRuntime/Toolkit/register.h"
// 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("OfflineGeocode"));
// 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 OfflineGeocode::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);
Esri::ArcGISRuntime::Toolkit::registerComponents(*(view.engine()));
// Set the source view.setSource(QUrl("qrc:/Samples/Search/OfflineGeocode/OfflineGeocode.qml"));
view.show();
return app.exec();}