How to determine if lat long coordinates are on a road with Google Maps API v2
Categories:
Determining if Coordinates are on a Road with Google Maps API v2 for Android

Learn how to leverage the Google Maps Android API v2 and Google Roads API to accurately determine if a given latitude and longitude coordinate falls on a road segment, and how to snap off-road points to the nearest road.
When developing location-aware Android applications, a common requirement is to verify if a user's reported location (latitude and longitude) is actually on a road. This is particularly useful for navigation apps, ride-sharing services, or any application where road-based accuracy is crucial. The Google Maps Android API v2 provides the mapping interface, but for road-specific checks, you'll need to integrate with the Google Roads API.
Understanding the Google Roads API
The Google Roads API offers several powerful features, but for determining if a coordinate is on a road, the snapToRoads
endpoint is most relevant. While its primary function is to 'snap' a series of GPS coordinates to the most likely road a vehicle was traveling on, it can also be used to check individual points. If a single coordinate is sent to snapToRoads
, the API will return the nearest road segment if one is found within a reasonable distance. If no road is found nearby, the original coordinate might be returned, or no result for that point will be provided, indicating it's likely not on a road.
sequenceDiagram participant App as Android App participant GoogleMapsAPI as Google Maps Android API v2 participant RoadsAPI as Google Roads API App->>GoogleMapsAPI: Get current Lat/Long GoogleMapsAPI-->>App: Lat, Long App->>RoadsAPI: Request snapToRoads(Lat, Long) RoadsAPI-->>App: Snapped Lat, Long (if on road) OR Original Lat, Long / No result alt Snapped coordinates received App->>App: Coordinate is on a road else No snapped coordinates / Original coordinates App->>App: Coordinate is NOT on a road (or too far off) end
Sequence diagram for checking if a coordinate is on a road using the Google Roads API.
Prerequisites and Setup
Before you can use the Google Roads API, ensure you have the following:
- Google Cloud Project: A project with billing enabled.
- APIs Enabled: Enable both the 'Google Maps Android API' and the 'Roads API' in your Google Cloud Console.
- API Key: Obtain an API key and restrict it appropriately for security. For Android apps, restrict by Android app (package name and SHA-1 certificate fingerprint).
- Gradle Dependencies: Include the Google Play Services Maps library in your
build.gradle
file.
dependencies {
implementation 'com.google.android.gms:play-services-maps:18.2.0'
// No direct Roads API client library for Android, use HTTP client
}
Add Google Play Services Maps dependency to your build.gradle
.
Implementing the Road Check
To check if a coordinate is on a road, you'll perform an HTTP GET request to the snapToRoads
endpoint. The key parameters are path
(your latitude,longitude pair) and your key
(API key).
The API response will be a JSON object. If a road is found, the snappedPoints
array will contain objects with location
(the snapped lat/long) and originalIndex
(referencing your input point). If no road is found for a point, it might be omitted from snappedPoints
or the original coordinate might be returned if interpolate=true
is used with multiple points, but for a single point, the absence of a snapped point is the key indicator.
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class RoadChecker {
private static final String TAG = "RoadChecker";
private static final String ROADS_API_BASE_URL = "https://roads.googleapis.com/v1/snapToRoads?";
private static final String API_KEY = "YOUR_API_KEY"; // Replace with your actual API key
public interface RoadCheckListener {
void onRoadCheckResult(boolean isOnRoad, double snappedLat, double snappedLon);
void onRoadCheckError(String errorMessage);
}
public void checkCoordinateOnRoad(double latitude, double longitude, RoadCheckListener listener) {
new CheckRoadTask(listener).execute(latitude, longitude);
}
private class CheckRoadTask extends AsyncTask<Double, Void, RoadCheckResult> {
private RoadCheckListener listener;
public CheckRoadTask(RoadCheckListener listener) {
this.listener = listener;
}
@Override
protected RoadCheckResult doInBackground(Double... params) {
double lat = params[0];
double lon = params[1];
String urlString = ROADS_API_BASE_URL + "path=" + lat + "," + lon + "&key=" + API_KEY;
Log.d(TAG, "Roads API URL: " + urlString);
try {
URL url = new URL(urlString);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
int responseCode = urlConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
JSONObject jsonResponse = new JSONObject(response.toString());
JSONArray snappedPoints = jsonResponse.optJSONArray("snappedPoints");
if (snappedPoints != null && snappedPoints.length() > 0) {
// For a single point, if snappedPoints is not empty, it means it's on a road
JSONObject snappedPoint = snappedPoints.getJSONObject(0);
JSONObject location = snappedPoint.getJSONObject("location");
double snappedLat = location.getDouble("latitude");
double snappedLon = location.getDouble("longitude");
return new RoadCheckResult(true, snappedLat, snappedLon);
} else {
// No snapped points, likely not on a road or too far off
return new RoadCheckResult(false, lat, lon); // Return original if not snapped
}
} else {
Log.e(TAG, "HTTP Error: " + responseCode + ", " + urlConnection.getResponseMessage());
return new RoadCheckResult(false, lat, lon, "HTTP Error: " + responseCode);
}
} catch (Exception e) {
Log.e(TAG, "Error checking road: ", e);
return new RoadCheckResult(false, lat, lon, e.getMessage());
}
}
@Override
protected void onPostExecute(RoadCheckResult result) {
if (listener != null) {
if (result.errorMessage == null) {
listener.onRoadCheckResult(result.isOnRoad, result.snappedLat, result.snappedLon);
} else {
listener.onRoadCheckError(result.errorMessage);
}
}
}
}
private static class RoadCheckResult {
boolean isOnRoad;
double snappedLat;
double snappedLon;
String errorMessage;
RoadCheckResult(boolean isOnRoad, double snappedLat, double snappedLon) {
this.isOnRoad = isOnRoad;
this.snappedLat = snappedLat;
this.snappedLon = snappedLon;
}
RoadCheckResult(boolean isOnRoad, double snappedLat, double snappedLon, String errorMessage) {
this.isOnRoad = isOnRoad;
this.snappedLat = snappedLat;
this.snappedLon = snappedLon;
this.errorMessage = errorMessage;
}
}
}
Java code for making a Roads API request and parsing the response.
YOUR_API_KEY
with your actual Google Cloud API key. Keep your API key secure and restrict it to prevent unauthorized use. Never embed API keys directly in client-side code without proper restrictions.Integrating with Google Maps Android API v2
Once you have the RoadChecker
class, you can integrate it with your GoogleMap
instance. For example, you might want to check the road status of a point when a user long-presses on the map, or when a new location update is received from a LocationProvider
.
import androidx.fragment.app.FragmentActivity;
import android.os.Bundle;
import android.widget.Toast;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
public class MapActivity extends FragmentActivity implements OnMapReadyCallback {
private GoogleMap mMap;
private RoadChecker roadChecker;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map);
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
roadChecker = new RoadChecker();
}
@Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
// Example: Check a point on long press
mMap.setOnMapLongClickListener(new GoogleMap.OnMapLongClickListener() {
@Override
public void onMapLongClick(LatLng latLng) {
mMap.clear(); // Clear previous markers
mMap.addMarker(new MarkerOptions().position(latLng).title("Original Point"));
checkPointOnRoad(latLng);
}
});
// Example: Initial check for a specific point
LatLng sydney = new LatLng(-34, 151);
mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney"));
// mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));
// checkPointOnRoad(sydney);
}
private void checkPointOnRoad(LatLng latLng) {
roadChecker.checkCoordinateOnRoad(latLng.latitude, latLng.longitude, new RoadChecker.RoadCheckListener() {
@Override
public void onRoadCheckResult(boolean isOnRoad, double snappedLat, double snappedLon) {
LatLng snappedLatLng = new LatLng(snappedLat, snappedLon);
if (isOnRoad) {
mMap.addMarker(new MarkerOptions().position(snappedLatLng).title("Snapped to Road").snippet("On Road"));
Toast.makeText(MapActivity.this, "Point is on a road! Snapped to: " + snappedLatLng.toString(), Toast.LENGTH_LONG).show();
} else {
mMap.addMarker(new MarkerOptions().position(snappedLatLng).title("Not on Road").snippet("Off Road"));
Toast.makeText(MapActivity.this, "Point is NOT on a road. Nearest point: " + snappedLatLng.toString(), Toast.LENGTH_LONG).show();
}
}
@Override
public void onRoadCheckError(String errorMessage) {
Toast.makeText(MapActivity.this, "Error checking road: " + errorMessage, Toast.LENGTH_LONG).show();
Log.e(TAG, "Road check error: " + errorMessage);
}
});
}
}
Example MapActivity
demonstrating how to use RoadChecker
.
snapToRoads
endpoint is designed for snapping points to roads. If a point is very far from any road, the API might still return the closest road, even if it's not truly 'on' it. You might need to implement additional logic to determine if the snapped point is within an acceptable distance from the original point to consider it 'on road'.