You will learn: how to build an app to search for coffee shops, gas stations, restaurants, and other nearby places.
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 tutorial you will build an app to search for different places around the Santa Monica Mountains area.
Make sure you have installed the latest version of Android Studio.
If you have completed the Create a starter app tutorial, then copy the project into a new empty folder. Otherwise, download and unzip the project solution. Open, run, and verify the map displays in the device simulator.
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>
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" />
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("https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer");
private Spinner spinner;
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 preferredSearchLocation
, 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;
if (mMapView.getVisibleArea() != null) {
searchPoint = mMapView.getVisibleArea().getExtent().getCenter();
if (searchPoint == null) {
return;
}
} else {
return;
}
parameters.setPreferredSearchLocation(searchPoint);
parameters.setMaxResults(25);
List<String> outputAttributes = parameters.getResultAttributeNames();
outputAttributes.add("Place_addr");
outputAttributes.add("PlaceName");
}
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();
}
});
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();
}
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());
}
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());
}
});
}
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);
}
});
}
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);
}
}
});
Run your app and test the code in the Android emulator to see the map.
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.
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>
In this tutorial, 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?
Instead of performing the search at a prescribed location, use the device's current location. Review the tutorial Display and track your location to learn more about how to do this.