1. Prima di iniziare
Abstract
Questo codelab ti insegna come utilizzare i dati di Google Maps Platform per mostrare i luoghi nelle vicinanze in realtà aumentata (AR) su Android.
Prerequisiti
- Conoscenza di base dello sviluppo di Android utilizzando Android Studio
- Familiarità con Kotlin
Cosa imparerai a fare:
- Richiedi all'utente l'autorizzazione ad accedere alla fotocamera e alla posizione del dispositivo.
- Esegui l'integrazione con l'API Places per recuperare i luoghi nelle vicinanze intorno alla posizione del dispositivo.
- Esegui l'integrazione con ARCore per trovare superfici piane orizzontali in modo da poter ancorare e posizionare gli oggetti virtuali in uno spazio 3D utilizzando Sceneform.
- Raccogli informazioni sulla posizione del dispositivo nello spazio utilizzando SensorManager e utilizza la Libreria Maps SDK for Android Utility per posizionare gli oggetti virtuali nell'intestazione corretta.
Che cosa ti serve
- Android Studio 2020.3.1 o versioni successive
- Una macchina di sviluppo che supporta OpenGL ES 3.0 o versioni successive
- Un dispositivo supportato da ARCore o un emulatore Android abilitato per ARCore (le istruzioni sono fornite nel passaggio successivo).
2. Configura
Android Studio
Questo codelab utilizza Android 10.0 (livello API 29) e richiede l'installazione di Google Play Services in Android Studio. Per installare entrambe le dipendenze, completa i seguenti passaggi:
- Vai a SDK Manager, a cui puoi accedere facendo clic su Strumenti > SDK Manager.
- Controlla se Android 10.0 è installato. In caso contrario, installala selezionando la casella di controllo accanto ad Android 10.0 (Q), fai clic su OK e infine su OK nella finestra di dialogo visualizzata.
- Infine, installa Google Play Services accedendo alla scheda Strumenti SDK, seleziona la casella di controllo accanto a Google Play Services, fai clic su OK, quindi seleziona nuovamente OK nella finestra di dialogo visualizzata**.
API obbligatorie
Nel passaggio 3 della sezione seguente, attiva l'SDK Maps per Android e l'API Places per questo codelab.
Inizia a utilizzare Google Maps Platform
Se non hai mai utilizzato Google Maps Platform, segui la guida introduttiva a Google Maps Platform o guarda la playlist Introduzione a Google Maps Platform per completare la seguente procedura:
- Crea un account di fatturazione.
- Crea un progetto.
- Abilita le API e gli SDK di Google Maps Platform (elencati nella sezione precedente).
- Genera una chiave API.
Facoltativo: emulatore Android
Se non hai un dispositivo supportato da ARCore, in alternativa puoi utilizzare l'emulatore Android per simulare una scena AR e simulare la posizione del tuo dispositivo. Dato che utilizzerai anche Sceneform in questo esercizio, dovrai anche assicurarti di seguire i passaggi descritti nella sezione "Configurare l'emulatore per supportare Sceneform".
3. Avvio rapido
Per iniziare il più rapidamente possibile, ecco un codice iniziale per aiutarti a seguire questo codelab. Ti diamo il benvenuto nella soluzione, ma se vuoi vedere tutti i passaggi, continua a leggere.
Puoi clonare il repository se hai installato git
.
git clone https://github.com/googlecodelabs/display-nearby-places-ar-android.git
In alternativa, puoi fare clic sul pulsante di seguito per scaricare il codice sorgente.
Dopo avere ottenuto il codice, apri il progetto che si trova all'interno della directory starter
.
4. Panoramica del progetto
Esplora il codice che hai scaricato nel passaggio precedente. All'interno di questo repository, dovresti trovare un singolo modulo denominato app
, che contiene il pacchetto com.google.codelabs.findnearbyplacesar
.
AndroidManifest.xml
I seguenti attributi sono dichiarati nel file AndroidManifest.xml
per consentirti di utilizzare le funzionalità richieste in questo codelab:
<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" />
Per uses-permission
, che specifica quali autorizzazioni devono essere concesse dall'utente prima che tali funzionalità possano essere utilizzate, vengono dichiarate le seguenti autorizzazioni:
android.permission.INTERNET
, in modo che la tua app possa effettuare operazioni di rete e recuperare dati su Internet, ad esempio informazioni sui luoghi tramite l'API Places.android.permission.CAMERA
: è necessario l'accesso alla fotocamera per utilizzare la fotocamera del dispositivo per visualizzare oggetti in realtà aumentata.android.permission.ACCESS_FINE_LOCATION
: è necessario l'accesso alla posizione per poter recuperare luoghi nelle vicinanze in relazione alla posizione del dispositivo.
Per uses-feature
, che specifica quali funzionalità hardware sono richieste dall'app, vengono dichiarate le seguenti informazioni:
- È necessaria OpenGL ES versione 3.0.
- È necessario un dispositivo che supporta ARCore.
Inoltre, sotto l'oggetto applicazione vengono aggiunti i seguenti tag di metadati:
<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 prima voce di metadati consiste nell'indicare che ARCore è un requisito per l'esecuzione di questa app, mentre la seconda è come fornisci la chiave API di Google Maps Platform all'SDK di Maps per Android.
build.gradle
In build.gradle
sono specificate le seguenti dipendenze aggiuntive:
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"
}
Ecco una breve descrizione di ciascuna dipendenza:
- Le biblioteche con ID gruppo
com.google.android.gms
, ovveroplay-services-location
eplay-services-maps
, vengono utilizzate per accedere alle informazioni sulla posizione del dispositivo e alle funzionalità relative a Google Maps. com.google.maps.android:maps-utils-ktx
è la libreria Kotlin (KTX) della Maps SDK for Android Utility Library. La funzionalità verrà utilizzata in questa libreria per posizionare gli oggetti virtuali in un secondo momento nello spazio reale.com.google.ar.sceneform.ux:sceneform-ux
è la libreria Sceneform, che ti consente di eseguire il rendering di scene 3D realistiche senza dover imparare a utilizzare OpenGL.- Le dipendenze all'interno dell'ID gruppo
com.squareup.retrofit2
sono le dipendenze Retrofit, che ti consentono di scrivere rapidamente un client HTTP per interagire con l'API Places.
Struttura del progetto
Qui troverai i seguenti pacchetti e file:
- **api:**questo pacchetto contiene classi utilizzate per interagire con l'API Places mediante Retrofit.
- **ar—**questo pacchetto contiene tutti i file correlati ad ARCore.
- **model:**questo pacchetto contiene un'unica classe di dati
Place
, che viene utilizzata per incapsulare un singolo luogo restituito dall'API Places. - MainActivity.kt: il singolo
Activity
contenuto nella tua app, che mostrerà una mappa e la visualizzazione di una fotocamera.
5. Configurazione della scena
Esplora i componenti fondamentali dell'app partendo dagli elementi in realtà aumentata.
MainActivity
contiene un SupportMapFragment
, che gestisce la visualizzazione dell'oggetto mappa e una sottoclasse di un elemento ArFragment
-PlacesArFragment
, che gestisce la visualizzazione della scena della realtà aumentata.
Configurazione della realtà aumentata
Oltre a mostrare la scena della realtà aumentata, PlacesArFragment
gestirà anche la richiesta di autorizzazione della fotocamera da parte dell'utente se non è già stata concessa. È inoltre possibile richiedere autorizzazioni aggiuntive eseguendo l'override del metodo getAdditionalPermissions
. Poiché ti occorre anche l'autorizzazione di accesso alla posizione, specificala e sostituisci il metodo getAdditionalPermissions
:
class PlacesArFragment : ArFragment() {
override fun getAdditionalPermissions(): Array<String> =
listOf(Manifest.permission.ACCESS_FINE_LOCATION)
.toTypedArray()
}
Esegui
Procedi e apri il codice di scheletro nella directory starter
in Android Studio. Se fai clic su Esegui > Esegui "app' dalla barra degli strumenti ed esegui il deployment dell'app sul tuo dispositivo o emulatore, ti dovrebbe essere prima chiesto di attivare l'autorizzazione di accesso alla posizione e alla fotocamera. Quindi, fai clic su Consenti e, una volta eseguita l'operazione, dovresti vedere una visualizzazione della fotocamera e una affiancata della seguente mappa:
Rilevamento dei piani
Osservando l'ambiente circostante con la videocamera, potresti notare un paio di punti bianchi sovrapposti sulle superfici orizzontali, come i punti bianchi sul tappeto in questa immagine.
Questi punti bianchi sono linee guida fornite da ARCore per indicare che è stato rilevato un piano orizzontale. Questi piani rilevati ti permettono di creare un "anchor" in modo da poter posizionare gli oggetti virtuali nello spazio reale.
Per saperne di più su ARCore e su come comprende l'ambiente circostante, leggi i nostri concetti fondamentali.
6. Trova luoghi nelle vicinanze
A questo punto, dovrai accedere alla posizione corrente del dispositivo e visualizzarla e recuperarla utilizzando l'API Places.
Configurazione di Maps
Chiave API di Google Maps Platform
In precedenza, hai creato una chiave API di Google Maps Platform per consentire di eseguire query sull'API Places e per poter utilizzare l'SDK di Maps per Android. Apri il file gradle.properties
e sostituisci la stringa "YOUR API KEY HERE"
con la chiave API che hai creato.
Visualizza la posizione del dispositivo sulla mappa
Una volta aggiunta la chiave API, aggiungi un helper sulla mappa per aiutare gli utenti a orientarsi in relazione alla mappa. A tale scopo, vai al metodo setUpMaps
e, nella chiamata di mapFragment.getMapAsync
, imposta googleMap.isMyLocationEnabled
su true.
. Il punto blu sulla mappa verrà visualizzato.
private fun setUpMaps() {
mapFragment.getMapAsync { googleMap ->
googleMap.isMyLocationEnabled = true
// ...
}
}
Ottieni la posizione attuale
Per ottenere la posizione del dispositivo, dovrai utilizzare la classe FusedLocationProviderClient
. L'istanza di questo problema è già stata eseguita nel metodo onCreate
di MainActivity
. Per utilizzare questo oggetto, compila il metodo getCurrentLocation
, che accetta un argomento lambda in modo che un indirizzo possa essere trasmesso a un chiamante di questo metodo.
Per completare questo metodo, puoi accedere alla proprietà lastLocation
dell'oggetto FusedLocationProviderClient
seguito da un addOnSuccessListener
come segue:
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
currentLocation = location
onSuccess(location)
}.addOnFailureListener {
Log.e(TAG, "Could not get location")
}
Il metodo getCurrentLocation
viene chiamato dall'interno del lambda fornito in getMapAsync
nel metodo setUpMaps
da cui vengono recuperati i luoghi nelle vicinanze.
Avvia chiamata di rete luoghi
Nella chiamata del metodo getNearbyPlaces
, tieni presente che i seguenti parametri vengono passati al metodo placesServices.nearbyPlaces
, ovvero una chiave API, la posizione del dispositivo, un raggio in metri (impostato su 2 km) e un tipo di luogo (attualmente impostato su park
).
val apiKey = "YOUR API KEY"
placesService.nearbyPlaces(
apiKey = apiKey,
location = "${location.latitude},${location.longitude}",
radiusInMeters = 2000,
placeType = "park"
)
Per completare la chiamata di rete, procedi e trasmetti la chiave API che hai definito nel tuo file gradle.properties
. Il seguente snippet di codice è definito nel file build.gradle
nella configurazione android > defaultConfig:
android {
defaultConfig {
resValue "string", "google_maps_key", (project.findProperty("GOOGLE_MAPS_API_KEY") ?: "")
}
}
In questo modo, il valore della risorsa della stringa google_maps_key
sarà disponibile al momento della creazione.
Per completare la chiamata di rete, puoi semplicemente leggere questa risorsa di stringa tramite getString
sull'oggetto Context
.
val apiKey = this.getString(R.string.google_maps_key)
7. Luoghi in AR
Finora hai eseguito le seguenti operazioni:
- Autorizzazioni fotocamera e posizione richieste all'utente la prima volta che viene eseguita l'app
- Configura ARCore per iniziare a monitorare i piani orizzontali
- Configura l'SDK di Maps con la chiave API
- Ha rilevato la posizione corrente del dispositivo
- Luoghi nelle vicinanze recuperati (in particolare parchi) mediante l'API Places
Il passaggio rimanente per completare questo esercizio consiste nel posizionare i luoghi che stai recuperando in realtà aumentata.
Comprensione della scena
ARCore è in grado di comprendere la scena reale attraverso la fotocamera del dispositivo rilevando punti interessanti e distinti, chiamati punti funzione, in ogni frame dell'immagine. Quando questi punti di funzionalità vengono raggruppati in base a un piano orizzontale comune, ad esempio tabelle e piani, ARCore può rendere disponibile questa funzionalità all'app come piano orizzontale.
Come hai visto in precedenza, ARCore aiuta l'utente a rilevare un aereo mostrando dei punti bianchi.
Aggiungere ancoraggi
Dopo aver rilevato un piano, puoi allegare un oggetto chiamato ancoraggio. Tramite un ancoraggio, puoi posizionare oggetti virtuali e garantire che sembrino rimanere nella stessa posizione nello spazio. Modifica il codice per allegarne uno dopo il rilevamento di un aereo.
In setUpAr
, OnTapArPlaneListener
è associato a PlacesArFragment
. Questo listener viene richiamato ogni volta che un aereo viene toccato nella scena AR. All'interno di questa chiamata, puoi creare un Anchor
e un AnchorNode
dal HitResult
fornito nell'ascoltatore, in questo modo:
arFragment.setOnTapArPlaneListener { hitResult, _, _ ->
val anchor = hitResult.createAnchor()
anchorNode = AnchorNode(anchor)
anchorNode?.setParent(arFragment.arSceneView.scene)
addPlaces(anchorNode!!)
}
AnchorNode
è il punto in cui collegherai oggetti nodo secondari (istanze PlaceNode
) nella scena gestita nella chiamata al metodo addPlaces
.
Esegui
Se esegui l'app con le modifiche sopra indicate, guardati intorno fino a quando non viene rilevato un aereo. Procedi a toccare i punti bianchi che indicano un aereo. In questo modo, dovresti vedere sulla mappa gli indicatori relativi a tutti i parchi più vicini. Tuttavia, se noterai che gli oggetti virtuali sono bloccati sull'ancoraggio creato e non posizionati rispetto a dove si trovano questi parchi nello spazio.
Per l'ultimo passaggio, devi correggerlo utilizzando l'SDK Maps per Android e la funzionalità SensorManager del dispositivo.
8. Posizione dei luoghi
Per poter posizionare l'icona del luogo virtuale in realtà aumentata in un'intestazione precisa, avrai bisogno di due informazioni:
- Dove il nord è vero
- L'angolo tra nord e ogni luogo
Determinare il nord
Il nord può essere determinato utilizzando i sensori di posizione (geomagnetici e accelerometro) disponibili sul dispositivo. Utilizzando questi due sensori, puoi raccogliere informazioni in tempo reale sulla posizione del dispositivo nello spazio. Per ulteriori informazioni sui sensori di posizione, consulta l'articolo Eseguire il calcolo dell'orientamento del dispositivo.
Per accedere a questi sensori, dovrai ottenere un SensorManager
seguito da un SensorEventListener
registrato su questi sensori. Questi passaggi sono già stati eseguiti nei metodi di ciclo di vita di 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)
}
Nel metodo onSensorChanged
viene fornito un oggetto SensorEvent
, che contiene i dettagli sull'origine di un determinato sensore quando cambia nel tempo. Aggiungi il seguente codice al metodo:
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)
}
Il codice riportato sopra consente di verificare il tipo di sensore e, a seconda del tipo, aggiornerà la lettura del sensore appropriata (la lettura dell'accelerometro o del magnetometro). Utilizzando queste letture del sensore, è possibile determinare il valore di quanti gradi rispetto al nord rispetto al dispositivo (ovvero il valore di orientationAngles[0]
).
Intestazione sferica
Ora che è stato determinato il nord, il passaggio successivo è determinare l'angolo tra il nord e ogni luogo seguito, utilizzando queste informazioni, per posizionare i luoghi nell'intestazione corretta nella realtà aumentata.
Per calcolare l'intestazione, utilizzerai l'SDK Maps per Android Utility Library, che contiene una serie di funzioni helper per il calcolo di distanze e intestazioni tramite la geometria sferica. Per ulteriori informazioni, leggi questa panoramica della libreria.
Successivamente, utilizzerai il metodo sphericalHeading
nella libreria di utilità, che calcola l'intestazione/cuscinetto tra due oggetti LatLng
. Queste informazioni sono necessarie all'interno del metodo getPositionVector
definito in Place.kt
. Questo metodo restituirà un oggetto Vector3
, che sarà poi utilizzato da ogni PlaceNode
come posizione locale nello spazio AR.
Sostituisci la definizione dell'intestazione in questo metodo con il seguente:
val heading = latLng.sphericalHeading(placeLatLng)
Tale operazione dovrebbe comportare la seguente definizione del metodo:
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)
}
Posizione locale
L'ultimo passaggio per orientare correttamente i luoghi in AR è utilizzare il risultato di getPositionVector
quando vengono aggiunti oggetti PlaceNode
alla scena. Procedi su addPlaces
in MainActivity
, subito sotto la riga in cui è impostato il genitore su ogni placeNode
(proprio sotto placeNode.setParent(anchorNode)
). Imposta il localPosition
di placeNode
sul risultato di chiamare getPositionVector
in questo modo:
val placeNode = PlaceNode(this, place)
placeNode.setParent(anchorNode)
placeNode.localPosition = place.getPositionVector(orientationAngles[0], currentLocation.latLng)
Per impostazione predefinita, il metodo getPositionVector
imposta la distanza y del nodo a 1 metro come specificato dal valore y
nel metodo getPositionVector
. Se vuoi regolare questa distanza, ad esempio su 2 metri, vai avanti e modifica il valore in base alle tue esigenze.
Con questa modifica, gli oggetti PlaceNode
aggiunti ora dovrebbero essere orientati nell'intestazione corretta. Ora procedi ed esegui l'app per vedere il risultato.
9. Complimenti
Congratulazioni per aver raggiunto questa destinazione!