Afficher une carte sous Android

Objectif

L’objectif est d’afficher une localisation sur une carte.

Pré-requis : Géolocalisation [PDF]

Introduction

Pour afficher une localisation sur une carte, on peut utiliser :

OpenStreetMap sous Android

OpenStreetMap (OSM) est un projet initié en 2004 qui a pour but de constituer une base de données géographiques libre du monde (permettant par exemple de créer des cartes sous licence libre), en utilisant le système GPS et d’autres données libres.

Android est un système d’exploitation basé sur Linux pour les téléphones mobiles et autres appareils mobiles.

Les appareils sous Android peuvent être utilisés pour afficher et éditer des cartes basées sur OpenStreetMap.

Il existe plusieurs bibliothèques permettant aux développeurs Android d’intégrer OpenStreetMap dans leurs propres applications, qu’il s’agisse d’une carte statique, d’une carte entièrement interactive ou d’utilisations plus sophistiquées telles que le géocodage et le routage.

Osmdroid est une bibliothèque Android qui fournit des outils pour interagir avec OpenStreetMap. L’OpenStreetMapView est un remplacement (presque) complet pour la classe MapView d’Android. Remarque : osmdroid est une application de démonstration disponible sur google play.

osmdroid

Pour intégrer la bibliothèque osmdroid dans votre application avec Android Studio, il faut éditer le fichier build.gradle situé dans votre dossier app :

...
dependencies {
    ...
    compile 'org.osmdroid:osmdroid-android:5.6.5'
}

Lire : HowTo osmdroid library

Les permissions

L’application a besoin de certains droits d’accès (uses-permission) et pour cela, il faut modifier le fichier AndroidManifest.xml de l’application :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.tv.myapplication6">
          
    <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" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />    
    ...
</manifest>

La GUI

On va intégrer un composant MapView à notre GUI :

Le layout XML :

...
<LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <org.osmdroid.views.MapView
            android:id="@+id/mapview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

</LinearLayout>
...

Affichage de la carte

La classe MapView est une vue qui permet d’afficher une carte. Lorsqu’elle a le focus, elle capture les pressions de touche et les gestes tactiles pour déplacer la carte.

Exemple :

...
import org.osmdroid.views.MapView;

...
private MapView myOpenMapView;


protected void onCreate(Bundle savedInstanceState)
{
    ...

    myOpenMapView = (MapView)findViewById(R.id.mapview);
    myOpenMapView.setBuiltInZoomControls(true);
    myOpenMapView.setClickable(true);
    myOpenMapView.getController().setZoom(15);

    ...
}
...

On utilisera la méthode setCenter() pour se déplacer en un point de la carte :

...

LocationListener ecouteurGPS = new LocationListener() {
        @Override
        public void onLocationChanged(Location localisation)
        {
            ...
            myOpenMapView.getController().setCenter(new GeoPoint(localisation.getLatitude(), localisation.getLongitude()));
            ...
        }
}

On peut ajouter une échelle sur la carte en superposition à l’aide de la classe ScaleBarOverlay :

ScaleBarOverlay myScaleBarOverlay = new ScaleBarOverlay(myOpenMapView);
myOpenMapView.getOverlays().add(myScaleBarOverlay);

On peut ajouter une boussole sur la carte en superposition à l’aide de la classe CompassOverlay :

CompassOverlay mCompassOverlay = new CompassOverlay(getApplicationContext(), new InternalCompassOrientationProvider(getApplicationContext()), myOpenMapView);
mCompassOverlay.enableCompass();
myOpenMapView.getOverlays().add(mCompassOverlay);

On peut ajouter sa position GPS en superposition à l’aide de la classe MyLocationNewOverlay :

MyLocationNewOverlay mLocationOverlay = new MyLocationNewOverlay(new GpsMyLocationProvider(getApplicationContext()), myOpenMapView);
mLocationOverlay.enableMyLocation();
myOpenMapView.setMultiTouchControls(true);
myOpenMapView.getOverlays().add(mLocationOverlay);

Voir aussi : la classe DirectedLocationOverlay

private MapView myOpenMapView;
private DirectedLocationOverlay myLocationOverlay;

