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

How To Schedule Routing for Truck Drivers 

Learn How To Add a Route Scheduler Using Fuzzy Search and Geocoding

When the United States Air Force developed the Global Positioning System (GPS), its objective was to enable someone with a GPS receiver to receive geolocation and time information anywhere on or near the earth. It’s obvious how GPS could be useful in a military context. Once GPS technology became available to the public at large, companies like TomTom began leveraging it to do many other useful things, including developing complex consumer applications. And thanks to TomTom’s recent decision to release APIs to developers, integrating GPS technology into complex consumer applications is easier than ever.

TomTom’s APIs provide developers with simple access to powerful mapping and geolocation solutions. The solutions are the result of years of engineering innovation by TomTom, and decades of information about traffic patterns and trends.

To demonstrate how powerful TomTom’s APIs are, this article shows how to use the TomTom Android SDK to build an application for truck drivers. Specifically, we’ll use the SDK to determine the best route to the destination, and we’ll tell the driver when they need to leave to get there on time.

(Note: that this article focuses on the Android SDK, but SDKs are also available for the Web and IOS. Also note that if you prefer, you can connect to the APIs directly from your application, rather than using the SDKs.)

Getting Started

Before we get started you will need to log in to the TomTom Developer portal (or register for a new account). You need to configure an application before you can request an API Key. From your dashboard, click on the “+ Add a new App”button. Applications require a name, and you’ll need to enable the APIs that the application needs to access. For this example, the products we’ll be using are the Search API, the Routing API, and the Maps API. If you’re following along, select the Search API, Routing API, and Maps API products and click on Create App. Additionally, we’ll need the Consumer API Key when we configure the TomTom dependencies in our application.

Setting Up your project

I used Android Studio to build this application and began with a blank activity screen. Our object is to build a simple application to demonstrate the following concepts:

  • Address completion with a “fuzzy” search (meaning a search whose results are not limited to the specific keywords or phrases used in the search query, but also extend to related terms)
  • Determining the current location of the device 
  • Finding the best route

I’ll include links at the end with more information on each component and a more complex example which you can download, and explore this functionality more comprehensively.

What we’ll do first is create input fields for the driver to enter their destination and arrival time. Once we have that information, we’ll find coordinates of the destination, and determine the best route. We’ll display the time the driver needs to leave and display the route.

The <fragment> at the bottom of the screen has an android:nameof com.tomtom.online.sdk.map.MapFragment.

permissions, access and build configuration

Before we start coding our solution, we need to take care of some administrative tasks. This includes adding the TomTom SDKs as dependencies and setting up device permissions. We’ll start by adding the TomTom repository to our project’s build.gradle file. Locate the allprojects object within the file and add the TomTom repository to this object.

Use the best free HTML tools: read the blog, use the online editor, find the tags.

    allprojects {
    repositories {
        google()
        jcenter()
        maven {
            url "https://maven.tomtom.com:8443/nexus/content/repositories/releases/"
        }
    }
}

Now that we can access the repository, open the app’s build.gradle file and add the following dependencies to the dependencies object. While you’re in this file, you’ll also want to make sure that the compileOptions set the Source and Target Compatibility to at least 1.8.

  
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.tomtom.online:sdk-maps:2.+'
    implementation 'com.tomtom.online:sdk-search:2.+'
    implementation 'com.tomtom.online:sdk-routing:2.+'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

Finally, you need to open the AndroidManifest.xml file and make the following additions. First, add user permission to allow the app access to the device location service.

  
uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"

Second, add the following meta-data objects to the application object. Ensure that you replace ReplaceWithAPIKey with your API Key.

  
meta-data
    android:name="OnlineSearch.Key"
    android:value="ReplaceWithAPIKey" />
meta-data
    android:name="OnlineMaps.Key"
    android:value="ReplaceWithAPIKey" />
meta-data
    android:name="OnlineRouting.Key"
    android:value="ReplaceWithAPIKey" />

