1. Avant de commencer
Résumé
Cet atelier de programmation vous explique comment utiliser les données de Google Maps Platform pour afficher les adresses à proximité en réalité augmentée (RA) sur Android.
Conditions préalables
- Connaissances de base en développement Android avec Android Studio
- Bonne connaissance de Kotlin
Points abordés
- Demander à l'utilisateur l'autorisation d'accéder à la caméra et à la position de l'appareil
- Intégrer l'API Places pour extraire les adresses à proximité en fonction de la position de l'appareil
- Intégrer ARCore pour trouver des surfaces planes horizontales permettant d'ancrer les objets virtuels et de les placer dans l'espace 3D à l'aide de Sceneform
- Collecter des informations sur la position de l'appareil dans l'espace à l'aide de SensorManager et utiliser la bibliothèque d'utilitaires du SDK Maps pour Android afin de positionner les objets virtuels selon le bon cap
Prérequis
- Android Studio 2020.3.1 ou version ultérieure
- Un ordinateur de développement compatible avec OpenGL ES 3.0 ou une version ultérieure
- Un appareil compatible avec ARCore ou un Android Emulator compatible avec ARCore (vous trouverez des instructions à l'étape suivante)
2. Configuration
Android Studio
Cet atelier de programmation utilise Android 10.0 (API de niveau 29). Vous devez avoir installé les services Google Play dans Android Studio. Pour installer ces deux dépendances, procédez comme suit :
- Accédez à SDK Manager en cliquant sur Tools (Outils) > SDK Manager.
- Vérifiez si Android 10.0 est installé. Si ce n'est pas le cas, installez-le en cochant la case Android 10.0 (Q), cliquez sur OK, puis de nouveau sur OK dans la boîte de dialogue qui s'affiche.
- Enfin, installez les services Google Play. Pour ce faire, dans l'onglet SDK Tools, cochez la case Google Play services (Services Google Play), cliquez sur OK, puis à nouveau sur OK dans la boîte de dialogue qui s'affiche.
API requises
À l'étape 3 de la section suivante, activez les options Maps SDK for Android (SDK Maps pour Android) et Places API (API Places) pour cet atelier de programmation.
Premiers pas avec Google Maps Platform
Si vous n'avez jamais utilisé Google Maps Platform, suivez le guide Premiers pas avec Google Maps Platform ou regardez la playlist de démarrage avec Google Maps Platform pour effectuer les étapes suivantes :
- Créer un compte de facturation
- Créer un projet
- Activer les SDK et les API Google Maps Platform (listés dans la section précédente)
- Générer une clé API
Facultatif : Android Emulator
Si vous ne possédez pas d'appareil compatible avec ARCore, vous pouvez également utiliser Android Emulator pour simuler une position d'appareil dans une scène en RA. Étant donné que vous utiliserez également Sceneform dans cet exercice, vous devez suivre la procédure "Configurer l'émulateur pour prendre en charge Sceneform".
3. Démarrage rapide
Voici un code de démarrage qui vous permettra de commencer rapidement cet atelier de programmation. Vous pouvez tout à fait passer directement au résultat, mais si vous souhaitez voir toutes les étapes, poursuivez votre lecture.
Vous pouvez cloner le dépôt si vous avez installé git
.
git clone https://github.com/googlecodelabs/display-nearby-places-ar-android.git
Vous pouvez également cliquer sur le bouton ci-dessous pour télécharger le code source.
Une fois que vous avez obtenu le code, ouvrez le projet qui se trouve dans le répertoire starter
.
4. Présentation du projet
Parcourez le code que vous avez téléchargé à l'étape précédente. Dans ce répertoire, vous devriez trouver un module unique nommé app
, qui contient le package com.google.codelabs.findnearbyplacesar
.
AndroidManifest.xml
Les attributs suivants sont déclarés dans le fichier AndroidManifest.xml
pour vous permettre d'utiliser les fonctionnalités requises dans cet atelier de programmation :
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Sceneform requires OpenGL ES 3.0 or later. -->
<uses-feature
android:glEsVersion="0x00030000"
android:required="true" />
<!-- Indicates that app requires ARCore ("AR Required"). Ensures the app is visible only in the Google Play Store on devices that support ARCore. For "AR Optional" apps remove this line. -->
<uses-feature android:name="android.hardware.camera.ar" />
Pour uses-permission
, qui spécifie les autorisations utilisateur nécessaires afin d'utiliser ces fonctionnalités, les éléments suivants sont déclarés :
android.permission.INTERNET
: cette autorisation permet à votre application d'effectuer des opérations réseau et d'extraire des données via Internet (par exemple, des informations sur les adresses via l'API Places).android.permission.CAMERA
: l'accès à la caméra de l'appareil est nécessaire pour pouvoir afficher des objets en réalité augmentée.android.permission.ACCESS_FINE_LOCATION
: l'accès à la position est nécessaire pour extraire les adresses à proximité de l'appareil.
Pour uses-feature
, qui spécifie les fonctions matérielles requises par cette application, les éléments suivants sont déclarés :
- OpenGL ES version 3.0 est requis.
- Un appareil compatible avec ARCore est requis.
De plus, les balises de métadonnées suivantes sont ajoutées sous l'objet d'application :
<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/AppTheme">
<!--
Indicates that this app requires Google Play Services for AR ("AR Required") and causes
the Google Play Store to download and install Google Play Services for AR along with
the app. For an "AR Optional" app, specify "optional" instead of "required".
-->
<meta-data
android:name="com.google.ar.core"
android:value="required" />
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_maps_key" />
<!-- Additional elements here -->
</application>
La première entrée "meta-data" indique qu'ARCore est nécessaire pour exécuter cette application. La seconde vous permet de fournir votre clé API Google Maps Platform au SDK Maps pour Android.
build.gradle
Dans build.gradle
, les dépendances supplémentaires suivantes sont spécifiées :
dependencies {
// Maps & Location
implementation 'com.google.android.gms:play-services-location:17.0.0'
implementation 'com.google.android.gms:play-services-maps:17.0.0'
implementation 'com.google.maps.android:maps-utils-ktx:1.7.0'
// ARCore
implementation "com.google.ar.sceneform.ux:sceneform-ux:1.15.0"
// Retrofit
implementation "com.squareup.retrofit2:retrofit:2.7.1"
implementation "com.squareup.retrofit2:converter-gson:2.7.1"
}
Voici une brève description de chaque dépendance :
- Les bibliothèques avec l'ID de groupe
com.google.android.gms
, à savoirplay-services-location
etplay-services-maps
, sont utilisées pour accéder aux informations de position de l'appareil et aux fonctionnalités d'accès associées à Google Maps. com.google.maps.android:maps-utils-ktx
correspond à la bibliothèque d'extensions Kotlin (KTX) pour la bibliothèque d'utilitaires du SDK Maps pour Android. Les fonctionnalités seront utilisées dans cette bibliothèque pour, plus tard, positionner des objets virtuels dans un espace réel.com.google.ar.sceneform.ux:sceneform-ux
est la bibliothèque Sceneform, qui vous permet d'afficher des scènes 3D réalistes sans avoir à apprendre à utiliser OpenGL.- Les dépendances dont l'ID de groupe est
com.squareup.retrofit2
sont celles de Retrofit. Elles vous permettent d'écrire rapidement un client HTTP pour interagir avec l'API Places.
Structure du projet
À cet endroit, vous trouverez les packages et fichiers suivants :
- api : ce package contient des classes qui sont utilisées pour interagir avec l'API Places à l'aide de l'outil Retrofit.
- ar : ce package contient tous les fichiers associés à ARCore.
- model : ce package contient une seule classe de données
Place
, utilisée pour encapsuler une adresse unique telle qu'elle est renvoyée par l'API Places. - MainActivity.kt : il s'agit du seul élément
Activity
contenu dans votre application. Il affichera une carte et une vue de la caméra.
5. Configuration de la scène
Découvrez les principaux composants de l'application en commençant par les sections faisant appel à la réalité augmentée.
MainActivity
contient un SupportMapFragment
, qui gérera l'affichage de l'objet de carte, et une sous-classe d'un ArFragment
(PlacesArFragment
) qui gérera l'affichage de la scène en réalité augmentée.
Configuration de la réalité augmentée
En plus d'afficher la scène en réalité augmentée, PlacesArFragment
gérera également la demande d'autorisation d'accès à la caméra, si elle n'a pas déjà été obtenue. Il est possible de demander des autorisations supplémentaires en ignorant la méthode getAdditionalPermissions
. Étant donné que vous avez également besoin d'une autorisation d'accéder à la position, spécifiez-la et ignorez la méthode getAdditionalPermissions
:
class PlacesArFragment : ArFragment() {
override fun getAdditionalPermissions(): Array<String> =
listOf(Manifest.permission.ACCESS_FINE_LOCATION)
.toTypedArray()
}
Exécuter le code
Ouvrez le code de squelette du répertoire starter
dans Android Studio. Si vous cliquez sur Run (Exécuter) > Run 'app' (Exécuter l'application) dans la barre d'outils, et que vous déployez l'application sur votre appareil ou votre émulateur, vous devriez d'abord être invité à autoriser l'accès à la position et à la caméra. Cliquez sur Allow (Autoriser). Vous devriez alors voir une vue de la caméra et une vue plan côte à côte comme sur cette image :
Détection des surfaces planes
En parcourant l'environnement dans lequel vous vous trouvez avec la caméra, vous remarquerez peut-être quelques points blancs superposés aux surfaces horizontales comme ceux situés sur le tapis dans cette image.
Ces points blancs sont des indications fournies par ARCore pour signifier qu'une surface plane horizontale a été détectée. Ces surfaces planes détectées vous permettent de créer ce que l'on appelle une "ancre" pour positionner des objets virtuels dans l'espace.
Pour en savoir plus sur ARCore et sur la façon dont l'outil appréhende l'environnement autour de vous, consultez les concepts fondamentaux.
6. Obtenir les adresses à proximité
Ensuite, vous devrez accéder à la position actuelle de l'appareil et l'afficher, puis extraire les adresses à proximité à l'aide de l'API Places.
Configuration Maps
Clé API Google Maps Platform
Précédemment, vous avez créé une clé API Google Maps Platform pour pouvoir interroger l'API Places et utiliser le SDK Maps pour Android. Ouvrez le fichier gradle.properties
et remplacez la chaîne "YOUR API KEY HERE"
par la clé API que vous avez créée.
Afficher la position de l'appareil sur la carte
Une fois que vous avez ajouté votre clé API, ajoutez un outil d'aide sur la carte pour que les utilisateurs sachent où ils se trouvent par rapport à la carte. Pour ce faire, accédez à la méthode setUpMaps
, puis, dans l'appel mapFragment.getMapAsync
, définissez googleMap.isMyLocationEnabled
sur true.
. Un point bleu s'affichera sur la carte.
private fun setUpMaps() {
mapFragment.getMapAsync { googleMap ->
googleMap.isMyLocationEnabled = true
// ...
}
}
Obtenir la position actuelle de l'appareil
Pour connaître la position de l'appareil, vous devez utiliser la classe FusedLocationProviderClient
. Vous avez déjà obtenu une instance de cette classe dans la méthode onCreate
de MainActivity
. Pour utiliser cet objet, remplissez la méthode getCurrentLocation
, qui accepte un argument lambda afin qu'une position puisse être transmise à l'appelant de cette méthode.
Pour mettre en œuvre cette méthode, vous pouvez accéder à la propriété lastLocation
de l'objet FusedLocationProviderClient
, puis ajouter un addOnSuccessListener
comme suit :
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
currentLocation = location
onSuccess(location)
}.addOnFailureListener {
Log.e(TAG, "Could not get location")
}
La méthode getCurrentLocation
est appelée depuis le lambda fourni dans getMapAsync
dans la méthode setUpMaps
à partir de laquelle les adresses à proximité sont extraites.
Lancer un appel réseau d'adresse
Dans l'appel de méthode getNearbyPlaces
, notez que les paramètres suivants sont transmis à la méthode placesServices.nearbyPlaces
: une clé API, la position de l'appareil, un rayon en mètres (défini sur 2 km) et un type d'adresse (actuellement défini sur park
).
val apiKey = "YOUR API KEY"
placesService.nearbyPlaces(
apiKey = apiKey,
location = "${location.latitude},${location.longitude}",
radiusInMeters = 2000,
placeType = "park"
)
Pour terminer l'appel réseau, transmettez la clé API que vous avez définie dans le fichier gradle.properties
. L'extrait de code suivant est défini dans votre fichier build.gradle
sous la configuration android > defaultConfig :
android {
defaultConfig {
resValue "string", "google_maps_key", (project.findProperty("GOOGLE_MAPS_API_KEY") ?: "")
}
}
La valeur de la ressource de chaîne google_maps_key
sera ainsi disponible au moment de la compilation.
Pour effectuer l'appel réseau, vous pouvez simplement lire cette ressource de chaîne via getString
sur l'objet Context
.
val apiKey = this.getString(R.string.google_maps_key)
7. Adresses en RA
Jusqu'à présent, vous avez :
- demandé à l'utilisateur l'autorisation d'accéder à la caméra et à la position de l'appareil lorsqu'il lance l'application pour la première fois ;
- configuré ARCore pour commencer à suivre des surfaces planes horizontales ;
- configuré le SDK Maps avec votre clé API ;
- obtenu la position actuelle de l'appareil ;
- extrait les adresses à proximité (en particulier des parcs) à l'aide de l'API Places.
Pour terminer cet exercice, il vous suffit de positionner les adresses que vous extrayez en réalité augmentée.
Compréhension de la scène
ARCore est en mesure de comprendre la scène réelle à travers l'appareil photo de l'appareil en détectant des points intéressants et distincts (appelés points de caractéristiques) dans chaque image. Lorsque ces points de caractéristiques sont regroupés et semblent se trouver sur une surface plane horizontale commune, comme des tables et des sols, ARCore peut rendre cette caractéristique disponible dans l'application en tant que surface plane horizontale.
Comme vous l'avez vu précédemment, ARCore aide l'utilisateur lorsqu'une surface plane est détectée en affichant des points blancs.
Ajouter des ancres
Une fois qu'une surface plane a été détectée, vous pouvez joindre un objet appelé une ancre. Grâce aux ancres, vous pouvez placer des objets virtuels afin de garantir que ceux-ci restent à la même position dans l'espace. Modifiez le code pour joindre une ancre lorsqu'une surface plane est détectée.
Dans setUpAr
, un OnTapArPlaneListener
est joint à PlacesArFragment
. Cet écouteur est appelé chaque fois que l'utilisateur appuie sur une surface plane dans la scène en RA. Dans cet appel, vous pouvez créer une Anchor
et un AnchorNode
à partir du HitResult
fourni dans l'écouteur. Pour ce faire, procédez comme suit :
arFragment.setOnTapArPlaneListener { hitResult, _, _ ->
val anchor = hitResult.createAnchor()
anchorNode = AnchorNode(anchor)
anchorNode?.setParent(arFragment.arSceneView.scene)
addPlaces(anchorNode!!)
}
L'AnchorNode
correspond à l'endroit où vous allez joindre des objets de nœud enfant (instances PlaceNode
) dans la scène gérée dans l'appel de méthode addPlaces
.
Exécuter le code
Si vous exécutez l'application avec les modifications ci-dessus, regardez autour de vous jusqu'à ce qu'une surface plane soit détectée. Appuyez sur les points blancs indiquant la présence d'une surface plane. Vous devriez alors voir des repères sur la carte pour tous les parcs les plus proches. Toutefois, vous pouvez remarquer que certains objets virtuels sont bloqués sur l'ancre qui a été créée et ne sont pas placés par rapport à la position de ces parcs dans l'espace.
La dernière étape consiste à corriger ce problème à l'aide de la bibliothèque d'utilitaires du SDK Maps pour Android et de SensorManager sur l'appareil.
8. Positionnement des adresses
Pour pouvoir positionner l'icône d'adresse virtuelle en réalité augmentée selon un cap précis, vous avez besoin de deux informations :
- Où se trouve le Nord géographique
- L'angle entre le Nord et chaque adresse
Déterminer le Nord
Le Nord peut être déterminé à l'aide des capteurs de position (magnétomètre et accéléromètre) disponibles sur l'appareil. Ces deux capteurs vous permettent de collecter en temps réel des informations sur la position de l'appareil. Pour plus d'informations sur les capteurs de position, consultez l'article sur le calcul de l'orientation de l'appareil.
Pour accéder à ces capteurs, vous devez obtenir un SensorManager
, puis enregistrer un SensorEventListener
sur ces capteurs. Ces étapes sont déjà effectuées pour vous dans les méthodes du cycle de vie de MainActivity
:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
sensorManager = getSystemService()!!
// ...
}
override fun onResume() {
super.onResume()
sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)?.also {
sensorManager.registerListener(
this,
it,
SensorManager.SENSOR_DELAY_NORMAL
)
}
sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)?.also {
sensorManager.registerListener(
this,
it,
SensorManager.SENSOR_DELAY_NORMAL
)
}
}
override fun onPause() {
super.onPause()
sensorManager.unregisterListener(this)
}
Dans la méthode onSensorChanged
, un objet SensorEvent
est fourni. Il contient des détails sur le plan de référence d'un capteur à mesure qu'il change au fil du temps. Ajoutez le code suivant à cette méthode :
override fun onSensorChanged(event: SensorEvent?) {
if (event == null) {
return
}
if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {
System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.size)
} else if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) {
System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.size)
}
// Update rotation matrix, which is needed to update orientation angles.
SensorManager.getRotationMatrix(
rotationMatrix,
null,
accelerometerReading,
magnetometerReading
)
SensorManager.getOrientation(rotationMatrix, orientationAngles)
}
Le code ci-dessus vérifie le type du capteur et met à jour son relevé selon son type (accéléromètre ou magnétomètre). À partir des relevés de ces capteurs, la valeur du nombre de degrés entre le Nord et l'appareil peut désormais être déterminée (valeur de orientationAngles[0]
).
Cap
Maintenant que le Nord est déterminé, l'étape suivante consiste à calculer l'angle entre celui-ci et chaque adresse, puis à utiliser ces informations afin de positionner les adresses correctement en réalité augmentée selon le cap approprié.
Pour calculer le cap, vous allez utiliser la bibliothèque d'utilitaires du SDK Maps pour Android, qui contient quelques fonctions d'assistance pour calculer les distances et les caps par géométrie sphérique. Pour en savoir plus, consultez cette présentation de la bibliothèque.
Vous allez ensuite utiliser la méthode sphericalHeading
dans la bibliothèque d'utilitaires. Elle permet de calculer le cap et le relèvement entre deux objets LatLng
. Cette information est nécessaire au sein de la méthode getPositionVector
définie dans Place.kt
. Cette méthode finira par afficher un objet Vector3
, qui sera ensuite utilisé par chaque PlaceNode
comme position locale dans l'espace de RA.
Remplacez la définition des caps dans cette méthode par ce qui suit :
val heading = latLng.sphericalHeading(placeLatLng)
Cela devrait donner la définition de méthode suivante :
fun Place.getPositionVector(azimuth: Float, latLng: LatLng): Vector3 {
val placeLatLng = this.geometry.location.latLng
val heading = latLng.sphericalHeading(placeLatLng)
val r = -2f
val x = r * sin(azimuth + heading).toFloat()
val y = 1f
val z = r * cos(azimuth + heading).toFloat()
return Vector3(x, y, z)
}
Position locale
La dernière étape de la procédure permettant d'orienter correctement les adresses en RA consiste à utiliser le résultat de getPositionVector
lorsque des objets PlaceNode
sont ajoutés à la scène. Accédez à addPlaces
dans MainActivity
, juste en dessous de la ligne où le parent est défini sur chaque placeNode
(juste au-dessous de placeNode.setParent(anchorNode)
). Définissez le localPosition
de du placeNode
sur le résultat de l'appel de getPositionVector
comme suit :
val placeNode = PlaceNode(this, place)
placeNode.setParent(anchorNode)
placeNode.localPosition = place.getPositionVector(orientationAngles[0], currentLocation.latLng)
Par défaut, la méthode getPositionVector
définit la distance Y du nœud sur 1 mètre, comme spécifié par la valeur y
de la méthode getPositionVector
. Si vous voulez ajuster cette distance (2 mètres et non 1, par exemple), modifiez cette valeur selon vos besoins.
Après cette modification, les objets PlaceNode
ajoutés devraient maintenant être orientés selon le bon cap. Vous pouvez maintenant exécuter l'application pour afficher le résultat.
9. Félicitations
Vous êtes arrivés à la fin, félicitations !