Add geocoding to your app

This tutorial guides you through the process of building an application to find places and addresses and show them on a map. It's assumed that you've completed the tutorial on how to create a simple map application using ArcGIS Runtime SDK for Java.

The API provides a Locator class that allows you to match addresses and place names to locations on a map (also known as geocoding), and conversely, match locations on a map to real world addresses (known as reverse geocoding).

The same Locator class and coding patterns can be used with both online and offline data. The online locator uses Esri's geocoding web services, which can be hosted on Esri's cloud platform (ArcGIS Online) or on your own server. You can also perform geocoding and reverse geocoding offline by providing your own local locator data. In this tutorial, you'll use Esri's World geocode web service hosted on ArcGIS Online.

Create the project in Eclipse

The following are concise steps on how to use the SDK's Eclipse plug-in to create a Java map application. See the Create a simple map application tutorial for more detailed instructions on setting up an ArcGIS Runtime SDK for Java project using the Eclipse plug-in before starting this workflow.

  1. In Eclipse, create a new map application using the plug-in's template project: File > New > Project > ArcGIS Runtime for Java > ArcGIS Runtime Java Map Application.
  2. Using the new project wizard, name the project and starting class PlaceSearch, and create it in a package of your choice, for example com.arcgis.java.tutorial.placesearch.

The PlaceSearch.java file now contains the base of the project you'll use in this tutorial.

Set up the map

  1. Add the following field declarations to the top of the PlaceSearch class:
    // application fields
    private GraphicsLayer locationLayer;
    private PictureMarkerSymbol locationSymbol;
    private Locator locator;

    Import all the classes that are now underlined in red (for example, GraphicsLayer) by pressing Ctrl-Shift-O. Make sure to choose the com.esri.core.tasks.geocode.Locator option for the Locator import. You can also import each class one by one by hovering over the class name underlined in red and selecting the appropriate class from the context menu that appears.

    Adding these fields allows you to instantiate those objects later in your application. You'll create a GraphicsLayer to show address graphics on the map on top of the basemap, a PictureMarkerSymbol to symbolize address graphics, and a Locator to perform the geocoding. Using fields instead of local variables in methods allows you to reuse the same objects in various parts of the application. For example, you don't want to create a Locator object each time you perform a geocoding operation, because it's a costly operation. Instead, you'll create the Locator instance once and reuse it whenever you geocode an address.

  2. Create the graphics layer you'll use to show a graphic on the map when a place is found, add it to the map, and set up the Locator. At the bottom of the PlaceSearch constructor, remove the commented out dynamic layer code and replace it with the following code:
    locationLayer = new GraphicsLayer();
    map.getLayers().add(locationLayer);
    
    // locator set up
    locator = Locator.createOnlineLocator(GEOCODE_URL);

    You will add a value for GEOCODE_URL in the next section; until then it will show as a build error.

  3. Set the initial map extent to your area of interest (North America) anywhere in the PlaceSearch() constructor after the map is instantiated.
    // set default extent to North America
    map.setExtent(new Envelope(-20042400, 856094, -2783530, 11716267));

    Import the Envelope class by pressing Ctrl-Shift-O.

  4. Create the location PictureMarkerSymbol you declared above, using the URL to the PNG image you want to symbolize a geocode result. Add the following code below the line where you create the online locator:
    // create symbol
    locationSymbol = new PictureMarkerSymbol(SYMBOL_URL);
    locationSymbol.setSize(40, 40);
    // Y-offset of half the height so that bottom of the pin points to location
    locationSymbol.setOffsetY(20.0f);

    You will add a value for SYMBOL_URL in the next section; until then it will show as a build error.

Set up the UI

