Skip To Content ArcGIS for Developers Sign In Dashboard

Overview

You will learn: how to find an address or place using the ArcGIS World Geocoding Service.

Applications can find addresses and places of interest around the world using the LocatorTask class and a QML search component. The class uses the ArcGIS World Geocoding Service to search for places, business names, and addresses. To make an interactive application with a search user interface, you can use the auto-search method to provide auto-suggested search results to users. In addition to geocoding, the ArcGIS World Geocoding Service also supports reverse geocoding: you can pass in a point (latitude and longitude) and it will return a list of possible addresses.

In this tutorial, you will build a search application to search for addresses, provide users with a list of possible addresses, and when a user clicks an address the app will display that address on the map.

Before you begin

You must have previously installed the ArcGIS Runtime SDK for Qt and set up the development environment for your operating system.

Open the starter app project

If you have already completed the Create a starter app tutorial, start Qt Creator and open your starter app project. Otherwise, download and unzip the starter app project solution, and then open it in Qt Creator.

Steps

Add images to the project

  1. This project requires a few graphics files for the UI. In the project hierarchy, navigate to Resources > Resources > /Resources where you should see the existing file AppIcon.png. Select Add Existing Files... on the /Resources folder and add the following image files:

    • green-pin.png
    • ic_menu_closeclear_light_d.png

    If you do not have images you want to use, download the project solution, unzip it, and grab the image files from there.

Register the QML search component

  1. In Projects, double click on Sources > Create_a_starter_app.cpp and add the following code to register QAbstractListModel as a QML non-instantiable type.

    qmlRegisterType<MapQuickView>("Esri.create_a_starter_app", 1, 0, "MapView");
    
    // *** ADD ***
    qmlRegisterType<Search_for_an_address>("Esri.create_a_starter_app", 1, 0, "Search_for_an_address");
    qmlRegisterUncreatableType<QAbstractListModel>("Esri.create_a_starter_app", 1, 0, "AbstractListModel", "AbstractListModel is uncreateable");
    