// Dans LocationListener
public void onLocationChanged(Location localisation)
{
    ...
    // voir OSMNavigator
    GeoPoint nouvelleLocalisation = new GeoPoint(localisation);

    if (!myLocationOverlay.isEnabled())
    {
        myLocationOverlay.setEnabled(true);
        myOpenMapView.getController().animateTo(nouvelleLocalisation);
    }

    GeoPoint localisationPrecedente = myLocationOverlay.getLocation();
    myLocationOverlay.setLocation(nouvelleLocalisation);
    myLocationOverlay.setAccuracy((int)localisation.getAccuracy());

    if (localisationPrecedente != null && localisation.getProvider().equals(LocationManager.GPS_PROVIDER))
    {
        mSpeed = localisation.getSpeed() * 3.6;
        long speedInt = Math.round(mSpeed);

        if (mSpeed >= 0.1)
        {
            mAzimuthAngleSpeed = localisation.getBearing();
            myLocationOverlay.setBearing(mAzimuthAngleSpeed);
        }
    }

    myOpenMapView.getController().animateTo(nouvelleLocalisation);
    myOpenMapView.setMapOrientation(-mAzimuthAngleSpeed);
    ...
}

...

@Override
protected void onCreate(Bundle savedInstanceState)
{
    ...
    myLocationOverlay = new DirectedLocationOverlay(this);
    myLocationOverlay.setEnabled(false);
    myOpenMapView.getOverlays().add(myLocationOverlay);
    ...
}

On peut ajouter un Marker (ici avec sa propre image copiée dans res -> drawable) :

Marker tec = new Marker(myOpenMapView);
tec.setPosition(new GeoPoint(localisation.getLatitude(), localisation.getLongitude()));
tec.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
tec.setIcon(getResources().getDrawable(R.drawable.trottinette));
tec.setTitle("TEC");
myOpenMapView.getOverlays().add(tec);

myOpenMapView.invalidate();

On peut ajouter un tracé à base de lignes avec la classe Polyline :

ArrayList<GeoPoint> trajet.add(new GeoPoint(localisation.getLatitude(), localisation.getLongitude()));

Polyline line = new Polyline(getApplicationContext());
line.setTitle("Un trajet");
line.setSubDescription(Polyline.class.getCanonicalName());
line.setWidth(10f);
line.setColor(Color.RED);
line.setPoints(trajet);
line.setGeodesic(true);
line.setInfoWindow(new BasicInfoWindow(R.layout.bonuspack_bubble, myOpenMapView));

myOpenMapView.invalidate();

Exemple

package com.example.tv.myapplicationcartegps;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.Criteria;
import android.location.LocationProvider;
import android.os.StrictMode;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

// before 5.2
//import org.osmdroid.DefaultResourceProxyImpl;
//import org.osmdroid.ResourceProxy;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.Marker;
import org.osmdroid.views.overlay.Polyline;
import org.osmdroid.views.overlay.ScaleBarOverlay;
import org.osmdroid.views.overlay.compass.CompassOverlay;
import org.osmdroid.views.overlay.compass.InternalCompassOrientationProvider;
import org.osmdroid.views.overlay.gestures.RotationGestureOverlay;
import org.osmdroid.views.overlay.infowindow.BasicInfoWindow;
import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider;
import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay;

public class MyActivity extends AppCompatActivity
{
    LocationManager locationManager = null;
    private int etat;
    private String fournisseur;
    private TextView latitude;
    private TextView longitude;
    private TextView Adresse;

    private MapView myOpenMapView;
    ScaleBarOverlay myScaleBarOverlay;
    CompassOverlay mCompassOverlay;
    MyLocationNewOverlay mLocationOverlay;
    RotationGestureOverlay mRotationGestureOverlay;
    ArrayList<GeoPoint> trajet;

    Geocoder geocoder = new Geocoder(this, Locale.getDefault());