The steps in this section guide you through creating a user interface (UI) to find places and addresses. You'll implement a user panel containing a label for entering an address, a text area to get the text input from the user, and a button to perform the geocoding using the address text provided by the user. The user panel and the map are added to a layered pane to display the panel on top of the map.

  1. To add text to the UI, set up each string as a constant in the class. This allows you to manage all UI text in a single location, which makes it easier to find and update. You can also set up any service URLs as string constants to make them easy to manage as well. Add the following string constants at the top of the PlaceSearch class:
    // geocode service
    private static final String GEOCODE_URL = 
      "http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer";
    // text for geocode label 
    private static final String GEOCODE_LABEL = "Enter a USA input address:"; 
    // locate button string
    private static final String BUTTON_LOCATE = "Locate address on map";
    // address hint
    private static final String ADDRESS = "380 New York St Redlands CA 92373";
    // red pin marker PNG image hosted by ArcGIS
    private static final String SYMBOL_URL = 
      "http://static.arcgis.com/images/Symbols/Basic/RedShinyPin.png";

    The build errors for undefined Strings from the previous section should no longer be showing.

  2. Add additional fields for UI components you'll use in the application and import the missing Swing classes using Ctrl-Shift-O.
    // label with instructions
    private JLabel geocodeLabel;
    // text field to get user input
    private JTextField addressInput;
  3. Create a JLayeredPane instance as your window's content pane so that you can layer components onto each other, then first add a user panel to it, then the map. In the PlaceSearch constructor, remove the window.getContentPane().add(map); line, and replace it with the following code:
    JLayeredPane contentPane = new JLayeredPane();
    contentPane.setLayout(new BorderLayout());
    contentPane.add(createPanel());
    contentPane.add(map);
    window.add(contentPane);
  4. In Eclipse, hover over the createPanel() text, which is underlined in red, and select Create method createPanel() to have Eclipse generate the method for you.
    private Component createPanel() {
      // TODO Auto-generated method stub
      return null;
    }

    The method is now stubbed out as shown above.

  5. Create the user panel, and set up the Swing components to retrieve and display content: a JLabel to display single-line instructions to the user, a JTextField to get single-line text input from the user, and a JButton to find locations on button click. Fill the createPanel() method stub with the following code:
    // user panel
    JPanel panel = new JPanel();
    panel.setLocation(10, 10);      
    panel.setBackground(Color.DARK_GRAY);
    panel.setBounds(10, 10, 320, 90);
    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
    panel.setBorder(BorderFactory.createEmptyBorder(5,10,5,10));    
     
    // address label
    geocodeLabel = new JLabel(GEOCODE_LABEL);
    geocodeLabel.setForeground(Color.WHITE);
    
    // user input text field
    addressInput = new JTextField();
    addressInput.setAlignmentX(Component.LEFT_ALIGNMENT);
    addressInput.setText(ADDRESS);
     
    // geocode button
    JButton findButton = new JButton(BUTTON_LOCATE);
    findButton.setAlignmentX(Component.LEFT_ALIGNMENT);
     
    // add the UI components to your panel
    panel.add(geocodeLabel);
    panel.add(Box.createRigidArea(new Dimension(0, 5)));
    panel.add(addressInput);
    panel.add(Box.createRigidArea(new Dimension(0, 5)));
    panel.add(findButton);
    panel.setVisible(true);

    As done previously, press Ctrl-Shift-O to import the unimported classes used above.

  6. Change the createPanel method from returning null to returning the panel instance.
    private Component createPanel() {
      JPanel panel = new JPanel();
      ...
      return panel;
    }
  7. You can now run the application: you'll see a map filling the content pane of the window with a user panel in the top left corner containing your label, text field, and button. In Eclipse, right-click the PlaceSearch.java file in the Package Explorer window, and select Run As > Java Application.

Get user input and geocode

