Sorry, you need to enable JavaScript to visit this website.

Search along a route

Overview

This tutorial shows how to use TomTom Maps SDK for Android to create an application that helps a user find points of interest along a planned route.

It shows how to use:

  • The TomTom Map SDK module to display a map, including markers with custom icons and balloons.
  • The TomTom Routing SDK module to plan routes with and without waypoints.
  • The TomTom Search SDK module to search for points of interest (POIs) and to geocode map positions.

An end user can start interacting with the application by planning a route with departure and destination points. One long click on the map sets a departure point. A second long click sets a destination point and draws a route between those two points on the map.

When the route is visible on the map, the user can type a POI name or category into a search field or click on any of the predefined POI category buttons (gas station, restaurant, ATM). The map displays markers for POIs that match the user's request. The user can add one of the displayed POIs to their planned route by clicking the marker on the map and choosing the "Add to my route" button inside the marker balloon that is then displayed. The route is recalculated and redrawn to include the selected point.

Prerequisites:

  1. Create a new project (minimum SDK API 23 – Android 6.0 “Marshmallow”) with an Empty Activity named MainActivity. Make sure that the Backwards Compatibility (AppCompat) option is enabled.
  2. In the build.gradle project file, add the TomTom repository to the list of repositories. The TomTom Maps SDK dependencies are downloaded from there.
    allprojects {
        repositories {
            google()
            jcenter()
            maven {
                url "https://maven.tomtom.com:8443/nexus/content/repositories/releases/"
            }
        }
    }
    
  3. In the app/build.gradle file, add dependencies to the TomTom Map, Search and Routing SDK modules along with android support libraries.
    dependencies { 
        implementation fileTree(dir: 'libs', include: ['*.jar']) 
        implementation 'com.android.support:appcompat-v7:27.1.1' 
        implementation 'com.android.support:support-compat:27.1.1' 
        implementation 'com.android.support.constraint:constraint-layout:1.1.2' 
        implementation 'com.tomtom.online:sdk-maps:2.243' 
        implementation 'com.tomtom.online:sdk-routing:2.243' 
        implementation 'com.tomtom.online:sdk-search:2.243' 
        implementation 'com.tomtom.online:sdk-maps-ui-extensions:2.243' 
    } 
  4. If you don't have an API key follow these steps:

    Create an API key now

    To create a new API key, first log in or register on the portal.

    Next, create a new application in your Dashboard:

    Choose all the APIs:

    You are now set up. Click on your newly created app and copy your API key:

  5. Copy and paste the API key into your AndroidManifest.xml inside the <application> tag:
    <meta-data android:name="OnlineMaps.Key" android:value="<_______YOUR_KEY_GOES_HERE_______>" />
    <meta-data android:name="OnlineSearch.Key" android:value="<_______YOUR_KEY_GOES_HERE_______>" />
    <meta-data android:name="OnlineRouting.Key" android:value="<_______YOUR_KEY_GOES_HERE_______>" />
  6. Create a file named dimens.xml inside your res/values directory. Add the dimension values of you UI components to it.
    <resources> 
      <dimen name="size_none">0dp</dimen> 
    </resources> 

Map initialization

To initialize a TomTom map, add a com.tomtom.onlinesdk.map.MapFragment fragment into the main ConstraintLayout section of the activity_main.xml file.

<?xml version="1.0" encoding="utf-8"?> 
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:app="http://schemas.android.com/apk/res-auto" 
    xmlns:tools="http://schemas.android.com/tools" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    tools:context=".MainActivity"> 
 
    <fragment 
        android:id="@+id/mapFragment" 
        android:name="com.tomtom.online.sdk.map.MapFragment" 
        android:layout_width="match_parent" 
        android:layout_height="@dimen/size_none" 
        app:layout_constraintBottom_toBottomOf="parent" 
        app:layout_constraintEnd_toEndOf="parent" 
        app:layout_constraintStart_toStartOf="parent" 
        app:layout_constraintTop_toTopOf="parent" /> 
 
</android.support.constraint.ConstraintLayout> 

Run your application. You should see a map.

The TomTom map handles zooming, panning, rotating and double tapping gestures. In this application you need to add additional map interactions including location handling, long map click events, drawing routes and markers.

You can start adding event handlers to the map only after it is fully initialized. To test if the map is ready, the MainActivity class should implement an OnMapReadyCallback interface.

Then implement an OnMapLongClickListener interface to provide a callback method for long click events on the map.

