Geocodes -- which mean GPS coordinates -- are great if you’re a computer and can easily calculate exactly where they map to. If you’re a humble human being, however, working with geocodes is tough, because humans don’t think or talk about locations in terms of geocodes. Instead, we use street names, addresses and landmarks to determine where things are.
This is why reverse geocoding is such an important feature for bridging the gap between computers and humans. Reverse geocoding allows applications to convert geocodes into street other human-readable location information. Reverse geocoding thus makes it possible to use the precision of GPS and geocodes for tracking locations within an application, while still presenting information to human users in ways that humans can easily interpret.
The tricky thing about reverse geocoding, however, is that there has not traditionally been a simple way to leverage it within an application. Unlike, say, IP addresses, which you can easily translate into domain names using a DNS service, there is no standardized service for translating geocodes into human-readable location information.
Fortunately, TomTom has made things much easier for developers in this respect by adding reverse geocoding to its SDKs. The feature makes it quite easy to implement reverse geocoding into an app using simple API calls.
To provide a sense of how reverse geocoding works and how developers can easily take advantage of it with the help of TomTom’s SDKs, this article walks through a scenario involving the display of location data in an Android mobile app. We’ll start by discussing what you need to be able to use the SDK; then we’ll create our app to determine the location of the device, find out more information about the location using the TomTom SDK, and then display the results on the screen.
In this article, we’re going to explore the capabilities available within the new TomTom SDKs. The example we’ll be building in this article uses the TomTom SDK for Android. The same functionality is available with the Web and iOS SDKs. You can also use the TomTom APIs directly from your applications. You can follow links to the IOS and Web SDKs below.
The TomTom SDKs
The TomTom Developer Portal is where you’ll find everything you need to get up and running with the TomTom APIs. The first thing you’ll want to do is create an account on the portal. From the home page, enter your email address and click on the “Get a free API key” button.
Fig. 1 The TomTom Developer Portal
The next step in the process is to select a username and read through the terms and conditions. Your free account supports up to 2,500 free transactions per day. If 2,500 API transactions per day aren’t enough, you can purchase more transactions by visiting the My Credits screen in the developer dashboard.
An email contains a link to set the password for your account, and with that, you’ll be ready to go. You need to configure an application before you can request an API Key. From your dashboard, click on the “+ Add an App” button.
Applications require a name, and you’ll need to enable the products which the application needs to access. For this example, the product we’ll be using is the Search API product. If you’re following along, select the Search API product and click on Create App.
Fig. 2 Creating a New Application
You can receive a unique API key by visiting the TomTom Developer Portal, and entering your email address for a “Free API Key.” You’ll need to read and agree to the terms and conditions and select a username. An email prompts you to set a password, and from there, you can navigate to your dashboard and create a new application.
Applications are approved quickly and appear on your dashboard. The entry shows the Consumer API Key and information about your application, including the approval status of the application, and each of the products it uses.
Fig. 3 Application Details, Including the API Key
We’ll need this API as we add the TomTom SDK to our project and use it to get detailed information about our location.
Setting Up Our Project
I used Android Studio to create this project and began with a blank activity screen. Opening up activity_main.xml, I added a button (btnGetInformation
) to trigger the information request, and then a group of text fields below to display information about the location. We’ll include fields for:
- The Street Address -
txtStreet
- City -
txtCity
- Zip Code -
txtZipCode
- State -
txtState
Fig. 4 Layout for Our App
Now let’s open MainActivity.java, connect to each of these controls, and we can add an EventListener
to the button, and connect to the device’s Location Manager.
public class MainActivity extends AppCompatActivity {
private Button btnInformation;
private TextView txtStreet;
private TextView txtCity;
private TextView txtState;
private TextView txtZipCode;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnInformation = findViewById(R.id.btnGetInformation);
txtStreet = findViewById(R.id.txtStreet);
txtCity = findViewById(R.id.txtCity);
txtZipCode = findViewById(R.id.txtZipCode);
txtState = findViewById(R.id.txtState);
}
}
Fig. 5 Initializing the Controls for the Main Activity (MainActivity.java)
Capturing Location Information from the Device
We’re going to set our application to get location updates from the GPS Location Service on the device. Before we can do this, we need to add a uses-permission
entry in the AndroidManifest.xml file.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Fig. 6 Adding Permissions for the Location Service
Next, we’ll add a LocationManager
and a LocationListener
to the MainActivity.java file.
private LocationManager locationManager;
private LocationListener locationListener;
Now, we’ll initialize them inside the onCreate
method. The LocationListener
has four methods we need to implement. We’ll just be implementing the onLocationChanged
method later in this project.
locationManager = (LocationManager) this.getSystemService(LOCATION_SERVICE);
locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras){
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
};
Fig. 7 Implementing the LocationManager and LocationListener
Now we need to enable the LocationManager
and ensure that the user has granted permissions for us to access the Location Service. We’ll check for permissions in the onCreate
method, and we’ll also add the check to the overridden onRequestPermissionResult
method. The command that we execute to start receiving updates is:
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
5000,
1,
locationListener
);
The parameters specify:
- The source of the location (GPS provider in our case)
- The minimum time between updates (we selected 5000 milliseconds or 5 seconds)
- The minimum distance between updates (we selected 1 meter)
- The LocationListener object.
We’ll check for permissions in onCreate
:
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
1
);
} else {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
5000,
1,
locationListener
);
}
Fig. 8 Checking Permissions in the onCreate Method
We’ll also add the permissions check to the onRequestPermissionsResult
method.
@Override
public void onRequestPermissionsResult(
int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(
requestCode,
permissions,
grantResults
);
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED) {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
5000,
1,
locationListener
);
}
}
}
Fig. 9 Checking Permissions in the onRequestPermissionsResult Method
So, now whether our user has already granted permissions for the app to use the location of the device, or they give permission when opening the app, the app can now access the location. Let’s add one more thing before we continue. We’ll initialize a variable called currentLocation
of type Location, and we’ll set this in the onLocationChanged
method of the LocationListener
.
private Location currentLocation;
We’ll set currentLocation whenever the Location Service broadcasts a change.
@Override
public void onLocationChanged(Location location) {
currentLocation = location;
}
Fig. 10 Setting the currentLocation property inside the LocationListener
Let’s add the TomTom SDK so we can take the current location and request more information about it to complete our app.
Adding TomTom Support
We need to add the TomTom SDK as a dependency of our project. The first step is to add the TomTom repository to our build.gradle file. We want the Gradle build file for the project, and we need to locate the allprojects
object within the file. Add the TomTom repository to this object.
allprojects {
repositories {
google()
jcenter()
maven {
url "https://maven.tomtom.com:8443/nexus/content/repositories/releases/"
}
}
}
Fig. 11 Adding the TomTom Repository
Now that we can access the repository, open the Gradle build file for the app, and add the following dependencies to the dependencies object.
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.tomtom.online:sdk-search: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'
}
Fig. 12 Adding the TomTom Search SDK
You may need to refresh the Gradle libraries for your project first, but these changes add TomTom search functionality to your project. The final step is to authorize API access with the API Key we created through the TomTom Developer Portal.
Open the AndroidManifest.xml file, and add the following meta-data
object after the last activity object and before the application object is closed. Ensure that you replace ReplaceWithAPIKey
with your API Key.
<meta-data
android:name="OnlineSearch.Key"
android:value="ReplaceWithAPIKey" />
Fig. 13 Adding the API Key for TomTom Search SDK
Reverse Geocoding the Location
The first thing we’re going to do is implement a couple of methods to configure the API and allow building a query. Add the following functions to the MainActivity.java
file. We’ll pass in the Application Context, which is how the API is configured with our API Key.
protected SearchApi createSearchAPI() {
SearchApi searchApi = OnlineSearchApi.create(getApplicationContext());
return searchApi;
}
Fig. 14 Function to Initialize the SearchAPI
The next function constructs a search query for us. We accept the latitude and longitude values and return a query object.
protected ReverseGeocoderSearchQuery createReverseGeocoderQuery(
double latitude,
double longitude) {
return ReverseGeocoderSearchQueryBuilder
.create(latitude, longitude)
.build();
}
Fig. 15 Function to Build our ReverseGeocoderQuery Object
Finally, let’s build our reverseGeocode
function, which configures the connection to the API, builds the query, and then handles the results.
protected void reverseGeocode(
final double latitude,
final double longitude) {
SearchApi searchAPI = createSearchAPI();
ReverseGeocoderSearchQuery reverseGeocoderQuery =
createReverseGeocoderQuery(latitude, longitude);
searchAPI.reverseGeocoding(
reverseGeocoderQuery, new RevGeoSearchResultListener() {
@Override
public void onSearchResult(ReverseGeocoderSearchResponse response) {
List<ReverseGeocoderFullAddress> addresses =
response.getAddresses();
if (addresses.size() > 0) {
Address address = addresses.get(0).getAddress();
txtStreet.setText(address.getStreetNumber() + ' ' + address.getStreetName());
txtCity.setText(address.getMunicipality());
txtState.setText(address.getCountrySubdivision());
txtZipCode.setText(address.getPostalCode());
}
}
@Override
public void onSearchError(SearchError error) {
Log.d("Address: ", getApplicationContext().getString(R.string.reverse_geocoding_error));
}
});
}
Fig. 15 Function to Build our ReverseGeocoderQuery Object
The last thing we need to do is connect the button to run the reverseGeocode
function, using the currentLocation
. Add the following to the end of the onCreate
function.
btnGetInformation.setOnClickListener(v ->
reverseGeocode(currentLocation.getLatitude(),
currentLocation.getLongitude()));
Fig. 16 Adding a Click Listener to the Button
Running the Application
You can run your app in an emulator, or install it on an Android device. I tested mine with a Pixel 2 Emulator running version 28 of the Android API. I manipulated the coordinates to 45.512046, -122.627833, and then clicked on the GET LOCATION INFORMATION button.
Fig. 17 The Completed App in Action
Additional Information
If you would like to learn more about options available within the TomTom APIs, you can access the online documentation or use the API Explorer to try different parameters and see how they all work together.
If you would like to download the code for this example, you can find it here. You’ll need to add your API key in the AndroidManifest file before you can run the application.