Overview

You will learn: how to search for coffee shops, gas stations, restaurants, and other places around a location.

The ArcGIS World Geocoding Service can find addresses and places, convert addresses to coordinates, and perform batch geocoding. If you want to create an application that searches for places such as coffee shops, gas stations, or restaurants, you can use a Locator with the ArcGIS World Geocoding Service and the geocodeAsync method. The method requires a GeocodeParameters object that contains the location (point) and the search category (e.g., "Coffee Shop") to search for. You can add the returned results to a map, create a route, or integrate them further into your application. Visit the documentation to learn more about finding places and the capabilities of the geocoding service.

In this lab you will build an app to search for different places around the Santa Monica Mountains area.

Before you begin

Make sure you have installed the latest version of Android Studio.

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

Reuse the starter project

In a new or empty project folder, make a copy of the Create a starter app or download and unzip the project solution.

  • Open the new Android project in Android Studio.

  • Run the project and verify the project builds and the map displays in the device simulator.

  • You may be required to sync the project dependencies.

Steps

Add a category selector

  1. Create a new XML file named categories.xml in the app > res > values folder of your project. Add the array of category names to it. The XML should appear as follows:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    <string-array name="categories_array">
        <item>Coffee shop</item>
        <item>Gas station</item>
        <item>Food</item>
        <item>Hotel</item>
        <item>Neighborhood</item>
        <item>Parks and Outdoors</item>
    </string-array>
    </resources>
    
  2. Open activity_main.xml in the app > res > layout folder in your project and add a Spinner control to contain the categories to choose from.

        <Spinner
            android:id="@+id/spinner"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_alignParentTop="true"
            android:entries="@array/categories_array"
            android:popupBackground="#fbfafa"
            tools:popupBackground="@android:color/background_light" />
    

Set up the LocatorTask

  1. Open the MainActivity.java file. Add a member variable to hold a reference to a GraphicsOverlay that you'll add the place search results to. Create a LocatorTask with the ArcGIS World Geocoder url. Also create a reference to the Spinner UI component.

    private MapView mMapView;
    // *** ADD ***
    private GraphicsOverlay graphicsOverlay;
    private LocatorTask locator = new LocatorTask("http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer");
    private Spinner spinner;
    
  2. Add a new findPlaces method to perform the place search and update the graphics overlay with the results. Create and set up the GeocodeParameters for the LocatorTask. Set the location, no more than 25 locations to return, and the Place_addr and PlaceName attributes to the GeocodeParameters object.

    private void findPlaces(String placeCategory) {
        GeocodeParameters parameters = new GeocodeParameters();
        Point searchPoint;
    
        // Set the current map extent as the current location. The search is limited to a
        // radius of 50 kilometers around this point.
        if (mMapView.getVisibleArea() != null) {
            searchPoint = mMapView.getVisibleArea().getExtent().getCenter();
            if (searchPoint == null) {
                return;
            }
        } else {
            return;
        }
        parameters.setPreferredSearchLocation(searchPoint);
    
        // We're interested in the top 25 nearest places.
        parameters.setMaxResults(25);
    
        // Return the place name and address fields in the results.
        List<String> outputAttributes = parameters.getResultAttributeNames();
        outputAttributes.add("Place_addr");
        outputAttributes.add("PlaceName");
    }
    
  3. On the next line after the attributes, call geocodeAsync with the selected place category and the parameters. On the result to be returned, add the listener. When the execution of geocodeAsync is complete, iterate over the results and add them to the map as graphics in the graphics overlay. Set the attributes, geometry, and symbol to each graphic as follows:

        // Execute the search and add the places to the graphics overlay.
        final ListenableFuture<List<GeocodeResult>> results = locator.geocodeAsync(placeCategory, parameters);
        results.addDoneListener(() -> {
            try {
                ListenableList<Graphic> graphics = graphicsOverlay.getGraphics();
                graphics.clear();
                List<GeocodeResult> places = results.get();
                for (GeocodeResult result : places) {
    
                    // Add a graphic representing each location with a simple marker symbol.
                    SimpleMarkerSymbol placeSymbol = new SimpleMarkerSymbol(SimpleMarkerSymbol.Style.CIRCLE, Color.GREEN, 10);
                    placeSymbol.setOutline(new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.WHITE, 2));
                    Graphic graphic = new Graphic(result.getDisplayLocation(), placeSymbol);
                    java.util.Map<String, Object> attributes = result.getAttributes();
    
                    // Store the location attributes with the graphic for later recall when this location is identified.
                    for (String key : attributes.keySet()) {
                        String value = attributes.get(key).toString();
                        graphic.getAttributes().put(key, value);
                    }
                    graphics.add(graphic);
                }
            } catch (InterruptedException | ExecutionException exception) {
                exception.printStackTrace();
            }
        });
    

