1. Prima di iniziare
Abstract
Questo codelab ti insegna a utilizzare i dati di Google Maps Platform per visualizzare i luoghi nelle vicinanze in realtà aumentata (AR) su Android.
Prerequisiti
- Conoscenza di base dello sviluppo per Android utilizzando Android Studio
- Familiarità con Kotlin
Cosa imparerai a fare
- Richiedi all'utente l'autorizzazione per accedere alla fotocamera e alla posizione del dispositivo.
- Esegui l'integrazione con l'API Places per recuperare i luoghi nelle vicinanze della posizione del dispositivo.
- Esegui l'integrazione con ARCore per trovare superfici piane orizzontali in modo che gli oggetti virtuali possano essere ancorati e posizionati nello spazio 3D utilizzando Sceneform.
- Raccogli informazioni sulla posizione del dispositivo nello spazio utilizzando SensorManager e utilizza la libreria di utilità di Maps SDK for Android per posizionare gli oggetti virtuali nella direzione corretta.
Che cosa ti serve
- Android Studio 2020.3.1 o versioni successive
- Un computer 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. Configurazione
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 queste dipendenze, completa i seguenti passaggi:
- Vai a SDK Manager, a cui puoi accedere facendo clic su Strumenti > SDK Manager.
- Verifica che sia installato Android 10.0. In caso contrario, installalo selezionando la casella di controllo accanto ad Android 10.0 (Q), poi fai clic su Ok e infine di nuovo su Ok nella finestra di dialogo visualizzata.
- Infine, installa Google Play Services andando alla scheda SDK Tools, seleziona la casella di controllo accanto a Google Play Services, fai clic su Ok e poi di nuovo su Ok nella finestra di dialogo visualizzata**.**
API richieste
Nel passaggio 3 della sezione seguente, abilita Maps SDK for Android e Places API per questo codelab.
Inizia a utilizzare Google Maps Platform
Se non hai mai utilizzato Google Maps Platform, segui la guida Inizia a utilizzare Google Maps Platform o guarda la playlist Inizia a utilizzare Google Maps Platform per completare i seguenti passaggi:
- Crea un account di fatturazione.
- Creare 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, puoi utilizzare in alternativa l'emulatore Android per simulare una scena AR e falsificare la posizione del dispositivo. Dato che in questo esercizio utilizzerai anche Sceneform, dovrai anche assicurarti di seguire i passaggi descritti in "Configurare l'emulatore per supportare Sceneform".
3. Avvio rapido
Per iniziare il più rapidamente possibile, ecco un codice iniziale che ti aiuterà a seguire questo codelab. Puoi passare direttamente alla 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 qui sotto per scaricare il codice sorgente.
Una volta ottenuto il codice, apri il progetto che si trova nella directory starter
.
4. Panoramica del progetto
Esplora il codice scaricato dal passaggio precedente. All'interno di questo repository dovresti trovare un singolo modulo denominato app
, che contiene il pacchetto com.google.codelabs.findnearbyplacesar
.
AndroidManifest.xml
Nel file AndroidManifest.xml
sono dichiarati i seguenti attributi 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 queste funzionalità possano essere utilizzate, vengono dichiarate le seguenti:
android.permission.INTERNET
: in modo che la tua app possa eseguire 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 poterla utilizzare per visualizzare oggetti in realtà aumentata.android.permission.ACCESS_FINE_LOCATION
: l'accesso alla posizione è necessario per recuperare i luoghi nelle vicinanze rispetto alla posizione del dispositivo.
Per uses-feature
, che specifica le funzionalità hardware necessarie per questa app, vengono dichiarate le seguenti:
- È richiesta la versione 3.0 di OpenGL ES.
- È necessario un dispositivo compatibile con ARCore.
Inoltre, vengono aggiunti i seguenti tag di metadati all'oggetto applicazione:
<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 indica che ARCore è un requisito per l'esecuzione di questa app, mentre la seconda indica come fornire la chiave API Google Maps Platform a Maps SDK for Android.
build.gradle
In build.gradle
vengono 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 ogni dipendenza:
- Le librerie con l'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 di estensioni Kotlin (KTX) per la libreria di utilità di Maps SDK for Android. La funzionalità verrà utilizzata in questa libreria per posizionare in un secondo momento gli oggetti virtuali nello spazio reale.com.google.ar.sceneform.ux:sceneform-ux
è la libreria Sceneform, che ti consentirà di eseguire il rendering di scene 3D realistiche senza dover imparare 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 utilizzando Retrofit.
- **ar**: questo pacchetto contiene tutti i file correlati ad ARCore.
- **model**: questo pacchetto contiene una singola classe di dati
Place
, utilizzata per incapsulare un singolo luogo restituito dall'API Places. - MainActivity.kt: è l'unica
Activity
contenuta nella tua app, che mostrerà una mappa e una visualizzazione della videocamera.
5. Configurazione della scena
Esplora i componenti principali dell'app a partire dagli elementi di realtà aumentata.
MainActivity
contiene un SupportMapFragment
, che gestirà la visualizzazione dell'oggetto mappa, e una sottoclasse di un ArFragment
—PlacesArFragment
—che gestisce la visualizzazione della scena di realtà aumentata.
Configurazione della realtà aumentata
Oltre a visualizzare la scena di realtà aumentata, PlacesArFragment
gestirà anche la richiesta di autorizzazione per l'utilizzo della fotocamera da parte dell'utente, se non è già stata concessa. È possibile richiedere autorizzazioni aggiuntive anche eseguendo l'override del metodo getAdditionalPermissions
. Dato che devi anche concedere l'autorizzazione di accesso alla posizione, specifica questa autorizzazione e sostituisci il metodo getAdditionalPermissions
:
class PlacesArFragment : ArFragment() {
override fun getAdditionalPermissions(): Array<String> =
listOf(Manifest.permission.ACCESS_FINE_LOCATION)
.toTypedArray()
}
Run it
Apri il codice di base nella directory starter
in Android Studio. Se fai clic su Esegui > Esegui "app" dalla barra degli strumenti e implementi l'app sul tuo dispositivo o emulatore, ti verrà chiesto prima di attivare l'autorizzazione per la posizione e la fotocamera. Fai clic su Consenti e dovresti visualizzare una visualizzazione della videocamera e una della mappa affiancate, come in questo esempio:
Rilevamento di aerei
Quando guardi l'ambiente in cui ti trovi con la fotocamera, potresti notare un paio di punti bianchi sovrapposti alle superfici orizzontali, un po' 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 consentono di creare un "ancoraggio" per posizionare gli oggetti virtuali nello spazio reale.
Per saperne di più su ARCore e su come comprende l'ambiente circostante, leggi i suoi concetti fondamentali.
6. Trovare luoghi nelle vicinanze
Successivamente, dovrai accedere alla posizione attuale del dispositivo e visualizzarla, per poi recuperare i luoghi nelle vicinanze utilizzando l'API Places.
Configurazione di Maps
Chiave API Google Maps Platform
In precedenza, hai creato una chiave API di Google Maps Platform per abilitare le query dell'API Places e per poter utilizzare Maps SDK for Android. Apri il file gradle.properties
e sostituisci la stringa "YOUR API KEY HERE"
con la chiave API che hai creato.
Visualizzare la posizione del dispositivo sulla mappa
Dopo aver aggiunto la chiave API, aggiungi un indicatore sulla mappa per aiutare gli utenti a orientarsi rispetto alla mappa. Per farlo, vai al metodo setUpMaps
e, all'interno della chiamata mapFragment.getMapAsync
, imposta googleMap.isMyLocationEnabled
su true.
. In questo modo, il punto blu verrà visualizzato sulla mappa.
private fun setUpMaps() {
mapFragment.getMapAsync { googleMap ->
googleMap.isMyLocationEnabled = true
// ...
}
}
Ottenere la posizione attuale
Per ottenere la posizione del dispositivo, dovrai utilizzare la classe FusedLocationProviderClient
. L'ottenimento di un'istanza è già stato eseguito nel metodo onCreate
di MainActivity
. Per utilizzare questo oggetto, compila il metodo getCurrentLocation
, che accetta un argomento lambda in modo che una posizione possa essere passata al chiamante di questo metodo.
Per completare questo metodo, puoi accedere alla proprietà lastLocation
dell'oggetto FusedLocationProviderClient
e aggiungere un addOnSuccessListener
nel seguente modo:
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
currentLocation = location
onSuccess(location)
}.addOnFailureListener {
Log.e(TAG, "Could not get location")
}
Il metodo getCurrentLocation
viene chiamato dall'interno della lambda fornita in getMapAsync
nel metodo setUpMaps
da cui vengono recuperati i luoghi nelle vicinanze.
Avviare una chiamata di rete di luoghi
Nella chiamata al metodo getNearbyPlaces
, tieni presente che i seguenti parametri vengono passati al metodo placesServices.nearbyPlaces
: 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, inserisci la chiave API definita nel 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 stringa google_maps_key
sarà disponibile al momento della compilazione.
Per completare la chiamata di rete, puoi semplicemente leggere questa risorsa 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:
- Richiesta all'utente delle autorizzazioni per la videocamera e la posizione al primo avvio dell'app
- Configurare ARCore per iniziare a monitorare i piani orizzontali
- Configurare l'SDK Maps con la chiave API
- Ha ottenuto la posizione corrente del dispositivo
- Recupero di luoghi nelle vicinanze (in particolare parchi) utilizzando l'API Places
Il passaggio rimanente per completare questo esercizio consiste nel posizionare i luoghi che stai recuperando nella realtà aumentata.
Comprensione della scena
ARCore è in grado di comprendere la scena del mondo reale attraverso la fotocamera del dispositivo rilevando punti interessanti e distinti chiamati punti caratteristici in ogni frame dell'immagine. Quando questi punti caratteristici sono raggruppati e sembrano trovarsi su un piano orizzontale comune, come tavoli e pavimenti, ARCore può rendere questa funzionalità disponibile per l'app come piano orizzontale.
Come hai visto in precedenza, ARCore aiuta a guidare l'utente quando viene rilevato un piano visualizzando dei punti bianchi.
Aggiungere ancore
Una volta rilevato un aereo, puoi collegare un oggetto chiamato ancora. Tramite un ancoraggio, puoi posizionare oggetti virtuali e garantire che rimangano nella stessa posizione nello spazio. Modifica il codice per allegarne una una volta rilevato un aereo.
In setUpAr
, un OnTapArPlaneListener
è collegato a PlacesArFragment
. Questo listener viene richiamato ogni volta che viene toccato un aereo nella scena AR. All'interno di questa chiamata, puoi creare un Anchor
e un AnchorNode
dal HitResult
fornito nel listener nel seguente modo:
arFragment.setOnTapArPlaneListener { hitResult, _, _ ->
val anchor = hitResult.createAnchor()
anchorNode = AnchorNode(anchor)
anchorNode?.setParent(arFragment.arSceneView.scene)
addPlaces(anchorNode!!)
}
AnchorNode
è il punto in cui collegherai gli oggetti nodo secondario, ovvero le istanze PlaceNode
, nella scena gestita nella chiamata al metodo addPlaces
.
Esegui
Se esegui l'app con le modifiche sopra indicate, guardati intorno finché non viene rilevato un aereo. Procedi e tocca i punti bianchi che indicano un aereo. A questo punto, dovresti visualizzare sulla mappa i segnaposto di tutti i parchi più vicini. Tuttavia, noterai che gli oggetti virtuali sono bloccati sull'ancora creata e non sono posizionati rispetto alla posizione dei parchi nello spazio.
Nell'ultimo passaggio, correggerai questo problema utilizzando la libreria di utilità dell'SDK Maps per Android e SensorManager sul dispositivo.
8. Posizionamento dei luoghi
Per poter posizionare l'icona del luogo virtuale in realtà aumentata con una direzione precisa, hai bisogno di due informazioni:
- Dove si trova il nord geografico
- L'angolo tra il nord e ogni luogo
Determinare il nord
Il nord può essere determinato utilizzando i sensori di posizione (geomagnetico e accelerometro) disponibili sul dispositivo. Utilizzando questi due sensori, puoi raccogliere informazioni in tempo reale sulla posizione del dispositivo nello spazio. Per saperne di più sui sensori di posizione, leggi Calcolare l'orientamento del dispositivo.
Per accedere a questi sensori, devi ottenere un SensorManager
e poi registrare un SensorEventListener
su questi sensori. Questi passaggi sono già stati eseguiti per te nei metodi del 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 del dato di un determinato sensore man mano che cambia nel tempo. Aggiungi il seguente codice a questo 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 controlla il tipo di sensore e, a seconda del tipo, aggiorna la lettura del sensore appropriata (accelerometro o magnetometro). Utilizzando queste letture del sensore, ora è possibile determinare il valore di quanti gradi a nord rispetto al dispositivo (ovvero il valore di orientationAngles[0]
).
Intestazione sferica
Ora che è stato determinato il nord, il passaggio successivo consiste nel determinare l'angolo tra il nord e ogni luogo, quindi utilizzare queste informazioni per posizionare i luoghi nella direzione corretta in realtà aumentata.
Per calcolare la direzione, utilizzerai la libreria di utilità di Maps SDK for Android, che contiene una serie di funzioni di assistenza per il calcolo di distanze e direzioni tramite la geometria sferica. Per ulteriori informazioni, leggi questa panoramica della libreria.
A questo punto, utilizzerai il metodo sphericalHeading
nella libreria di utilità, che calcola la direzione/il rilevamento tra due oggetti LatLng
. Queste informazioni sono necessarie all'interno del metodo getPositionVector
definito in Place.kt
. Questo metodo restituirà infine un oggetto Vector3
, che verrà poi utilizzato da ogni PlaceNode
come posizione locale nello spazio AR.
Procedi e sostituisci la definizione dell'intestazione in questo metodo con la seguente:
val heading = latLng.sphericalHeading(placeLatLng)
In questo modo, dovresti ottenere 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 consiste nell'utilizzare il risultato di getPositionVector
quando gli oggetti PlaceNode
vengono aggiunti alla scena. Vai a addPlaces
in MainActivity
, subito sotto la riga in cui è impostato il genitore per ogni placeNode
(subito sotto placeNode.setParent(anchorNode)
). Imposta localPosition
di placeNode
sul risultato della chiamata a getPositionVector
come segue:
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 su 1 metro, come specificato dal valore y
nel metodo getPositionVector
. Se vuoi regolare questa distanza, ad esempio a 2 metri, modifica il valore in base alle tue esigenze.
Con questa modifica, gli oggetti PlaceNode
aggiunti ora dovrebbero essere orientati nella direzione corretta. Ora esegui l'app per vedere il risultato.
9. Complimenti
Congratulazioni per aver raggiunto questo traguardo.