Add a initTomTomServices method to part of the MainActivity class where Maps API modules are initialized. At the same time, add a initUIViews method and a setupUIViewListeners method where User Interface (UI) elements are initialized.

public class MainActivity extends AppCompatActivity implements OnMapReadyCallback, 
        TomtomMapCallback.OnMapLongClickListener { 
 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 
        initTomTomServices(); 
        initUIViews(); 
        setupUIViewListeners(); 
    } 
 
    @Override 
    public void onMapReady(@NonNull TomtomMap tomtomMap) {} 
 
    @Override 
    public void onMapLongClick(@NonNull LatLng latLng) {} 
 
    private void initTomTomServices() { 
        MapFragment mapFragment = (MapFragment) getSupportFragmentManager().findFragmentById(R.id.mapFragment); 
        mapFragment.getAsyncMap(this); 
    } 
 
    private void initUIViews() {} 
    private void setupUIViewListeners() {} 
} 

Add a private field for the TomTom map object inside the MainActivity class:

private TomtomMap tomtomMap;

and initialize it in the onMapReady callback.

@Override 
public void onMapReady(@NonNull final TomtomMap tomtomMap) { 
    this.tomtomMap = tomtomMap; 
    this.tomtomMap.setMyLocationEnabled(true); 
    this.tomtomMap.addOnMapLongClickListener(this); 
    this.tomtomMap.getMarkerSettings().setMarkersClustering(true); 
}     

Override the onRequestPermissionsResult method so that permission callbacks from activities are forwarded to the tomtomMap object.

@Override 
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 
    super.onRequestPermissionsResult(requestCode, permissions, grantResults); 
    this.tomtomMap.onRequestPermissionsResult(requestCode, permissions, grantResults); 
} 

After the onMapReady callback is executed from the Maps SDK, the tomtomMap object is ready to be used. Now you can register your own map event listeners.

Drawing a route on the map

Drawing a route on the map requires that the following additional private fields be defined in the MainActivity class:

  • RoutingApi
  • SearchApi (here used for reverse geocoding of a map position to a valid address)
  • Calculated route
  • Departure coordinates
  • Destination coordinates
  • Waypoint coordinates
private SearchApi searchApi; 
private RoutingApi routingApi; 
private Route route; 
private LatLng departurePosition; 
private LatLng destinationPosition; 
private LatLng wayPointPosition; 
private Icon departureIcon; 
private Icon destinationIcon; 

Then initialize the TomTom Search and Routing services by adding the following to the initTomTomServices method:

searchApi = OnlineSearchApi.create(this); 
routingApi = OnlineRoutingApi.create(this); 

Initialize icons for departure and destination positions inside the initUIViews method.

departureIcon = Icon.Factory.fromResources(MainActivity.this, R.drawable.ic_map_route_departure); 
destinationIcon = Icon.Factory.fromResources(MainActivity.this, R.drawable.ic_map_route_destination); 

You need a clearMap function, where all the markers and the route are removed from the map.

private void clearMap() { 
    tomtomMap.clear(); 
    departurePosition = null; 
    destinationPosition = null; 
    route = null;  
} 

Update the res/values/strings.xml file by adding strings:

<string name="geocode_no_results">No geocoder results. Choose different location and try again.</string> 
<string name="api_response_error">API response error: \'%1$s\'.</string> 

Now you can add an implementation to the function that handles long click events on the map.

@Override 
public void onMapLongClick(@NonNull LatLng latLng) { 
    if (isDeparturePositionSet() && isDestinationPositionSet()) { 
        clearMap(); 
    } else { 
        handleLongClick(latLng); 
    } 
} 
 
private void handleLongClick(@NonNull LatLng latLng) { 
    searchApi.reverseGeocoding(new ReverseGeocoderSearchQueryBuilder(latLng.getLatitude(), latLng.getLongitude())) 
            .subscribeOn(Schedulers.io()) 
            .observeOn(AndroidSchedulers.mainThread()) 
            .subscribe(new DisposableSingleObserver<ReverseGeocoderSearchResponse>() {
                @Override 
                public void onSuccess(ReverseGeocoderSearchResponse response) { 
                    processResponse(response); 
                } 
 
                @Override 
                public void onError(Throwable e) { 
                    handleApiError(e); 
                } 
 
                private void processResponse(ReverseGeocoderSearchResponse response) { 
                    if (response.hasResults()) { 
                        processFirstResult(response.getAddresses().get(0).getPosition()); 
                    } 
                    else { 
                        Toast.makeText(MainActivity.this, getString(R.string.geocode_no_results), Toast.LENGTH_SHORT).show(); 
                    } 
                } 
 
                private void processFirstResult(LatLng geocodedPosition) { 
                    if (!isDeparturePositionSet()) { 
                        setAndDisplayDeparturePosition(geocodedPosition); 
                    } else { 
                        destinationPosition = geocodedPosition; 
                        tomtomMap.removeMarkers(); 
                        drawRoute(departurePosition, destinationPosition);  
                    } 
                } 
 
                private void setAndDisplayDeparturePosition(LatLng geocodedPosition) { 
                    departurePosition = geocodedPosition; 
                    createMarkerIfNotPresent(departurePosition, departureIcon); 
                } 
            }); 
} 
 