Now that we have the housekeeping taken care of, it’s time to build out the solution.

Using a Fuzzy Search to Autocomplete the Address

The control for the Delivery Address is an AutoCompleteTextView Object. We can add a listener to this control, which can react to changes to the text, and suggest possible addresses using the SearchAPI. 

Let’s start by initializing some helper objects and a couple of constants that we’ll need to use. We’re also going to initialize the TomTom APIs we’ll be using in this example and create an initialization function for them.  

  
private AutoCompleteTextView txtAddress;

private List addressAutocompleteList;
private Map<String, LatLng> searchResultsMap;
private ArrayAdapter searchAdapter;
private Handler searchTimerHandler = new Handler();
private Runnable searchRunnable;

private SearchApi searchApi;
private RoutingApi routingApi;
private TomtomMap tomtomMap;

private static final int MIN_LEVEL_OF_FUZZINESS = 2;
private static final int MIN_AUTOCOMPLETE_CHARACTERS = 3;
private static final int AUTOCOMPLETE_SEARCH_DELAY_MILLIS = 600;

private void initTomTomAPIs() {
    searchApi = OnlineSearchApi.create(getApplicationContext());
    routingApi = OnlineRoutingApi.create(getApplicationContext());
    MapFragment mapFragment = (MapFragment) getSupportFragmentManager().findFragmentById(R.id.mapFragment);
    mapFragment.getAsyncMap(this);
}

Let’s look at the constants in more detail. Each of these values can be adjusted to tune how the autocomplete functionality works. 

  • MIN_LEVEL_OF_FUZZINESS - A fuzzy search finds exact and similar matches to the text we enter. Different levels of “fuzziness” affect the types of results you get. 
  • MIN_COMPLETE_CHARACTERS - We won’t start searching for matches until at least 3 characters have been entered. 
  • AUTOCOMPLETE_SEARCH_DELAY_MILLIS - We’ll wait 600ms after a user stops typing before we return results. This delay limits the number of calls to the API and provides a better user experience.  

We’ll implement the Autocomplete functionality in two steps. The first step is to configure the autocomplete functionality on the field, and the second step is to create the search function. The autocomplete functionality is boilerplate autocomplete code, and it calls addressAutoComplete, which we’ll explore next.  

  
public void configureAutocomplete(final AutoCompleteTextView autoCompleteTextView) {
    addressAutocompleteList = new ArrayList<>();
    searchResultsMap = new HashMap<>();
    searchAdapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, addressAutocompleteList);

    autoCompleteTextView.setAdapter(searchAdapter);
    autoCompleteTextView.addTextChangedListener(new BaseTextWatcher() {
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            if (searchTimerHandler != null) {
                searchTimerHandler.removeCallbacks(searchRunnable);
            }
        }

        @Override
        public void afterTextChanged(final Editable s) {
            if (s.length() > 0) {
                if (s.length() >= MIN_AUTOCOMPLETE_CHARACTERS) {
                    searchRunnable = () -> addressAutoComplete(s.toString());
                    searchAdapter.clear();
                    searchTimerHandler.postDelayed(searchRunnable, AUTOCOMPLETE_SEARCH_DELAY_MILLIS);
                }
            }
        }
    });
    autoCompleteTextView.setOnItemClickListener((parent, view, position, id) -> {
        String item = (String) parent.getItemAtPosition(position);
        if (autoCompleteTextView == txtAddress) {
            destination = searchResultsMap.get(item);
        } else if (autoCompleteTextView == txtAddress) {
            destination = searchResultsMap.get(item);
        }
        hideKeyboard(view);
    });
}

private void hideKeyboard(View view) {
    InputMethodManager in = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
    if (in != null) {
        in.hideSoftInputFromWindow(view.getApplicationWindowToken(), 0);
    }
}

private abstract class BaseTextWatcher implements TextWatcher {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
}