    LocationListener ecouteurGPS = new LocationListener() {
        @Override
        public void onLocationChanged(Location localisation)
        {
            Toast.makeText(getApplicationContext(), fournisseur + " localisation", Toast.LENGTH_SHORT).show();

            Log.d("GPS", "localisation : " + localisation.toString());
            String coordonnees = String.format("Latitude : %f - Longitude : %f\n", localisation.getLatitude(), localisation.getLongitude());
            Log.d("GPS", coordonnees);
            String autres = String.format("Vitesse : %f - Altitude : %f - Cap : %f\n", localisation.getSpeed(), localisation.getAltitude(), localisation.getBearing());
            Log.d("GPS", autres);
            //String timestamp = String.format("Timestamp : %d\n", localisation.getTime());
            //Log.d("GPS", "timestamp : " + timestamp);
            SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
            Date date = new Date(localisation.getTime());
            Log.d("GPS", sdf.format(date));

            String strLatitude = String.format("Latitude : %f", localisation.getLatitude());
            String strLongitude = String.format("Longitude : %f", localisation.getLongitude());
            latitude.setText(strLatitude);
            longitude.setText(strLongitude);

            myOpenMapView.getController().setCenter(new GeoPoint(localisation.getLatitude(), localisation.getLongitude()));
            //myOpenMapView.setMapOrientation(localisation.getBearing());

            trajet.add(new GeoPoint(localisation.getLatitude(), localisation.getLongitude()));

            // Un tracé à base de lignes rouges
            Polyline line = new Polyline(getApplicationContext());
            line.setTitle("Un trajet");
            line.setSubDescription(Polyline.class.getCanonicalName());
            line.setWidth(10f);
            line.setColor(Color.RED);
            line.setPoints(trajet);
            line.setGeodesic(true);
            line.setInfoWindow(new BasicInfoWindow(R.layout.bonuspack_bubble, myOpenMapView));
            myOpenMapView.getOverlayManager().add(line);

            /*Marker tec = new Marker(myOpenMapView);
            tec.setPosition(new GeoPoint(localisation.getLatitude(), localisation.getLongitude()));
            tec.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
            tec.setIcon(getResources().getDrawable(R.drawable.trottinette));
            tec.setTitle("TEC");
            myOpenMapView.getOverlays().add(tec);*/

            /*ArrayList<OverlayItem> items = new ArrayList<OverlayItem>();
            items.add(new OverlayItem("Title", "Description", new GeoPoint(localisation.getLatitude(), localisation.getLongitude())));

            ItemizedOverlayWithFocus<OverlayItem> mOverlay = new ItemizedOverlayWithFocus<OverlayItem>(getApplicationContext(), items,
                    new ItemizedIconOverlay.OnItemGestureListener<OverlayItem>() {
                        @Override
                        public boolean onItemSingleTapUp(final int index, final OverlayItem item) {
                            Toast.makeText( getApplicationContext(), "Overlay Titled: " +
                                    item.getTitle() + " Single Tapped" + "\n" + "Description: " +
                                    item.getSnippet(), Toast.LENGTH_LONG).show();
                            return true;
                        }
                        @Override
                        public boolean onItemLongPress(final int index, final OverlayItem item) {
                            return false;
                        }
                    });
            //mOverlay.setFocusItemsOnTap(true);
            myOpenMapView.getOverlays().add(mOverlay);*/

            myOpenMapView.invalidate();

            List<Address> adresses = null;
            try
            {
                adresses = geocoder.getFromLocation(localisation.getLatitude(), localisation.getLongitude(), 1);
            }
            catch (IOException ioException)
            {
                Log.e("GPS", "erreur", ioException);
            } catch (IllegalArgumentException illegalArgumentException)
            {
                Log.e("GPS", "erreur " + coordonnees, illegalArgumentException);
            }

            if (adresses == null || adresses.size()  == 0)
            {
                Log.e("GPS", "erreur aucune adresse !");
            }
            else
            {
                Address adresse = adresses.get(0);
                ArrayList<String> addressFragments = new ArrayList<String>();

                String strAdresse = adresse.getAddressLine(0) + ", " + adresse.getLocality();
                Log.d("GPS", "adresse : " + strAdresse);

                for(int i = 0; i <= adresse.getMaxAddressLineIndex(); i++)
                {
                    addressFragments.add(adresse.getAddressLine(i));
                }
                Log.d("GPS", TextUtils.join(System.getProperty("line.separator"), addressFragments));
                Adresse.setText(TextUtils.join(System.getProperty("line.separator"), addressFragments));
            }
        }

        @Override
        public void onProviderDisabled(String fournisseur)
        {
            Toast.makeText(getApplicationContext(), fournisseur + " désactivé !", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onProviderEnabled(String fournisseur)
        {
            Toast.makeText(getApplicationContext(), fournisseur + " activé !", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onStatusChanged(String fournisseur, int status, Bundle extras)
        {
            if (etat != status)
            {
                switch (status)
                {
                    case LocationProvider.AVAILABLE:
                        Toast.makeText(getApplicationContext(), fournisseur + " état disponible", Toast.LENGTH_SHORT).show();
                        break;
                    case LocationProvider.OUT_OF_SERVICE:
                        Toast.makeText(getApplicationContext(), fournisseur + " état indisponible", Toast.LENGTH_SHORT).show();
                        break;
                    case LocationProvider.TEMPORARILY_UNAVAILABLE:
                        Toast.makeText(getApplicationContext(), fournisseur + " état temporairement indisponible", Toast.LENGTH_SHORT).show();
                        break;
                    default:
                        Toast.makeText(getApplicationContext(), fournisseur + " état : " + status, Toast.LENGTH_SHORT).show();
                }
            }
            etat = status;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.commande);

        etat = 0;
        latitude = (TextView) findViewById(R.id.textViewLatitude);
        longitude = (TextView) findViewById(R.id.textViewLongitude);
        Adresse = (TextView) findViewById(R.id.textViewAdresse);

        myOpenMapView = (MapView)findViewById(R.id.mapview);
        myOpenMapView.setBuiltInZoomControls(true);
        myOpenMapView.setClickable(true);
        myOpenMapView.getController().setZoom(18);

        myScaleBarOverlay = new ScaleBarOverlay(myOpenMapView);
        myOpenMapView.getOverlays().add(myScaleBarOverlay);

        mCompassOverlay = new CompassOverlay(getApplicationContext(), new InternalCompassOrientationProvider(getApplicationContext()), myOpenMapView);
        mCompassOverlay.enableCompass();
        myOpenMapView.getOverlays().add(mCompassOverlay);

        mRotationGestureOverlay = new RotationGestureOverlay(getApplicationContext(), myOpenMapView);
        mRotationGestureOverlay.setEnabled(true);
        myOpenMapView.setMultiTouchControls(true);
        myOpenMapView.getOverlays().add(this.mRotationGestureOverlay);

        trajet = new ArrayList<GeoPoint>();

        Log.d("GPS", "onCreate");

        initialiserLocalisation();

        mLocationOverlay = new MyLocationNewOverlay(new GpsMyLocationProvider(getApplicationContext()), myOpenMapView);
        mLocationOverlay.enableMyLocation();
        myOpenMapView.setMultiTouchControls(true);
        myOpenMapView.getOverlays().add(mLocationOverlay);
    }

    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        arreterLocalisation();
    }

    private void initialiserLocalisation()
    {
        if (locationManager == null)
        {
            locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
            Criteria criteres = new Criteria();

            // la précision  : (ACCURACY_FINE pour une haute précision ou ACCURACY_COARSE pour une moins bonne précision)
            criteres.setAccuracy(Criteria.ACCURACY_FINE);

            // l'altitude
            criteres.setAltitudeRequired(true);

            // la direction
            criteres.setBearingRequired(true);

            // la vitesse
            criteres.setSpeedRequired(true);

            // la consommation d'énergie demandée
            criteres.setCostAllowed(true);
            //criteres.setPowerRequirement(Criteria.POWER_HIGH);
            criteres.setPowerRequirement(Criteria.POWER_MEDIUM);

            fournisseur = locationManager.getBestProvider(criteres, true);
            Log.d("GPS", "fournisseur : " + fournisseur);
        }

        if (fournisseur != null)
        {
            // dernière position connue
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)
            {
                Log.d("GPS", "no permissions !");
                return;
            }

            Location localisation = locationManager.getLastKnownLocation(fournisseur);
            if(localisation != null)
            {
                // on notifie la localisation
                ecouteurGPS.onLocationChanged(localisation);
            }

            // on configure la mise à jour automatique : au moins 10 mètres et 15 secondes
            locationManager.requestLocationUpdates(fournisseur, 15000, 10, ecouteurGPS);
        }
    }

    private void arreterLocalisation()
    {
        if(locationManager != null)
        {
            locationManager.removeUpdates(ecouteurGPS);
            ecouteurGPS = null;
        }
    }
}

Google Maps sous Android

Voir : La localisation et les cartes (OpenClassRooms)

Liens