private boolean isDestinationPositionSet() { 
    return destinationPosition != null; 
} 
 
private boolean isDeparturePositionSet() { 
    return departurePosition != null; 
} 
 
private void handleApiError(Throwable e) { 
    Toast.makeText(MainActivity.this, getString(R.string.api_response_error, e.getLocalizedMessage()), Toast.LENGTH_LONG).show(); 
} 

The handleLongClick function calls the searchApi's reverseGeocoding method. This method checks if a road can be found in the place where a long click is registered on the map.

Method reverseGeocoding returns a RxJava Single<ReverseGeocoderSearchResponse> object, subscribe on this object with DisposableSingleObserver<ReverseGeocoderSearchResponse>. When the Single is finished, it emits either a single successful value or an error. If a successful value is emitted, a method onSuccess is executed in the subscribing DisposableSingleObserver, otherwise an onError method is executed.

If the reverse geocoding call is successful, the effect of the LongClick depends on context:

  • The first long click on the map sets the departurePosition object.
  • A second long click sets the destinationPosition object and draws a route on the map.
  • A third long click removes any destination and departure markers and the route from the map.

Next add functions to send a route request to the Routing API and to draw a route from the response on the map.

private RouteQuery createRouteQuery(LatLng start, LatLng stop, LatLng[] wayPoints) { 
    return (wayPoints != null) ? 
            new RouteQueryBuilder(start, stop).withWayPoints(wayPoints).withRouteType(RouteType.FASTEST) : 
            new RouteQueryBuilder(start, stop).withRouteType(RouteType.FASTEST); 
} 
 
private void drawRoute(LatLng start, LatLng stop) { 
    wayPointPosition = null; 
    drawRouteWithWayPoints(start, stop, null); 
} 
 
private void drawRouteWithWayPoints(LatLng start, LatLng stop, LatLng[] wayPoints) { 
    RouteQuery routeQuery = createRouteQuery(start, stop, wayPoints);  
    routingApi.planRoute(routeQuery) 
            .subscribeOn(Schedulers.io()) 
            .observeOn(AndroidSchedulers.mainThread()) 
            .subscribe(new DisposableSingleObserver<RouteResult>() { 
 
                @Override 
                public void onSuccess(RouteResult routeResult) { 
                    displayRoutes(routeResult.getRoutes()); 
                    tomtomMap.displayRoutesOverview(); 
                } 
 
                private void displayRoutes(List<FullRoute> routes) { 
                    for (FullRoute fullRoute : routes) { 
                        route = tomtomMap.addRoute(new RouteBuilder( 
                                fullRoute.getCoordinates()).startIcon(departureIcon).endIcon(destinationIcon).isActive(true)); 
                    } 
                } 
 
                @Override 
                public void onError(Throwable e) { 
                    handleApiError(e); 
                    clearMap(); 
                } 
            }); 
}

The createRouteQuery method returns a routeQuery object:

  • With additional waypoints (if the wayPoints array is not equal to null).
  • Without additional waypoints (if the wayPoints array is equal to null).

The drawRouteWithWaypoints method calls the Routing API. If the response is successful, a route is drawn on the map. If the API returns an error, a message is displayed on the screen.

Add a createMarkerIfNotPresent method to display a departure position marker if the destination position is not set:

private void createMarkerIfNotPresent(LatLng position, Icon icon) { 
    Optional optionalMarker = tomtomMap.findMarkerByPosition(position); 
    if (!optionalMarker.isPresent()) { 
        tomtomMap.addMarker(new MarkerBuilder(position) 
                .icon(icon)); 
    } 
}

Now you can draw the route on the map by using long clicks in chosen locations.

Searching for POIs along the route

Add strings to res/values/strings.xml

