Overview

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

With the ArcGIS Runtime SDK for Qt you can easily find places and addresses all around the world. The process of matching locations on the map to an address is referred to as geocoding. Your user can specify a location of interest or street address and see it on the map.

Locating addresses is a complex topic. This lab presents one of many possible ways to handle address searches in an app. Refer to Search for places (geocoding) if you would like to read more about the process and options.

The solution to this lab will include the following concepts and features:

  • An input field where the user can enter an address to look up.
  • A list control to display suggested address matches and locate a selected result.
  • Display a pin on the map indicating a located address.
  • Display a callout with the located address information or an error message.

Before you begin

You must have previously installed the ArcGIS Runtime SDK for Qt and set up your development environment. Please review the install and set up instructions if you have not done this.

Steps

Create a new ArcGIS Runtime App Qt Creator Project

  1. Start Qt Creator.

  2. Choose File > New File or Project and select the ArcGIS project template for ArcGIS Runtime 100.0.0 Qt Quick app. Give your app a name. For this lab we chose search-for-an-address, but you can choose any name that suits you. Also select the location where your project files should reside on your system. Select Continue.

  3. On the Define Project Details dialog, leave the settings as suggested. Select Continue.

  4. On the Kit Selection dialog, check the kits you previously set up when you installed the SDK. Select Continue.

  5. Verify your selections and select Done.

Add resource files

  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.

Imports and variables

  1. Open the main.qml code file in your project (Resources > qml > qml.qrc > /qml > main.qml). If you followed the prior steps, it should already be open in the IDE.

  2. Add new import statements to import Qt Quick Controls Styles, Esri ArcGIS Runtime Extas and Toolkit. These libraries support the user interface you are going to build in this lab.

    import QtQuick.Controls.Styles 1.4
    import Esri.ArcGISExtras 1.1
    import Esri.ArcGISRuntime.Toolkit.Controls 2.0
    
  3. Inside the ApplicationWindow object definition, immediately after the title property, add additional properties you will use to control the logic in this lab. We will explain the purpose of these variables when you use them in the code.

    property int pinGraphicHeight: 35
    property Point pinLocation: null
    property bool isUpdatingView: false
    property string geocodeService: "http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer"
    property real scaleFactor: System.displayScaleFactor
    

