Table des matières

Google Map

Dépôt du projet

Documentation: https://developers.google.com/maps/documentation/android-sdk/map#view_the_code

Pour pouvoir utiliser une carte Google Map, il faut créer une clé d'API et l'insérer dans le fichier AndroidManifest.xml à la place de YOUR_API_KEY

Se rendre sur la console Google pour définir la clé. Suivre les indications sur cette page : Créer des clés API

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="ca.cegepgarneau.google_map">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Google_Map">

        <!--
             TODO: Before you run your application, you need a Google Maps API key.

             To get one, follow the directions here:

                https://developers.google.com/maps/documentation/android-sdk/get-api-key

             Once you have your API key (it starts with "AIza"), define a new property in your
             project's local.properties file (e.g. MAPS_API_KEY=Aiza...), and replace the
             "YOUR_API_KEY" string in this file with "${MAPS_API_KEY}".
        -->
        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="YOUR_API_KEY" />

        <activity
            android:name=".MapsActivity"
            android:label="@string/title_activity_maps">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Restriction de la clé

Dans la console Google, sélectionner Application Android (1)

Ajouter ensuite une restriction (3) en saisissant le nom du package de votre application, et l'empreinte numérique SHA-1

Pour trouver l'empreinte numérique SHA-1 de votre application, suivre les indications (2) suivant votre système d'exploitation.

Ajout de la carte dans une activité

Voir démo et documentation

https://developers.google.com/maps/documentation/android-sdk/map

Fenêtre d'information

Implémentation des interfaces GoogleMap.OnInfoWindowClickListener, GoogleMap.InfoWindowAdapter

(voir démo)

// détection du click sur une fenêtre d'information d'un marqueur
mMap.setOnInfoWindowClickListener(this);

// Permet de modifier l'apparence de la fenêtre d'information d'un marqueur
mMap.setInfoWindowAdapter(this);

Afficher sa position

Documentation: https://developers.google.com/maps/documentation/android-sdk/location

Demande de permission ACCESS_COARSE_LOCATION ou ACCESS_FINE_LOCATION

La méthode setMyLocationEnabled(true) permet d'afficher le bouton pour centrer la carte sur la position de l'utilisateur.

La méthode setOnMyLocationButtonClickListener(this) avec onMyLocationButtonClick() permet de détecter le click sur ce bouton (1)

La méthode setOnMyLocationClickListener(this) avec onMyLocationClick() permet de détecter le click sur la position de l'utilisateur (2)

Suivre la localisation

Documentation:

Ajout de play-services-location dans le build.gradle du module

// Google Play Service
implementation 'com.google.android.gms:play-services-location:21.0.1'

Dans MainActivity; création d'une instance du Fused Location Provider

private lateinit var fusedLocationClient: FusedLocationProviderClient


// ..

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  // ...
  fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)

}

Retrouver la dernière position connue

fusedLocationClient.lastLocation
    .addOnSuccessListener(this) { location ->
        // Vérifie que la position n'est pas null
        if (location != null) {
            Log.d(TAG, "onSuccess: $location")
            // Centre la carte sur la position de l'utilisateur au démarrage
            val latLng = LatLng(location.latitude, location.longitude)
            mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 11f))
        }
    }


Il faut penser à ajouter la vérification des permissions (voir démo)

Mise à jour position automatique

Pour avoir une mise à jour régulière de la position ⇒ documentation: https://developer.android.com/training/location/change-location-settings

Configuration pour mise à jour automatique de la position


// Configuration pour mise à jour automatique de la position
locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000L)
    .setWaitForAccurateLocation(false)
    .setMinUpdateIntervalMillis(2000L)
    .setMaxUpdateDelayMillis(5000L)
    .build()

// Création de la requête pour la mise à jour de la position
// avec la configuration précédente
val request = LocationSettingsRequest.Builder()
    .addLocationRequest(locationRequest)
    .build()

// Création du client pour la mise à jour de la position.
// Le client va permettre de vérifier si la configuration est correcte,
// si l'utilisateur a activé ou désactivé la localisation
val client = LocationServices.getSettingsClient(this)

// Vérifie que la configuration de la mise à jour de la position est correcte
// Si l'utilisateur a activé ou désactivé la localisation
client.checkLocationSettings(request)
    .addOnSuccessListener {
        Log.d(TAG, "onSuccess: $it")
        // Si la configuration est correcte, on lance la mise à jour de la position
        fusedLocationClient.requestLocationUpdates(
            // Configuration de la mise à jour de la position
            locationRequest,
            // Callback pour la mise à jour de la position
            locationCallback,
            null
        )
    }
    .addOnFailureListener {
        Log.d(TAG, "onFailure: $it")
        // Si la configuration n'est pas correcte, on affiche un message
        Toast.makeText(this, "Veuillez activer la localisation", Toast.LENGTH_SHORT).show()
    }