<string name="poi_name_key">poiNamFe</string> 
<string name="address_key">address</string> 
<string name="poisearch_hint">Kind of place to add to your route</string> 
<string name="no_search_results">No search results for \'%1$s\'. Please try another search term.</string> 
<string name="add_to_my_route">Add to my route</string>

Add dimensions to res/values/dimens.xml

<dimen name="layout_height_xlarge">60dp</dimen> 
<dimen name="layout_height_xxlarge">80dp</dimen> 
<dimen name="spacing_xtiny">5dp</dimen> 
<dimen name="spacing_xsmall">8dp</dimen> 
<dimen name="spacing_small">10dp</dimen> 
<dimen name="text_size_small">12sp</dimen> 
<dimen name="text_size_normal">14sp</dimen> 

Add colors to res/values/colors.xml

<color name="bg_balloon_button_color">#C3D552</color> 

Use the searchApi object that was created earlier to search for a POI to add to the existing route. Modify the activity_main.xml layout file by adding a search field and its button.

<?xml version="1.0" encoding="utf-8"?> 
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:app="http://schemas.android.com/apk/res-auto" 
    xmlns:tools="http://schemas.android.com/tools" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    tools:context=".MainActivity"> 
 
    <fragment 
        android:id="@+id/mapFragment" 
        android:name="com.tomtom.online.sdk.map.MapFragment" 
        android:layout_width="match_parent" 
        android:layout_height="@dimen/size_none" 
        app:layout_constraintBottom_toTopOf="@+id/layout_edittext" 
        app:layout_constraintEnd_toEndOf="parent" 
        app:layout_constraintStart_toStartOf="parent" 
        app:layout_constraintTop_toTopOf="parent" /> 
 
    <android.support.constraint.ConstraintLayout 
        android:id="@+id/layout_edittext" 
        android:layout_width="@dimen/size_none" 
        android:layout_height="@dimen/layout_height_xlarge" 
        android:background="@color/white_fully_opaque" 
        android:paddingBottom="@dimen/spacing_small" 
        app:layout_constraintBottom_toBottomOf="parent" 
        app:layout_constraintEnd_toEndOf="parent" 
        app:layout_constraintStart_toStartOf="parent"> 
 
        <EditText 
            android:id="@+id/edittext_main_poisearch" 
            android:layout_width="@dimen/size_none" 
            android:layout_height="@dimen/size_none" 
            android:layout_marginStart="@dimen/spacing_small" 
            android:background="@android:color/transparent" 
            android:hint="@string/poisearch_hint" 
            android:imeOptions="actionSearch" 
            android:textSize="@dimen/text_size_normal" 
            app:layout_constraintBottom_toBottomOf="parent" 
            app:layout_constraintEnd_toStartOf="@+id/btn_main_poisearch" 
            app:layout_constraintStart_toStartOf="parent" 
            app:layout_constraintTop_toTopOf="parent" 
            android:inputType="text" /> 
 
        <ImageButton 
            android:id="@+id/btn_main_poisearch" 
            android:layout_width="wrap_content" 
            android:layout_height="@dimen/size_none" 
            android:layout_marginEnd="@dimen/spacing_small" 
            android:layout_marginTop="@dimen/spacing_xtiny" 
            android:adjustViewBounds="true" 
            android:background="@android:color/transparent" 
            app:layout_constraintEnd_toEndOf="parent" 
            app:layout_constraintTop_toTopOf="parent" 
            app:srcCompat="@android:drawable/ic_menu_search" /> 
    </android.support.constraint.ConstraintLayout> 
</android.support.constraint.ConstraintLayout> 

Then add behaviors to the newly created controls. Add private fields for the search button and text field in the MainActivity class

private ImageButton btnSearch; 
private EditText editTextPois; 

and initialize them in the initUIViews method.

btnSearch = findViewById(R.id.btn_main_poisearch); 
editTextPois = findViewById(R.id.edittext_main_poisearch);  

After initializing the search button, add a listener inside the setupUIViewListeners method to receive events when the button is clicked.

View.OnClickListener searchButtonListener = getSearchButtonListener(); 
btnSearch.setOnClickListener(searchButtonListener); 

Add a method to create a search button listener.