Set up the map

  1. Update the MapView declaration to give it an id you can reference in other parts of your code.

        MapView {
           id: mapView
           ...
    
  2. Update the Map declaration to use the topographic vector basemap and center the map view around the Los Angeles area.

       Map {
            BasemapTopographicVector {}
            ViewpointCenter {
                Point {
                    x: -118.71511
                    y: 34.09042
                    SpatialReference { wkid: 4326 }
                }
                targetScale: 300000
            }
        }
    
  3. Add the callout. Use the callout to display information about a located address returned from a successful locator task result. This declaration should be placed immediately after the Map object closing brace so that Callout is an attribute of the MapView object.

        calloutData {
             title: "location"
             detail: "address"
             geoElement: pinGraphic
         }
         Callout {
             id: callout
             calloutData: parent.calloutData
             screenOffsetY: -pinGraphicHeight / 2
             accessoryButtonHidden: true
         }
    
  4. Create a GraphicsOverlay to show a pin on the map at the located address. This declaration should be place immediately after the Callout object closing brace so that GraphicsOverlay is an attribute of the MapView object.

         GraphicsOverlay {
             id: graphicsOverlay
             Graphic {
                 id: pinGraphic
                 visible: false
                 geometry: pinLocation
                 PictureMarkerSymbol {
                     url: "qrc:/Resources/green-pin.png"
                     width: pinGraphicHeight
                     height: pinGraphicHeight
                     offsetY: height / 2 // the tip of the pin will point to the location
                 }
             }
         }
    
  1. Create a search input field using the QML TextField control. Place this code immediately after the closing brace of the MapView declaration, so that it is within the ApplicationWindow object declaration.

         Column {
              anchors {
                  fill: parent
                  margins: 10 * scaleFactor
              }
              Row {
                  width: parent.width
                  height: pinGraphicHeight * scaleFactor
                  spacing: 5
                  TextField {
                      id: searchField
                      width: parent.width
                      height: parent.height
                      placeholderText: "Enter an address"
                      font.pixelSize: 16 * scaleFactor
                      style: TextFieldStyle {
                          background: Rectangle {
                              color: "#f7f8fa"
                              border {
                                  color: "#7B7C7D"
                                  width: 1 * scaleFactor
                              }
                              radius: 5
                          }
                      }
                      onAccepted: {
                          if (searchField.text.length > 0) {
                              locatorTask.geocodeWithParameters(searchField.text, geocodeParameters);
                              suggestView.visible = false;
                              Qt.inputMethod.hide();
                          }
                          clearResult();
                      }
                      onTextChanged: {
                          if (searchField.text.length == 0) {
                              suggestView.visible = false;
                              clearResult();
                          } else {
                              suggestView.visible = true;
                          }
                      }
                      Image {
                          anchors {
                              right: parent.right
                              top: parent.top
                              bottom: parent.bottom
                              margins: 1 * scaleFactor
                          }
                          source: "qrc:/Resources/ic_menu_closeclear_light_d.png"
                          width: parent.height
                          height: parent.height
                          visible: parent.text.length !== 0
                          MouseArea {
                              anchors.fill: parent
                              onClicked: {
                                  searchField.text = "";
                                  clearResult();
                              }
                          }
                      }
                  }
              }
          }
    

Show suggestions

  1. Use a QML ListView to display suggested location results and bind it to the locator task list model. This declaration belongs inside the Column definition you created in the prior step. Make sure it is inside the Column declaration and immediately after the closing brace of the Row declaration. This way the list appears beneath the search input field.

            ListView {
                id: suggestView
                height: 300 * scaleFactor
                width: searchField.width
                visible: false
                clip: true
                model: locatorTask.suggestions
                delegate: Component {
                    Rectangle {
                        id: rect
                        width: parent.width
                        height: 25 * scaleFactor
                        color: "#f7f8fa"
                        Rectangle {
                            anchors {
                                top: parent.top;
                                left: parent.left;
                                right: parent.right;
                                topMargin: -5 * scaleFactor
                                leftMargin: 20 * scaleFactor
                                rightMargin: 20 * scaleFactor
                            }
                            color: "darkgrey"
                            height: 1
                        }
                        Text {
                            text: label
                            anchors {
                                fill: parent
                                leftMargin: 5 * scaleFactor
                            }
                            font.pixelSize: 16 * scaleFactor
                        }
                        MouseArea {
                            anchors.fill: parent
                            onClicked: {
                                searchField.text = label;
                                suggestView.visible = false;
                                Qt.inputMethod.hide();
                                locatorTask.geocodeWithSuggestResultAndParameters(locatorTask.suggestions.get(index), geocodeParameters);
                            }
                        }
                    }
                }
            }
    
    

Search for an address

  1. Create a LocatorTask to handle suggestions as the user types into the search field and to geocode an address entered by the user into a geographical location. This declaration belongs on the ApplicationWindow object: place it after either the MapView or the Column object declarations.

        LocatorTask {
            id: locatorTask
            url: geocodeService
            GeocodeParameters {
                id: geocodeParameters
                minScore: 75
                maxResults: 1
                resultAttributeNames: ["Place_addr", "Match_addr"]
            }
            suggestions {
                searchText: searchField.text
                suggestTimerThreshold: 750
                suggestParameters: SuggestParameters {
                    maxResults: 4
                }
            }
            onGeocodeStatusChanged: {
                if (geocodeStatus === Enums.TaskStatusCompleted) {
                    if (geocodeResults.length > 0) {
                        // update and show the pin marker and the callout with the address information
                        pinLocation = geocodeResults[0].displayLocation;
                        pinGraphic.visible = true;
                        mapView.setViewpointGeometry(geocodeResults[0].extent);
                        mapView.calloutData.title = geocodeResults[0].attributes.Match_addr;
                        mapView.calloutData.detail = geocodeResults[0].attributes.Place_addr;
                    } else {
                        // no results found, show the user a message
                        mapView.calloutData.location = mapView.currentViewpointCenter.center;
                        mapView.calloutData.title = "No matching location"
                        mapView.calloutData.detail = "Try another search"
                    }
                    callout.showCallout();
                    isUpdatingView = true;
                }
            }
        }
    

Final touches

  1. There are a few missing pieces of code. Inside MapView, immediately following the declaration of GraphicsOverlay, add the following code to manage the map coordination with the pin and callout.

            onViewpointChanged: {
                if ( ! isUpdatingView) {
                    callout.dismiss();
                }
                locatorTask.suggestions.preferredSearchLocation = mapView.currentViewpointCenter.center;
            }
            onDrawStatusChanged: {
                if (drawStatus !== Enums.DrawStatusInProgress) {
                    isUpdatingView = false;
                }
            }
    

    Next, define a function to clear any results visible on the map once they are no longer valid. This function is defined on the ApplicationWindow declaration: place it at the bottom before the closing brace.

        function clearResult() {
            pinGraphic.visible = false;
            callout.dismiss();
         }
    

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 with a callout displaying the geocoded address. Compare your map with our completed solution project.

Challenge

Explore graphics

Try different graphics rendering for the pinGraphic and Callout - change the text and graphic size and color.

Multiple results

Try setting setMaxResults to a number larger than one and show the user the possible matches. Can you come up with a UI that allows the user to select a possible match?

Geocoding parameters

Can you discover other geocoding parameters available to the locator task? For example, limit the search area to the visible extent of the map view.

Reverse geocode

The LocatorTask class has a ReverseGeocodeAsync method that returns an interpolated address (string) for a location (map point). Experiment with code that uses a click on the map to perform a reverse geocode.