You’ll see that we added an ItemClickListener to the Autocomplete functionality. When a user clicks on the address they would like, the response from the API also includes the GPS coordinates. We save these in the destination parameter to use later. 

The addressAutoComplete function takes address text and an AutoCompleteTextView control, and performs a fuzzy search using the TomTom SearchAPI object. We’ll first create the object, using the ApplicationContext, which passes our API Keys from the manifest file. Then, we’ll build a SearchQuery, subscribe to the response, and when it returns, we’ll update the autocomplete list on the text control. 

  
public void addressAutoComplete(final String address, final AutoCompleteTextView autoCompleteTextView) {
    searchApi.search(new FuzzySearchQueryBuilder(address)
        .withLanguage(Locale.getDefault().toLanguageTag())
        .withTypeAhead(true)
        .withMinFuzzyLevel(MIN_LEVEL_OF_FUZZINESS).build()
    )
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new DisposableSingleObserver() {
        @Override
        public void onSuccess(FuzzySearchResponse fuzzySearchResponse) {
            if (!fuzzySearchResponse.getResults().isEmpty()) {
                addressAutocompleteList.clear();
                searchResultsMap.clear();
                for (FuzzySearchResult result : fuzzySearchResponse.getResults()) {
                    String addressString = result.getAddress().getFreeformAddress();
                    addressAutocompleteList.add(addressString);
                    searchResultsMap.put(addressString, result.getPosition());
                }
                searchAdapter.clear();
                searchAdapter.addAll(addressAutocompleteList);
                searchAdapter.getFilter().filter("");
            }
        }

        @Override
        public void onError(Throwable e) {
            Toast.makeText(MainActivity.this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
        }
    });
}

The final step is to add the autocomplete functionality to the text field. Inside the onCreate function, add the following lines. 

  
initTomTomAPIs();
initCurrentLocation();

txtAddress = findViewById(R.id.txtAddress);
configureAutocomplete(txtAddress);
txtAddress.setAdapter(searchAdapter)

 

We’ll create the initCurrentLocation function next.

Determining the Current Location

Now that we know where we’re going, we need to know our starting position. We’ll use the current location of the device, giving us a chance to explore the TomTom LocationSource object. Step one is to implement the LocationUpdateListener interface on the Java class. We’ll then create a LocationSource object, a currentLocation object to store the current location, and a constant to hold the location of the Location Service Permission. 

  
public class MainActivity extends AppCompatActivity implements LocationUpdateListener {

    private LocationSource locationSource;
    private LatLng currentLocation;

    private static final int PERMISSION_REQUEST_LOCATION = 0;

The LocationUpdateListener interface requires that we implement an onLocationChanged function. We’ll also create a function to initialize the LocationSource and handle the permissions check.

  
private void initCurrentLocation() {
    PermissionChecker permissionChecker = AndroidPermissionChecker.createLocationChecker(this);
    if(permissionChecker.ifNotAllPermissionGranted()) {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST_LOCATION);
    }
    LocationSourceFactory locationSourceFactory = new LocationSourceFactory();
    locationSource = locationSourceFactory.createDefaultLocationSource(this, this,  LocationRequest.create()
            .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
            .setFastestInterval(2000)
            .setInterval(5000));
    locationSource.activate();
}

@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case PERMISSION_REQUEST_LOCATION:
            if(grantResults.length >= 2 &&
                    grantResults[0] == PackageManager.PERMISSION_GRANTED &&
                    grantResults[1] == PackageManager.PERMISSION_GRANTED) {
                locationSource.activate();
            }
            else {
                Toast.makeText(this, "Location permissions not granted.", Toast.LENGTH_SHORT).show();
            }
            break;
    }
}

@Override
public void onLocationChanged(final Location location) {
    currentLocation = new LatLng(location);
}

The LocationSource object enables us access to the Location Service on the Android device. This service returns the GPS coordinates and can be configured to update the app as the location changes. If our app becomes too chatty, we can increase the interval between updates, and even deactivate the LocationSource object. Initialization of the LocationSource object depends on the user granting permission to the app to use the Location Service on the device. 