@NonNull 
private View.OnClickListener getSearchButtonListener() { 
    return new View.OnClickListener() { 
        @Override 
        public void onClick(View v) { 
            handleSearchClick(v); 
        } 
 
        private void handleSearchClick(View v) { 
            if (isRouteSet()) { 
                Optional<CharSequence> description = Optional.fromNullable(v.getContentDescription()); 
 
                if (description.isPresent()) { 
                    editTextPois.setText(description.get()); 
                    v.setSelected(true); 
                } 
                if (isWayPointPositionSet()) { 
                    tomtomMap.clear(); 
                    drawRoute(departurePosition, destinationPosition); 
                } 
                String textToSearch = editTextPois.getText().toString(); 
                if (!textToSearch.isEmpty()) { 
                    tomtomMap.removeMarkers(); 
                    searchAlongTheRoute(route, textToSearch); 
                } 
            } 
        } 
 
        private boolean isRouteSet() { 
            return route != null; 
        } 
 
        private boolean isWayPointPositionSet() { 
            return wayPointPosition != null; 
        } 
        private void searchAlongTheRoute(Route route, final String textToSearch) { 
            final Integer MAX_DETOUR_TIME = 1000; 
            final Integer QUERY_LIMIT = 10; 
 
            searchApi.alongRouteSearch(new AlongRouteSearchQueryBuilder(textToSearch, route.getCoordinates(), MAX_DETOUR_TIME).withLimit(QUERY_LIMIT)) 
                    .subscribeOn(Schedulers.io()) 
                    .observeOn(AndroidSchedulers.mainThread()) 
                    .subscribe(new DisposableSingleObserver<AlongRouteSearchResponse>() { 
                        @Override 
                        public void onSuccess(AlongRouteSearchResponse response) { 
                            displaySearchResults(response.getResults()); 
                        } 
 
                        private void displaySearchResults(List<AlongRouteSearchResult> results) { 
                            if (!results.isEmpty()) { 
                                for (AlongRouteSearchResult result : results) { 
                                    createAndDisplayCustomMarker(result.getPosition(), result); 
                                } 
                                tomtomMap.zoomToAllMarkers(); 
                            } else { 
                                Toast.makeText(MainActivity.this, String.format(getString(R.string.no_search_results), textToSearch), Toast.LENGTH_LONG).show(); 
                            } 
                        } 
 
                        private void createAndDisplayCustomMarker(LatLng position, AlongRouteSearchResult result) { 
                            String address = result.getAddress().getFreeformAddress(); 
                            String poiName = result.getPoi().getName(); 
 
                            BaseMarkerBalloon markerBalloonData = new BaseMarkerBalloon(); 
                            markerBalloonData.addProperty(getString(R.string.poi_name_key), poiName); 
                            markerBalloonData.addProperty(getString(R.string.address_key), address); 
 
                            MarkerBuilder markerBuilder = new MarkerBuilder(position) 
                                    .markerBalloon(markerBalloonData) 
                                    .shouldCluster(true); 
                            tomtomMap.addMarker(markerBuilder); 
                        } 
 
                        @Override 
                        public void onError(Throwable e) { 
                            handleApiError(e); 
                        } 
                    }); 
        } 
    }; 
}

The getSearchButtonListener method creates a View.OnClickListener object. The searchAlongTheRoute function in this object executes a search query for the provided search term along the displayed route. The createAndDisplayCustomMarker method then adds a map marker in the position returned by the search query, including the name and address of the POI.

Adding custom marker balloons

Add a file with a rounded button shape named bg_balloon_button.xml to the res/drawables folder. This adds the styling to the buttons in the marker balloons.

<?xml version="1.0" encoding="utf-8"?> 
<shape xmlns:android="http://schemas.android.com/apk/res/android" 
    android:shape="rectangle" android:padding="10dp" > 
    <solid android:color="@color/bg_balloon_button_color" /> 
    <corners android:radius="20dp" /> 
</shape> 

To create a custom layout, add a file named marker_custom_balloon.xml inside the res/layout directory.