Définir le callback qui est appelé quand la position change

class MapsActivity : AppCompatActivity(), ...{
    // ...

    // Déclaration pour le callback de la mise à jour de la position de l'utilisateur
    // Le callback est appelé à chaque fois que la position de l'utilisateur change
    private var locationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult) {
            super.onLocationResult(locationResult)
            userLocation = locationResult.lastLocation
            Log.d(TAG, "onLocationResult: ${userLocation?.latitude} ${userLocation?.longitude}")
        }
    }
}

Calculer une distance

Méthode distanceTo() entre deux objets de type Location


val distance = userLocation?.distanceTo(location)

Détecter un clic sur la carte

mMap.setOnMapClickListener { latLng ->
    Log.d(TAG, "onMapClick: $latLng")
    // Ajoute un marqueur à l'endroit cliqué
    mMap.addMarker(
        MarkerOptions().position(latLng)
            .title("Je suis ici !")
            .draggable(true)
    )
}

Marqueur déplaçable

mMap.addMarker(
    MarkerOptions().position(latLng)
        .title("Je suis ici !")
        .draggable(true)
)

Écouteur pour le drag&drop d'un marqueur

mMap.setOnMarkerDragListener(object : OnMarkerDragListener {
    override fun onMarkerDragStart(marker: Marker) {
        Log.d(TAG, "onMarkerDragStart: " + marker.position)
    }

    override fun onMarkerDrag(marker: Marker) {
        Log.d(TAG, "onMarkerDrag: " + marker.position)
    }

    override fun onMarkerDragEnd(marker: Marker) {
        Log.d(TAG, "onMarkerDragEnd: " + marker.position)
    }
})

Connaître la position de la caméra (centre de la carte)

CameraPosition cameraPosition = mMap.getCameraPosition();
LatLng position = new LatLng(cameraPosition.target.latitude, cameraPosition.target.longitude);

Ajouter des informations dans un marqueur

On peut ajouter un titre au marqueur avec la méthode title()

Pour ajouter des informations supplémentaires, il faut utiliser la propriété tag.

message = new Message(1, "Gaël", "Un message", 46.7819, -71.3571)
...
val marker = mMap.addMarker(
    MarkerOptions()
        .position(position)
        .title(message.author)
)

if (marker != null) {
    marker.tag = message 
}

Grâce à marker.tag, on pourra retrouver cet objet plus tard dans le code

val message: Message? = marker.tag as Message?

Afficher une image dans la fenêtre d'information

Quand on veut afficher une image qui est sur un serveur dans la fenêtre d'information, elle ne s'affiche pas car le temps qu'elle soit chargée, la fenêtre a déjà fini de se créer. Donc l'image n'apparaît pas.

Une astuce consiste à fermer la fenêtre d'information quand l'image a été chargée puis de ré-ouvrir la fenêtre pour qu'elle se redessine avec l'image avec les méthodes hideInfoWindow() et showInfoWindow().

Voici un exemple d'utilisation avec le librairie Coil-kt et onSuccess qui permet de mettre en place un écouteur pour savoir quand l'image est bien chargée.

override fun getInfoContents(marker: Marker): View {


    // Crée une vue à partir du layout personnalisé pour les fenêtres d'information
    // ...
    
    // Initialise les composants de la vue avec leurs identifiants respectifs
    // ...
    
    // Récupère les informations de la propriété associées au marqueur
    val markerInfo: Property? = marker.tag as Property?

    if (markerInfo != null) {
        // Affiche les informations de la propriété dans les composants de la vue
        tvPrice.text = markerInfo.priceDisplay
        tvType.text = markerInfo.type
        tvStreet.text = markerInfo.street
        tvCity.text = markerInfo.city

        // Utilise Coil pour charger l'image de la propriété depuis l'URL.
        val request = ImageRequest.Builder(requireContext())
            .data(markerInfo.photos?._600) // URL de l'image
            .allowHardware(false) // Évite des problèmes de rendu logiciel
            .target(
                onSuccess = { result ->
                    // Met à jour l'image de la vue lorsque le chargement réussit
                    ivPhoto.setImageDrawable(result)
                    // Rafraîchit la fenêtre d'information si elle est déjà affichée pour montrer l'image chargée
                    if (marker.isInfoWindowShown) {
                        marker.hideInfoWindow()
                        marker.showInfoWindow()
                    }
                },
                onError = { error ->
                    // Utilise une image par défaut en cas d'erreur de chargement
                    ivPhoto.setImageResource(R.drawable.placeholder)
                }
            )
            .build()
        // Enfile la requête de chargement d'image dans le gestionnaire d'images de Coil
        requireContext().imageLoader.enqueue(request)
    }
    // Retourne la vue personnalisée pour être utilisée comme contenu de la fenêtre d'information
    return view
}