OSMDroid with Google Play Location Services

Introduction

OSMDroid is a suitable alternative to Google Maps if you need an alternative tile source for mobile maps (e.g. a hiking map or a custom representation of a campus or theme park). In this post we show how to integrate with Google’s location services.

The demonstration project is available on GitHub.

Set up an Android Studio project

Create a new project with a Google Play Services activity as a main activity. This will generate some boilerplate code to connect to the services. In this example we have a project named Location Demo with OsmLocationActivity.java as the source for the main activity. Add the OSMDroid and SLF4J dependencies to build.gradle of your app module:

...
repositories {mavenCentral()}
 
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:21.0.3'
    compile 'com.google.android.gms:play-services:6.5.87'
    compile 'org.osmdroid:osmdroid-android:4.2'
    compile 'org.slf4j:slf4j-simple:1.6.1'
}

Make sure to have the following permissions in AndroidManifest.xml. External storage is needed for OSMDroid to download tiles.

 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
 <uses-permission android:name="android.permission.INTERNET"/>
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Create a MapView

We define a MapView in XML, although there are reasons not to do so.
activity_osm_location.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
 
    <org.osmdroid.views.MapView
        android:id="@+id/mapview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
 
</LinearLayout>

Declare the MapView in your activity and configure it in onCreate(). (Refer to the GitHub project for the complete code and required imports.)

        mapView = (MapView) findViewById(R.id.mapview);
        mapView.setTileSource(TileSourceFactory.MAPNIK);
        mapView.setBuiltInZoomControls(true);
        mapView.setMultiTouchControls(true);
        mapController = mapView.getController();
        mapController.setZoom(12);

Add an overlay to display the display the location. We’ll use the default marker here. An ArrayList of OverLayItems will hold only one item, representing the user location. Also pass an instance of OSMDroid’s default resource proxy and a gesture listener stub to the ItemizedOverlay instance:

        resourceProxy = new DefaultResourceProxyImpl(getApplicationContext());
        items = new ArrayList<>();
 
        locationOverlay = new ItemizedIconOverlay<>(items,
                new Glistener(), resourceProxy);
        mapView.getOverlays().add(locationOverlay);
    class Glistener implements ItemizedIconOverlay.OnItemGestureListener<OverlayItem> {
        @Override
        public boolean onItemLongPress(int index, OverlayItem item) {
            return false;
        }
 
        @Override
        public boolean onItemSingleTapUp(int index, OverlayItem item) {
            return true;
        }
 
    }

API client

Build a Google API client and add the LocationServices API in onStart():

    @Override
    protected void onStart() {
        super.onStart();
        if (googleApiClient == null) {
            googleApiClient = new GoogleApiClient.Builder(this)
                    .addApi(LocationServices.API)
                    .addConnectionCallbacks(this)
                    .addOnConnectionFailedListener(this)
                    .build();
        }
        googleApiClient.connect();
    }

Disconnect the client in onStop():

        if (googleApiClient != null) {
            googleApiClient.disconnect();
        }

Request the last known location

Once connected, we can retrieve the last known location from the API client as a Location object. In onConnected():

        Location lastLocation = LocationServices.FusedLocationApi.getLastLocation(
                googleApiClient);
        updateMap(lastLocation);

Update the map

Updating the map implies the creation of a new overlay item from a GeoPoint and adding a new location overlay to the map:

    private void updateMap(Location location) {
        if (null != location) {
            int lat = (int) (location.getLatitude() * 1E6);
            int lng = (int) (location.getLongitude() * 1E6);
            GeoPoint gpt = new GeoPoint(lat, lng);
            mapController.setCenter(gpt);
            items.clear();
            items.add(new OverlayItem(getString(R.string.location), (getString(R.string.location)), gpt));
            locationOverlay = new ItemizedIconOverlay<>(
                    items, new Glistener(), resourceProxy);
            mapView.getOverlays().clear();
            mapView.getOverlays().add(locationOverlay);
            mapView.invalidate();
        }
    }

Request location updates

To follow the user’s location we define a location request in onCreate(). Adjust the update interval in millseconds to suit your needs.

        locationRequest = new LocationRequest();
        locationRequest.setInterval(10000);
        locationRequest.setFastestInterval(5000);
        locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

Start the location updates when the API client is connected, in onConnected():

        LocationServices.FusedLocationApi.requestLocationUpdates(
                googleApiClient, locationRequest, this);

Stop requesting updates when the activity pauses:

    @Override
    protected void onResume() {
        super.onResume();
        if (googleApiClient.isConnected()) {
            LocationServices.FusedLocationApi.requestLocationUpdates(
                    googleApiClient, locationRequest, this);
        }
    }
 
    @Override
    protected void onPause() {
        LocationServices.FusedLocationApi.removeLocationUpdates(
                googleApiClient, this);
        super.onPause();
    }

Update the map when the location changes:

    @Override
    public void onLocationChanged(Location location) {
        updateMap(location);
    }

locationdemo