Import headers and declare member variables

  1. In Projects, double click on Headers > Create_a_starter_app.h and add the following code to the namespace declaration.

    namespace Esri
    {
    namespace ArcGISRuntime
    {
    class Map;
    class MapQuickView;
    
    // *** ADD ***
    class GraphicsOverlay;
    class Graphic;
    class LocatorTask;
    class GeocodeResult;
    class SuggestResult;
    
  2. Remove the include statement for QObject and update it with the following include statements.

    class GraphicsOverlay;
    class Graphic;
    class LocatorTask;
    class GeocodeResult;
    class SuggestResult;
    
    // *** UPDATE ***
    // #include <QObject>
    #include <QQuickItem>
    #include <QAbstractListModel>
    #include "GeocodeParameters.h"
    
  3. In the declaration of the Create_a_starter_app class, update all occurrences of QObject to QQuickItem.

    // *** UPDATE ***
    // class Create_a_starter_app: public QObject
    class Create_a_starter_app: public QQuickItem
    {
    
  4. Add an additional Q_Property declaration after the declaration for mapView.

    class Create_a_starter_app: public QQuickItem
    {
      Q_OBJECT
    
      Q_PROPERTY(Esri::ArcGISRuntime::MapQuickView* mapView READ mapView WRITE setMapView NOTIFY mapViewChanged)
    
      // *** ADD ***
      Q_PROPERTY(QAbstractListModel* suggestions READ suggestions NOTIFY suggestionsChanged)
    
  5. In the declaration of the public methods replace QObject with QQuickItem and then add the following public member functions with the Q_INVOKABLE macro.

      Q_PROPERTY(QAbstractListModel* suggestions READ suggestions NOTIFY suggestionsChanged)
    
    public:
      // *** UPDATE ***
      // explicit Create_a_starter_app(QObject* parent = nullptr);
      explicit Create_a_starter_app(QQuickItem* parent = nullptr);
      ~Create_a_starter_app() override;
    
      // *** ADD **
      void componentComplete() override;
      Q_INVOKABLE void geocode(const QString& query);
      Q_INVOKABLE void clearGraphics();
      Q_INVOKABLE void setSuggestions(const QString& text);
    
  6. Then add the QML signal declarations.

    signals:
      void mapViewChanged();
    
      // *** ADD ***
      void suggestionsChanged();
      void hideSuggestionView();
    
  7. Add the following private member functions and properties.

    private:
      Esri::ArcGISRuntime::MapQuickView* mapView() const;
      void setMapView(Esri::ArcGISRuntime::MapQuickView* mapView);
      Esri::ArcGISRuntime::Map* m_map = nullptr;
      Esri::ArcGISRuntime::MapQuickView* m_mapView = nullptr;
    
      // *** ADD ***
      void configureGraphic();
      QAbstractListModel* suggestions() const;
    
      Esri::ArcGISRuntime::GraphicsOverlay* m_graphicsOverlay = nullptr;
      Esri::ArcGISRuntime::LocatorTask* m_locatorTask = nullptr;
      Esri::ArcGISRuntime::Graphic* m_graphic = nullptr;
      QAbstractListModel* m_suggestListModel = nullptr;
      Esri::ArcGISRuntime::GeocodeParameters m_geocodeParams;
    

Implement the locator task and auto-suggestion methods

In this section you will make changes to Sources > Create_a_starter_app.cpp to create a new LocatorTask instance that is configured to use the World Geocode Service. Then your will register the QML property suggestions with the private member property m_suggestListModel.

  1. In Projects, double click on Sources > Create_a_starter_app.cpp and add the following header file declarations.

    #include "MapQuickView.h"
    
    // *** ADD ***
    #include "SimpleRenderer.h"
    #include "PictureMarkerSymbol.h"
    #include "LocatorTask.h"
    #include "SuggestResult.h"
    #include "SuggestListModel.h"
    
    #include <QUrl>
    #include <QAbstractListModel>
    #include <QGeoPositionInfoSource>
    
  2. In the constructor, update the constructor to replace all occurrences of QObject with QQuickItem.

    // *** UPDATE ***
    // Create_a_starter_app::Create_a_starter_app(QObject* parent /* = nullptr */):
    //   QObject(parent),
    //   m_map(new Map(Basemap::topographicVector(this), this))
    Create_a_starter_app::Create_a_starter_app(QQuickItem* parent /* = nullptr */):
      QQuickItem(parent),
      m_map(new Map(Basemap::topographicVector(this), this))
    {
    
  3. Add the following code to the constructor to create the LocatorTask object and initialize the auto-suggestion list.

    Create_a_starter_app::Create_a_starter_app(QQuickItem* parent /* = nullptr */):
      QQuickItem(parent),
      m_map(new Map(Basemap::topographicVector(this), this))
    {
     const Point center(-118.71511, 34.09042, SpatialReference::wgs84());
     const Viewpoint vp(center, 300000.0);
      m_map->setInitialViewpoint(viewpoint);
    
      // *** ADD ***
      QUrl geocode_service("https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer");
      m_locatorTask = new LocatorTask(geocode_service, this);
    
      m_geocodeParams.setMinScore(75);
      m_geocodeParams.setResultAttributeNames(QStringList { "Place_addr", "Match_addr" });
    
      m_suggestListModel = m_locatorTask->suggestions();
      emit suggestionsChanged();
    
  4. Implement the auto-suggestion list and register it as the QML component search.

    void Create_a_starter_app::setSuggestions(const QString& text)
    {
      if (!m_suggestListModel)
        return;
    
      SuggestListModel* suggestListModel = dynamic_cast<SuggestListModel*>(m_suggestListModel);
    
      if (!suggestListModel)
        return;
    
      suggestListModel->setSearchText(text);
    }
    
  5. Add a componentComplete method that is called when the QML components of this class are fully loaded. You can use this method to also setup Qt's signals and slots to handle user interactions and asynchronous geocode methods after all the QML components are fully loaded.

    void Create_a_starter_app::componentComplete()
    {
      QQuickItem::componentComplete();
    
      // connect to geocode complete signal on the LocatorTask
      connect(m_locatorTask, &LocatorTask::geocodeCompleted, this, [this](QUuid, const QList<GeocodeResult>& geocodeResults)
        {
          if (geocodeResults.length() > 0 && m_graphic)
          {
            m_graphic->setGeometry(geocodeResults.at(0).displayLocation());
            m_graphic->attributes()->setAttributesMap(geocodeResults.at(0).attributes());
            constexpr double scale = 8000.0;
            m_mapView->setViewpointCenter(geocodeResults.at(0).extent().center(), scale);
            m_graphic->setVisible(true);
          }
        });
    
      connect(m_mapView, &MapQuickView::mousePressed, this, [this](QMouseEvent& /* event */)
        {
          emit hideSuggestionView();
        });
    }
    
  6. Add the configureGraphic public member function. It configures the map view to display the green pin using the SimpleRenderer and PictureMarkerSymbol classes.

    void Create_a_starter_app::configureGraphic()
    {
      if (m_graphic)
          return;
    
      // create graphics overlay and add to map view
      m_graphicsOverlay = new GraphicsOverlay(this);
    
      // set a renderer on the graphics overlay
      SimpleRenderer* simpleRenderer = new SimpleRenderer(this);
      const QUrl green_pin("qrc:/Resources/green-pin.png");
    
      // set a symbol to be used by the renderer
      PictureMarkerSymbol* pictureMarkerSymbol = new PictureMarkerSymbol(green_pin, this);
      constexpr int size = 35;
      pictureMarkerSymbol->setWidth(size);
      pictureMarkerSymbol->setHeight(size);
      pictureMarkerSymbol->setOffsetY(size / 2);
      simpleRenderer->setSymbol(pictureMarkerSymbol);
    
      m_graphicsOverlay->setRenderer(simpleRenderer);
    
      m_graphicsOverlay->graphics()->append(new Graphic(this));
      m_graphic = m_graphicsOverlay->graphics()->at(0);
    }
    
  7. Add the following to geocode an address when the user clicks on that address in the auto-suggestion list.

    void Create_a_starter_app::geocode(const QString& query)
    {
      SuggestParameters suggestParams;
      QStringList categories;
      categories << "Address" << "POI" << "Populated Place";
      suggestParams.setCategories(categories);
      suggestParams.setMaxResults(5);
      m_locatorTask->suggestions()->setSuggestParameters(suggestParams);
      m_locatorTask->geocodeWithParameters(query, m_geocodeParams);
    }
    
  8. Add a Create_a_starter_app::suggestions function to register the value of m_suggestListModel as the value of the QML property suggestions.

    QAbstractListModel* Create_a_starter_app::suggestions() const
    {
      return m_suggestListModel;
    }
    
  9. Add the Create_a_starter_app::clearGraphics function.

    void Create_a_starter_app::clearGraphics()
    {
      m_graphic->setGeometry(Point());
    }
    
  10. In the setMapView public member function, add the following code which adds the graphics overlay to the map view.

    void Search_for_an_address::setMapView(MapQuickView* mapView)
    {
      if (!mapView || mapView == m_mapView)
      {
        return;
      }
    
      m_mapView = mapView;
      m_mapView->setMap(m_map);
    
      // *** ADD ***
      configureGraphic();
      m_mapView->graphicsOverlays()->append(m_graphicsOverlay);
      emit mapViewChanged();
    

Design the QML search component

  1. In Projects, click Resources > qml/qml.qrc > /qml, double click on Create_a_starter_appForm.qml, and then add an import statement.

    import QtQuick.Controls 2.2
    
    // *** ADD ***
    import QtQuick.Layouts 1.3
    
  2. Add the following code to create the user interface and interactions for the search bar.

      mapView: view
    
      // *** ADD ***
    
      id: search
    
      height: parent.height
      width: parent.width
    
      property int cellHeight: 40;
    
      Column {
        anchors {
          fill: parent
          margins: 10
       }
       Rectangle {
         color: "#f7f8fa"
         border {
           color: "#7B7C7D"
         }
         radius: 2
         width: parent.width
         height: childrenRect.height
    
         GridLayout {
           width: parent.width
           columns: 4
            TextField {
              Layout.margins: 5
              Layout.fillWidth: true
              id: textField
              font.pixelSize: 14
              placeholderText: "Type in an address"
    
              onTextChanged: {
                if (text.length > 0 && suggestView)
                  suggestView.visible = true;
                search.setSuggestions(text);
              }
            }
    
            Rectangle {
              Layout.margins: 5
              width: height
              height: textField.height
              color: "#f7f8fa"
              visible: textField.length === 0
              enabled: visible
    
              Image {
                anchors.fill: parent
                source: "qrc:/Resources/ic_menu_closeclear_light_d.png"
                MouseArea {
                  anchors.fill: parent
                  onClicked: {
                    textField.focus = true;
                    suggestView.visible = !suggestView.visible;
                  }
                }
               }
             }
    
             Rectangle {
               Layout.margins: 5
               width: height
               color: "transparent"
               height: textField.height
               visible: textField.length !== 0
               enabled: visible
    
               Image {
                 anchors.fill: parent
                 source: "qrc:/Resources/ic_menu_closeclear_light_d.png"
    
                 MouseArea {
                   anchors.fill: parent
                   onClicked: {
                     textField.text = "";
                     search.clearGraphics();
                   }
                 }
               }
             }
           }
         }
    
  3. Use a QML ListView to display suggested location results and bind it to the locator task list model. Directly below the top-level Rectangle component, but inside the scope of the Column component, add the following QML ListView component to handle the displaying and UI of the auto-suggestions.

         // show a drop down of suggested locations
         ListView {
           id: suggestView
           height: 20 * search.cellHeight
           width: textField.width
           model: search.suggestions
    
           visible: false
           clip: true
           delegate: Component {
             Rectangle {
               id: rect
               width: textField.width
               height: search.cellHeight
               color: "#f7f8fa"
    
               Text {
                 anchors {
                   verticalCenter: parent.verticalCenter
                   leftMargin: 5
                   rightMargin: 5
                 }
    
                 font {
                   weight: Font.Black
                   pixelSize: 16
                 }
    
                 width: textField.width
                 text: label
                 elide: Text.ElideRight
                 leftPadding: 5
                 color: "black"
               }
    
               MouseArea {
                 anchors.fill: parent
                 onClicked: {
                   textField.text = label;
                   suggestView.visible = false;
                   search.geocode(label);
                   Qt.inputMethod.hide();
                 }
               }
             }
           }
         }
       }
    

Congratulations, you're done!

Your map should load. A text input field should display near the top of the map view with the Enter an address placeholder prompting the user to enter an address. As the user enters text in the search field suggestions appear. When an address is entered and search activated, or a suggested result is selected, the map should re-center on a pin at the chosen location. Compare your map with our completed solution project.

Challenge

Explore graphics

Try different configurations for the green_pin graphic, like changing its text, size, and color.

Add a callout

This tutorial is a simplified version of the Find an address and Find a place code samples. Go read the API documentation on CalloutData and then examine the GitHub repos for each of the code samples, and examine the header, source, and QML files to see how each project uses Qt signals and slots to show, hide, and modify callouts.

For this challenge, you will also need to import Esri.ArcGISRuntime.Toolkit.Controls 100.6 in your QML file to use callouts.

Reverse geocode

The LocatorTask class has a ReverseGeocodeParameters method. Experiment with code that uses a click on the map to perform a reverse geocode. For example, you could use a mouse click signal on the map view to reverse geocode the locations clicked on the map:

  connect(m_mapView, &MapQuickView::mouseClicked, this, [this](QMouseEvent& e)
  {
    m_mapView->identifyGraphicsOverlay(m_graphicsOverlay, e.x(), e.y(), 5, false, 1);
  });

  connect(m_mapView, &MapQuickView::identifyGraphicsOverlayCompleted, this,
    [this](QUuid, IdentifyGraphicsOverlayResult* result)
    {
      if (result->graphics().length() == 0)
        return;

      // display the callout with the identify result
      Graphic* graphic = result->graphics().at(0);
      const Point locationAtClick(graphic->geometry());
      qDebug() << "x,y: " << locationAtClick->x() << ", " << locationAtClick->y();
      m_locatorTask->reverseGeocode(locationAtClick);
    });

  connect(m_locatorTask, &LocatorTask::geocodeCompleted, this,
    [this](QUuid, const   QList<GeocodeResult>& geocodeResults)
    {
      if (geocodeResults.length() == 0)
        return;

      qDebug() << "Reverse geocode result: " << geocodeResults.at(0).label();
    });

Compare the FindPlace::connectSignals method implemented in FindPlace.cpp.