In this section you'll implement the geocode method and wire it to the button in your UI so that when the user clicks the button, geocoding is performed using the text provided by the user in your UI's text field.

  1. Add the following to the findButton in the createPanel method you created previously and import the missing classes, selecting com.esri.core.tasks.geocode.LocatorGeocodeResult when given a choice.
    findButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        // remove any previous graphics
        locationLayer.removeAll();
        // get address text from text field and use it to geocode
        LocatorGeocodeResult result = geocode(addressInput.getText());
      }
    });

    In this implementation, when the user clicks the find button, you clear any previous graphics from your graphics layer, get the address text from the text field, and pass it as input to a geocode method. You'll implement this geocode method next.

  2. The geocode method call in the button action listener above appears with a red underline in Eclipse, because you have not yet created this method. Hover over this method call and select Create method geocode(String) in type PlaceSearch to have Eclipse generate a method stub for you.
    private LocatorGeocodeResult geocode(String text) {
      // TODO Auto-generated method stub
      return null;
    }
  3. Set up the LocatorFindParameters for single-line geocoding by adding the following code at the top of the geocode method:
    // Set basic parameters to locate the address
    LocatorFindParameters findParams = new LocatorFindParameters(text);
    findParams.setOutSR(map.getSpatialReference());
    findParams.setMaxLocations(4);

    To find matching locations for an address, you must provide a LocatorFindParameters object containing the address in a format expected by the service, either as a single-line address or a formatted address in a Map<String, String> with the address fields that the service expects. The service you used above allows for a search string to be in the form of a single line of text, which is why you use a single text field component to obtain text input from the user.

  4. Add the following code directly below the previous code snippet to perform the geocoding:
    LocatorGeocodeResult highestScoreResult = null;
    try {
      List<LocatorGeocodeResult> results = locator.find(findParams);
      if (results != null && results.size() > 0) 
        highestScoreResult = results.get(0);
      else {
        // show no address found message
        JOptionPane.showMessageDialog(map.getParent(), "No address found!");
      }
    } catch (Exception ex) {
      // show exception error message
      JOptionPane.showMessageDialog(map.getParent(), ex.getMessage());
      ex.printStackTrace();
    }

    First you declared a LocatorGeocodeResult to store your highest scoring result. Next you passed the address parameters to the Locator's find method to have a point returned representing the location you can add to your map (the find method needs to be used with a service that supports single-line geocoding). You now have to ensure that the find method has returned at least one result and that no error has occurred. In either case, a pop-up dialog with an error message will be displayed to the user.

  5. Update the return value of the geocode method to highestScoreResult instead of null. This way, the calling code can store the geocode result and take further steps to display it to the user.

Display a result location on the map

In the previous section, you had your geocode method return the single highest scoring result found by the locator. Now you'll display this result as a point on the map.

  1. In the findButton action listener, add a method call to displayGeocodeResult(result); you'll implement this method next and have it show the top result location on the map.
    ...
    LocatorGeocodeResult result = geocode(addressInput.getText());
    displayGeocodeResult(result);
  2. Hover over this method call and select Create method displayGeocodeResult(LocatorGeocodeResult) in type PlaceSearch to have Eclipse generate the method stub for you.
  3. If a result is found, use the result to create and populate an attribute map and create a graphic, and add the graphic to the graphics layer to display it on the map. Add the following code to the unimplemented displayGeocodeResult method:
    if (result == null) return;
    // make sure this runs on the UI thread
    SwingUtilities.invokeLater(new Runnable() {
    
      @Override
      public void run() {
        // create and populate attribute map
        Map<String, Object> attributes = new HashMap<String, Object>();
        for (Entry<String, String> entry : result.getAttributes().entrySet()) {
          attributes.put(entry.getKey(), entry.getValue());
        }
        // create a graphic at this location
        Graphic addressGraphic = new Graphic(result.getLocation(), locationSymbol, attributes);
        locationLayer.addGraphic(addressGraphic);
    
        // centre the map at this location, in a 10x10 km envelope
        Envelope extent = map.getExtent();
        extent.centerAt(result.getLocation(), 10000, 10000);
        map.zoomTo(extent);
      }
    });

    Alternatively, to have the map centered on the result, but not further zoomed in, use the single parameter version of the centerAt method: extent.centerAt(result.getLocation()).

  4. In Eclipse, right-click your project and select Run As > Java Application.

    Your application runs with the text input field populated with the default value and ready for you to click the Locate address on map button.

  5. Type any United States place name in the text field and click the button to test your application with different inputs.

That's it! You've built a place search application using ArcGIS Runtime SDK for Java. For more geocoding samples, see the sample application installed with the SDK under the table of contents heading Search. Using these samples, you can learn how to extend this application to display the location graphic's attributes in a pop-up window or a map tip, or learn how to perform geocoding using local data for an offline scenario.