After the LocationSource object is activated, the currentLocation is updated with the current location. We can use this location, and the location from the destination address to determine a route. 

Finding the route

Now that we have our current location, our destination location, and the delivery time, we have everything we need to ask TomTom to create a route for us. We’ll start from the onCreate function and then build out the functions we need from there.  

  
txtDepartureTime = findViewById(R.id.txtTime);
btnScheduleDelivery = findViewById(R.id.btnScheduleDelivery);

btnScheduleDelivery.setOnClickListener(v -> {
    String time[] =  txtDepartureTime.getText().toString().split(":");
    LocalDateTime deliveryTime = LocalDate.now().atTime(new Integer(time[0]), new Integer(time[1]));
    requestRoute(currentLocation, destination, TravelMode.TRUCK, Date.from(deliveryTime.atZone(ZoneId.systemDefault()).toInstant()));
    hideKeyboard(v);
    tomtomMap.zoomToAllMarkers();
});

The first thing we do is get a reference to the text field with the delivery time and the Schedule Delivery button. We add a click listener on the button, which gets the text from the delivery time field and splits it. Ideally, we’d validate these values, but for the sake of brevity, we’ll assume that the input format is hour:minute. 

We call the requestRoute function with our current location, the destination, the travel mode, which we hardcode to Truck, and the delivery time.  The delivery time allows TomTom to compensate for typical traffic conditions based on the time of day. 

  
private void requestRoute(final LatLng departure, final LatLng destination, TravelMode byWhat, Date arriveAt) {
    RouteQuery routeQuery = new RouteQueryBuilder(departure, destination)
            .withRouteType(RouteType.FASTEST)
            .withConsiderTraffic(true)
            .withTravelMode(byWhat)
            .withArriveAt(arriveAt)
            .build();

    routingApi.planRoute(routeQuery)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new DisposableSingleObserver() {
                @Override
                public void onSuccess(RouteResponse routeResponse) {
                    if (routeResponse.hasResults()) {
                        FullRoute fullRoute = routeResponse.getRoutes().get(0);
                        int currentTravelTime = fullRoute.getSummary().getTravelTimeInSeconds();
                        LocalDateTime departureTime = arriveAt.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().minusSeconds(currentTravelTime);
                        Toast.makeText(getApplicationContext(), "Depart at " + departureTime.format(timeFormatter), Toast.LENGTH_LONG).show();
                        displayRouteOnMap(fullRoute.getCoordinates());
                    }
                }

                @Override
                public void onError(Throwable e) {
                    Toast.makeText(getApplicationContext(), "Error finding the route.", Toast.LENGTH_LONG).show();
                }

                private void displayRouteOnMap(List coordinates) {
                    RouteBuilder routeBuilder = new RouteBuilder(coordinates)
                            .isActive(true);
                    tomtomMap.clear();
                    tomtomMap.addRoute(routeBuilder);
                    tomtomMap.displayRoutesOverview();
                }
            });
}

The RouteAPI allows you to set different parameters. For our query, we’re looking for the fastest route, and we want consideration given to traffic at the time of day. Additional options available are the ability to select the most economical route, shortest route, and you can even submit fuel consumption information and receive an estimate for the amount of fuel required. 

Once the route is generated, we display it on the Map, and then we calculate the required departure time and display it to the user as a pop-up within the app.

Learning More and Moving Beyond This Example

This application provides a basic introduction to the APIs for TomTom Search API, Routing API and Maps API, as well as the Android SDK. If you would like to look at the source code in its entirety, you can download it from GitHub. 

TomTom provides a more extensive example which you can learn more about here 

For more information about the APIs used in this example, including additional options and capabilities, you can review the documentation through the links below: 

First published: 
Saturday, May 11, 2019 - 05:56
Last edited: 
Friday, August 23, 2019 - 00:39