<?xml version="1.0" encoding="utf-8"?> 
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:app="http://schemas.android.com/apk/res-auto" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:background="@android:color/white"> 
 
    <Button 
        android:id="@+id/btn_balloon_waypoint" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:layout_marginBottom="@dimen/spacing_small" 
        android:layout_marginEnd="@dimen/spacing_small" 
        android:layout_marginStart="@dimen/spacing_small" 
        android:layout_marginTop="@dimen/spacing_small" 
        android:background="@drawable/bg_balloon_button" 
        android:paddingBottom="@dimen/spacing_xtiny" 
        android:paddingLeft="@dimen/spacing_small" 
        android:paddingRight="@dimen/spacing_small" 
        android:paddingTop="@dimen/spacing_xtiny" 
        android:text="@string/add_to_my_route" 
        android:textAlignment="center" 
        android:textSize="@dimen/text_size_small" 
        android:textStyle="bold" 
        app:layout_constraintBottom_toBottomOf="parent" 
        app:layout_constraintEnd_toEndOf="parent" 
        app:layout_constraintStart_toStartOf="parent" 
        app:layout_constraintTop_toBottomOf="@+id/textview_balloon_poiaddress" /> 
 
    <TextView 
        android:id="@+id/textview_balloon_poiname" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:layout_marginEnd="@dimen/spacing_xsmall" 
        android:layout_marginStart="@dimen/spacing_xsmall" 
        android:layout_marginTop="@dimen/spacing_xsmall" 
        android:textStyle="bold" 
        app:layout_constraintEnd_toEndOf="parent" 
        app:layout_constraintStart_toStartOf="parent" 
        app:layout_constraintTop_toTopOf="parent" /> 
 
    <TextView 
        android:id="@+id/textview_balloon_poiaddress" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:layout_marginEnd="@dimen/spacing_xsmall" 
        android:layout_marginStart="@dimen/spacing_xsmall" 
        app:layout_constraintEnd_toEndOf="parent" 
        app:layout_constraintStart_toStartOf="parent" 
        app:layout_constraintTop_toBottomOf="@id/textview_balloon_poiname" /> 
</android.support.constraint.ConstraintLayout> 

The marker_custom_balloon layout displays a balloon above a map marker that the user has clicked. This balloon has two text fields to display an address and a POI name are displayed, plus a button to add the POI to the existing route.

Finally, add a method createCustomViewAdapter that returns a custom SingleLayoutBalloonViewAdapter adapter object.

Use the marker_custom_balloon layout as an argument for the adapter object. Then override the onBindView method inside the adapter to fill in the marker balloon layout fields. Then implement a btnAddWayPoint onClick event inside the adapter. This events executes a setWayPoint method and recalculates the route to include the marker that the user has clicked.

private SingleLayoutBalloonViewAdapter createCustomViewAdapter() { 
    return new SingleLayoutBalloonViewAdapter(R.layout.marker_custom_balloon) { 
        @Override 
        public void onBindView(View view, final Marker marker, BaseMarkerBalloon baseMarkerBalloon) { 
            Button btnAddWayPoint = view.findViewById(R.id.btn_balloon_waypoint); 
            TextView textViewPoiName = view.findViewById(R.id.textview_balloon_poiname); 
            TextView textViewPoiAddress = view.findViewById(R.id.textview_balloon_poiaddress); 
            textViewPoiName.setText(baseMarkerBalloon.getStringProperty(getApplicationContext().getString(R.string.poi_name_key))); 
            textViewPoiAddress.setText(baseMarkerBalloon.getStringProperty(getApplicationContext().getString(R.string.address_key))); 
            btnAddWayPoint.setOnClickListener(new View.OnClickListener() { 
                @Override 
                public void onClick(View v) { 
                    setWayPoint(marker); 
                } 
 
                private void setWayPoint(Marker marker) { 
                    wayPointPosition = marker.getPosition(); 
                    tomtomMap.clearRoute(); 
                    drawRouteWithWayPoints(departurePosition, destinationPosition, new LatLng[] {wayPointPosition}); 
                    marker.deselect(); 
                } 
            }); 
        } 
      };
}

Use above method inside the onMapReady function to set a marker balloon view adapter.

this.tomtomMap.getMarkerSettings().setMarkerBalloonViewAdapter(createCustomViewAdapter()); 

Now you should have a fully working application where you can:

  • Display a map.
  • Create a route between 2 points.
  • Display points of interest.
  • Add a single POI to your route.

The additional styling, shortcut buttons, and help screen in the application screenshots are not a part of this tutorial. You can find them, along with all the icons and images used in this tutorial, in the application posted on github.

Summary

This tutorial explained how to create a sample application that searches for and displays points of interest along a route, then replans the route to include one of those POIs.

This application can be extended with other TomTom Maps SDK functions, such as displaying information about traffic and travel distances.

Happy coding!

Example application

The full application, including additional layout changes and improvements, is visible below. It uses a ConstraintLayout with a search field and a button for its main layout. At the bottom of the screen there are also three optional buttons that can be used for quick searches for gas stations, restaurants, and ATMs. There is a help button in the top right corner along with a clear button to remove the route and any markers from the map.