Display a location with a Callout

  1. Create a new method to show the place attributes in a Callout when the user taps a place on the map. This new method will require the graphic that was identified in the graphics overlay and the corresponding point on the map. The location attributes are formatted in an Android TextView then set on the Callout.

    private void showCalloutAtLocation(Graphic graphic, Point mapPoint) {
        Callout callout = mMapView.getCallout();
        TextView calloutContent = new TextView(getApplicationContext());
    
        callout.setLocation(graphic.computeCalloutLocation(mapPoint, mMapView));
        calloutContent.setTextColor(Color.BLACK);
        calloutContent.setText(Html.fromHtml("<b>" + graphic.getAttributes().get("PlaceName").toString() + "</b><br>" + graphic.getAttributes().get("Place_addr").toString()));
        callout.setContent(calloutContent);
        callout.show();
    }
    

Call findPlaces when a category is selected

  1. Add a new setupSpinner method to add the spinner event handlers and run the initial place search. When a new selection is made, call findPlaces with the selected category to run a place search.

    private void setupSpinner() {
        spinner = findViewById(R.id.spinner);
        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                findPlaces(adapterView.getItemAtPosition(i).toString());
            }
    
            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {
            }
        });
        findPlaces(spinner.getSelectedItem().toString());
    }
    
  2. Add a new setupNavigationChangedListener method to add an event lister when map navigation changes. This method handles running a new search when the user stops navigating the map using the updated map location.

    private void setupNavigationChangedListener() {
        mMapView.addNavigationChangedListener(navigationChangedEvent -> {
            if (!navigationChangedEvent.isNavigating()) {
                mMapView.getCallout().dismiss();
                findPlaces(spinner.getSelectedItem().toString());
            }
        });
    }
    

Call identify when the user touches a result on the map

  1. Add a new setupPlaceTouchListener method in MainActivity. This adds an OnTouchListener to the MapView that identifies a place location from the GraphicsOverlay and displays it's place name and address using the showCalloutAtLocation method you created in a prior step.

    private void setupPlaceTouchListener() {
        mMapView.setOnTouchListener(new DefaultMapViewOnTouchListener(this, mMapView) {
            @Override
            public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
    
                // Dismiss a prior callout.
                mMapView.getCallout().dismiss();
    
                // get the screen point where user tapped
                final android.graphics.Point screenPoint = new android.graphics.Point(Math.round(motionEvent.getX()), Math.round(motionEvent.getY()));
    
                // identify graphics on the graphics overlay
                final ListenableFuture<IdentifyGraphicsOverlayResult> identifyGraphic = mMapView.identifyGraphicsOverlayAsync(graphicsOverlay, screenPoint, 10.0, false, 2);
    
                identifyGraphic.addDoneListener(() -> {
                    try {
                        IdentifyGraphicsOverlayResult graphicsResult = identifyGraphic.get();
                        // get the list of graphics returned by identify graphic overlay
                        List<Graphic> graphicList = graphicsResult.getGraphics();
    
                        // get the first graphic selected and show its attributes with a callout
                        if (!graphicList.isEmpty()){
                            showCalloutAtLocation(graphicList.get(0), mMapView.screenToLocation(screenPoint));
                        }
                    } catch (InterruptedException | ExecutionException exception) {
                        exception.printStackTrace();
                    }
                });
                return super.onSingleTapConfirmed(motionEvent);
            }
        });
    }
    

Put it all together

  1. Update the setupMap method to create the places graphics overlay and set up the spinner, navigation changed listener, and place touch listener, but only after the map has completed drawing its initial view.

            mMapView.setMap(map);
    
            // *** ADD ***
            mMapView.addViewpointChangedListener(new ViewpointChangedListener() {
                @Override
                public void viewpointChanged(ViewpointChangedEvent viewpointChangedEvent) {
                    if (graphicsOverlay == null) {
                        graphicsOverlay = new GraphicsOverlay();
                        mMapView.getGraphicsOverlays().add(graphicsOverlay);
                        setupSpinner();
                        setupPlaceTouchListener();
                        setupNavigationChangedListener();
                        mMapView.removeViewpointChangedListener(this);
                    }
                }
            });
    
  2. Run your app and test the code in the Android emulator to see the map.

Congratulations, you're done!

Your map should load and center on the Malibu area near Los Angeles, California, with coffee shop locations displayed. You can touch one of the coffee shops and see it's place name and address. In the upper left corner of the app, you'll see the spinner that allows you to select different place categories to search for. Compare your project with our completed solution project.

Challenge

Explore more categories

The World Geocoding service can find many different types of places. Explore the Level 1, Level 2, and Level 3 Categories and add them to the array in the categories.xml file to search for additional places that you are interested in. For example:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="categories_array">
    <item>Coffee shop</item>
    <item>Gas station</item>
    <item>Food</item>
    <item>Hotel</item>
    <item>Neighborhood</item>
    <item>Parks and Outdoors</item>
    <item>American Food</item>
    <item>Chinese Food</item>
    <item>Mexican Food</item>
</string-array>
</resources>

Improve the UI

In this lab, you display categories to search for with a persistent spinner control constrained to the top of the map. Can you come up with a better way?

Use your current location

Instead of performing the search at a prescribed location, use the device's current location. Review the lab Display and track your location to learn more about how to do this.