Orte in der Nähe in AR auf Android-Geräten anzeigen (Kotlin)

1. Hinweis

Zusammenfassung

In diesem Codelab lernen Sie, wie Sie mithilfe von Daten aus der Google Maps Platform Orte in der Nähe von Augmented Reality (AR) auf Android-Geräten anzeigen können.

2344909dd9a52c60.png

Vorbereitung

  • Grundlegendes Verständnis der Android-Entwicklung mit Android Studio
  • Vertrautheit mit Kotlin

Lerninhalte

  • Fordern Sie die Berechtigung des Nutzers an, auf die Kamera und den Standort des Geräts zuzugreifen.
  • In die Places API integrieren, um Orte in der Nähe vom Standort des Geräts abzurufen
  • Verwenden Sie ARCore, um horizontale Oberflächen zu finden und virtuelle Objekte so zu verankern und in 3D-Raum mithilfe von Sceneform zu platzieren.
  • Sammeln Sie mit SensorManager Informationen zur Position des Geräts im Raum und verwenden Sie die Maps SDK for Android-Dienstprogrammbibliothek, um die virtuellen Objekte in die richtige Richtung zu verschieben.

Voraussetzungen

2. Einrichten

Android Studio

Für dieses Codelab wird Android 10.0 (API-Level 29) verwendet. Außerdem müssen Google Play-Dienste in Android Studio installiert sein. Wenn Sie beide Abhängigkeiten installieren möchten, führen Sie die folgenden Schritte aus:

  1. Klicke auf SDK, um auf den SDK Manager zuzugreifen. Klicke auf Tools.

6c44a9cb9cf6c236

  1. Prüfen Sie, ob Android 10.0 installiert ist. Falls nicht, installieren Sie sie, indem Sie das Kästchen neben Android 10.0 (Q) anklicken, dann auf OK und schließlich im eingeblendeten Dialogfeld auf OK klicken.

368f17a974c75c73

  1. Installieren Sie zuletzt Google Play-Dienste, indem Sie den Tab SDK Tools aufrufen, das Kästchen neben Google Play-Dienste anklicken, auf OK und dann im daraufhin angezeigten Dialogfeld noch einmal auf OK** klicken.

497a954b82242f4b

Erforderliche APIs

Aktivieren Sie in Schritt 3 des folgenden Abschnitts Maps SDK for Android und Places API für dieses Codelab.

Einführung in Google Maps Platform

Wenn du die Google Maps Platform noch nicht verwendet hast, folge der Einführung in die Google Maps Platform oder folge der Anleitung unter Erste Schritte mit der Google Maps Platform-Playlist.

  1. Erstellen Sie ein Rechnungskonto.
  2. Projekt erstellen
  3. Aktivieren Sie die im vorherigen Abschnitt aufgeführten Google Maps Platform APIs und SDKs.
  4. Generieren Sie den API-Schlüssel.

Optional: Android-Emulator

Wenn du kein ARCore-fähiges Gerät hast, kannst du alternativ den Android Emulator verwenden, um eine AR-Szene zu simulieren und den Standort deines Geräts zu simulieren. Hinweis: Da du in dieser Übung auch Sceneform verwendest, musst du außerdem die Schritte unter „"Konfigurieren des Emulators für Sceneform“ ausführen.

3. Schnelleinstieg

Damit Sie so schnell wie möglich loslegen können, erhalten Sie hier einen Startcode, den Sie bei diesem Codelab nutzen können. Sie können gerne zur Lösung wechseln. Wenn Sie sich jedoch alle Schritte ansehen möchten, lesen Sie weiter.

Sie können das Repository klonen, wenn git installiert ist.

git clone https://github.com/googlecodelabs/display-nearby-places-ar-android.git

Alternativ können Sie auf die Schaltfläche unten klicken, um den Quellcode herunterzuladen.

Nachdem Sie den Code abgerufen haben, können Sie das Projekt im Verzeichnis starter öffnen.

4. Projektübersicht

Sehen Sie sich den Code an, den Sie im vorherigen Schritt heruntergeladen haben. In diesem Repository finden Sie das Modul app, das das Paket com.google.codelabs.findnearbyplacesar enthält.

AndroidManifest.xml

Die folgenden Attribute sind in der Datei AndroidManifest.xml deklariert, damit Sie die in diesem Codelab erforderlichen Funktionen verwenden können:

<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" />

Für uses-permission wird festgelegt, welche Berechtigungen vom Nutzer erteilt werden müssen, damit diese Funktionen verwendet werden können. Dazu werden folgende Berechtigungen angegeben:

  • android.permission.INTERNET: So kann Ihre App Netzwerkvorgänge ausführen und Daten über das Internet abrufen, z. B. über die Places API.
  • android.permission.CAMERA: Kamerazugriff ist erforderlich, damit Sie die Kamera des Geräts mit Augmented Reality verwenden können.
  • android.permission.ACCESS_FINE_LOCATION: Standortzugriff ist erforderlich, damit du Orte in der Nähe relativ zum Standort des Geräts abrufen kannst.

Für uses-feature wird angegeben, welche Hardwarefunktionen von dieser App benötigt werden. Dazu wird Folgendes angegeben:

  • OpenGL ES-Version 3.0 ist erforderlich.
  • ARCore-fähiges Gerät ist erforderlich.

Außerdem werden unter dem Anwendungsobjekt die folgenden Metadaten-Tags hinzugefügt:

<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>

Der erste Meta-Dateneintrag gibt an, dass ARCore zur Ausführung dieser App erforderlich ist. Zum anderen geben Sie an, wie Sie Ihren Google Maps Platform API-Schlüssel für das Maps SDK for Android bereitstellen.

build.gradle

In build.gradle sind die folgenden zusätzlichen Abhängigkeiten angegeben:

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"
}

Kurze Beschreibung jeder Abhängigkeit:

  • Die Bibliotheken mit der Gruppen-ID com.google.android.gms, also play-services-location und play-services-maps, werden verwendet, um auf Standortinformationen des Geräts und die mit Google Maps verbundenen Funktionen zuzugreifen.
  • com.google.maps.android:maps-utils-ktx ist die Kotlin-Erweiterung (KTX) für die Maps SDK for Android-Dienstprogrammbibliothek. Die Funktion wird in dieser Bibliothek verwendet, um virtuelle Objekte später in Echtzeit zu positionieren.
  • com.google.ar.sceneform.ux:sceneform-ux ist die Bibliothek Sceneform, mit der Sie realistische 3D-Szenen rendern können, ohne OpenGL zu lernen.
  • Die Abhängigkeiten innerhalb der Gruppen-ID com.squareup.retrofit2 sind die Retrofit-Abhängigkeiten, mit denen Sie schnell einen HTTP-Client für die Interaktion mit der Places API schreiben können.

Projektstruktur

Hier finden Sie die folgenden Pakete und Dateien:

  • **api**: Dieses Paket enthält Klassen, die für die Interaktion mit der Places API über Retrofit verwendet werden.
  • **ar—**Dieses Paket enthält alle Dateien, die mit ARCore zusammenhängen.
  • **model**: Dieses Paket enthält eine einzelne Datenklasse Place, die dazu verwendet wird, einen einzelnen Ort zu kapseln, der von der Places API zurückgegeben wird.
  • Hauptaktivität.kt: Das ist die einzige Activity in Ihrer App, die eine Karte und eine Kameraansicht darstellt.

5. Szene einrichten

Hier erfährst du mehr über die Hauptkomponenten der App – beginnend mit den Augmented-Reality-Elementen.

MainActivity enthält eine SupportMapFragment, die das Kartenobjekt verarbeitet, und eine Unterklasse von ArFragmentPlacesArFragment, die die Darstellung der Augmented Reality-Szene verarbeitet.

Augmented-Reality-Einrichtung

Abgesehen von der Augmented-Reality-Szene verarbeitet PlacesArFragment auch die Kameraberechtigung vom Nutzer, sofern er diese Berechtigung noch nicht erhält. Sie können auch zusätzliche Berechtigungen anfordern, indem Sie die Methode getAdditionalPermissions überschreiben. Da Sie auch die Berechtigung zur Standortermittlung benötigen, geben Sie diese an und überschreiben Sie die getAdditionalPermissions-Methode:

class PlacesArFragment : ArFragment() {

   override fun getAdditionalPermissions(): Array<String> =
       listOf(Manifest.permission.ACCESS_FINE_LOCATION)
           .toTypedArray()
}

Ausführen

Öffnen Sie in Android Studio den Skeleton-Code im Verzeichnis starter. Wenn Sie in der Symbolleiste auf Ausführen &Ausführen & App #39; klicken und die App auf Ihrem Gerät oder Emulator bereitstellen, sollten Sie zuerst aufgefordert werden, die Standort- und Kameraberechtigung zu aktivieren. Klicken Sie auf Zulassen. Anschließend sollten Sie eine Kamera- und eine Kartenansicht nebeneinander sehen:

e3e3073d5c86f427

Flugzeuge erkennen

Wenn Sie die Umgebung erkunden, in der Sie die Kamera stecken, sehen Sie möglicherweise einige weiße Punkte auf horizontalen Oberflächen, ähnlich wie die weißen Punkte auf dem Teppich.

2a9b6ea7dcb2e249.png

Diese weißen Punkte werden von ARCore bereitgestellt und geben an, dass eine horizontale Ebene erkannt wurde. Mit diesen erkannten Ebenen können Sie sogenannte Ankerpunkte erstellen, um virtuelle Objekte im richtigen Raum zu positionieren.

Weitere Informationen zu ARCore und zu Ihrer Umgebung finden Sie im Artikel zu den grundlegenden Konzepten.

6. Orte in der Nähe abrufen

Als Nächstes musst du auf den aktuellen Standort des Geräts zugreifen und diesen anzeigen. Danach werden Orte in der Nähe über die Places API abgerufen.

Google Maps einrichten

Google Maps Platform API-Schlüssel

Du hast einen Google Maps Platform API-Schlüssel erstellt, mit dem du die Places API abfragen und das Maps SDK for Android nutzen kannst. Öffne dazu die Datei gradle.properties und ersetze den String "YOUR API KEY HERE" durch den von dir erstellten API-Schlüssel.

Standort des Geräts auf der Karte anzeigen

Nachdem Sie den API-Schlüssel hinzugefügt haben, können Sie einen Hilfsassistenten auf der Karte einfügen, mit dem sich die Orientierung des Nutzers in Bezug auf die Karte festlegen lässt. Rufen Sie dazu die Methode setUpMaps auf und legen Sie im mapFragment.getMapAsync-Aufruf googleMap.isMyLocationEnabled auf true. fest. Dadurch wird der blaue Punkt auf der Karte angezeigt.

private fun setUpMaps() {
   mapFragment.getMapAsync { googleMap ->
       googleMap.isMyLocationEnabled = true
       // ...
   }
}

Aktuellen Standort abrufen

Zum Abrufen des Gerätestandorts musst du die Klasse FusedLocationProviderClient verwenden. Eine solche Instanz wurde bereits in der onCreate-Methode von MainActivity abgerufen. Wenn Sie dieses Objekt verwenden möchten, füllen Sie die Methode getCurrentLocation aus, die ein Lambda-Argument akzeptiert, damit ein Standort an den Anrufer dieser Methode weitergegeben werden kann.

Um diese Methode auszuführen, können Sie auf die lastLocation-Property des FusedLocationProviderClient-Objekts zugreifen und dann ein addOnSuccessListener-Objekt so hinzufügen:

fusedLocationClient.lastLocation.addOnSuccessListener { location ->
    currentLocation = location
    onSuccess(location)
}.addOnFailureListener {
    Log.e(TAG, "Could not get location")
}

Die Methode getCurrentLocation wird aus dem Lambda aufgerufen, das in getMapAsync für die Methode setUpMaps angegeben wurde, aus der die Orte in der Nähe abgerufen werden.

Netzwerkanrufe für Orte starten

Beachten Sie im Aufruf der Methode getNearbyPlaces, dass die folgenden Parameter an die placesServices.nearbyPlaces-Methode übergeben werden: ein API-Schlüssel, der Standort des Geräts, ein Radius in Metern (auf 2 km festgelegt) und ein Ortstyp (derzeit auf park eingestellt).

val apiKey = "YOUR API KEY"
placesService.nearbyPlaces(
   apiKey = apiKey,
   location = "${location.latitude},${location.longitude}",
   radiusInMeters = 2000,
   placeType = "park"
)

Um den Netzwerkaufruf abzuschließen, übergeben Sie den API-Schlüssel, den Sie in der Datei gradle.properties definiert haben. Das folgende Code-Snippet ist in der Datei build.gradle unter der Konfiguration android > defaultConfig definiert:

android {
   defaultConfig {
       resValue "string", "google_maps_key", (project.findProperty("GOOGLE_MAPS_API_KEY") ?: "")
   }
}

Dadurch wird der String-Ressourcenwert google_maps_key zur Erstellungszeit verfügbar.

Zum Abschluss des Netzwerkaufrufs können Sie diese Stringressource über getString im Context-Objekt lesen.

val apiKey = this.getString(R.string.google_maps_key)

7. Orte in AR

Bisher hast du Folgendes getan:

  1. Nutzer hat bei der ersten Ausführung der App Kamera- und Standortberechtigungen angefordert
  2. ARCore zum Tracking horizontaler Flugzeuge einrichten
  3. Maps SDK mit deinem API-Schlüssel einrichten
  4. Aktuellen Standort des Geräts erhalten
  5. Orte in der Nähe (insbesondere Parks) mithilfe der Places API abgerufen

Der letzte Schritt zum Abschluss dieser Übung besteht darin, die Orte, die Sie abrufen, in Augmented Reality zu positionieren.

Szenenverstehen

ARCore erkennt in der Kamera des Geräts die reale Szene, indem in jedem Bildrahmen interessante und spezifische Punkte genannt werden. Wenn diese Funktionspunkte gruppiert sind und scheinbar auf einer gemeinsamen horizontalen Ebene liegen, z. B. Tische und Böden, kann ARCore diese Funktion als horizontale Ebene für die App verfügbar machen.

Wie Sie bereits gesehen haben, zeigt der ARCore Nutzern, wenn ein Flugzeug erkannt wurde, anhand von weißen Punkten an.

2a9b6ea7dcb2e249.png

Anker hinzufügen

Sobald eine Flugzeuge erkannt wurden, können Sie ein Ankerobjekt anhängen. Mithilfe eines Ankers können Sie virtuelle Objekte platzieren und so garantieren, dass diese Objekte an derselben Position im Projektbereich bleiben. Ändern Sie den Code so, dass nach Abschluss eines Flugzeugs ein Anhang angehängt wird.

In setUpAr ist OnTapArPlaneListener an PlacesArFragment angehängt. Dieser Listener wird immer dann aufgerufen, wenn ein Flugzeug in der AR-Szene angetippt wird. In diesem Aufruf können Sie eine Anchor und eine AnchorNode aus der bereitgestellten HitResult im Listener erstellen:

arFragment.setOnTapArPlaneListener { hitResult, _, _ ->
   val anchor = hitResult.createAnchor()
   anchorNode = AnchorNode(anchor)
   anchorNode?.setParent(arFragment.arSceneView.scene)
   addPlaces(anchorNode!!)
}

An AnchorNode fügen Sie untergeordnete Knoten (PlaceNode-Instanzen) in der Szene hinzu, die im Methodenaufruf addPlaces verarbeitet wird.

Ausführen

Wenn Sie die App mit den oben genannten Änderungen ausführen, sehen Sie sich um, bis ein Flugzeug erkannt wurde. Tippe dazu auf die weißen Punkte, die ein Flugzeug kennzeichnen. Jetzt sollten Sie auf der Karte Markierungen für alle nächstgelegenen Parks sehen. Wenn Sie jedoch bemerken, bleiben die virtuellen Objekte am Anker, der erstellt wurde, und werden nicht an der Stelle positioniert, an der sich diese Parks befinden.

f93eb87c98a0098d

Zuletzt können Sie das Problem mit der Maps SDK for Android-Dienstprogrammbibliothek und dem SensorManager auf dem Gerät beheben.

8. Orte positionieren

Damit das Symbol für den virtuellen Ort in einer Augmented Reality-Position genau angezeigt werden kann, benötigen Sie zwei Informationen:

  • Wo ist der Norden?
  • Der Winkel zwischen dem Norden und jedem Ort

Nord bestimmen

Der Norden kann mithilfe der Positionssensoren (geomagnetisch und Beschleunigungsmesser) auf dem Gerät ermittelt werden. Mit diesen beiden Sensoren kannst du Echtzeitinformationen zur Position des Geräts im Raum erheben. Weitere Informationen zu Positionssensoren findest du unter Ausrichtung des Geräts.

Zum Zugriff auf diese Sensoren benötigst du ein SensorManager, gefolgt von einem SensorEventListener für diese Sensoren. Im Folgenden sind diese Schritte der Lebenszyklusmethoden für MainActivity aufgeführt:

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)
}

In der onSensorChanged-Methode wird ein SensorEvent-Objekt bereitgestellt, das Details zu einem bestimmten Sensordatum enthält, wenn diese sich im Laufe der Zeit ändern. Fügen Sie dazu folgenden Code hinzu:

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)
}

Der Code oben prüft den Sensortyp und aktualisiert, abhängig vom Typ, den geeigneten Sensorwert (entweder den Beschleunigungsmesser oder den Magnetometer). Mit diesen Sensorwerten kann jetzt der Wert von Norden relativ zum Gerät relativ zum Wert orientationAngles[0] ermittelt werden.

Kugelüberschrift

Nachdem der Norden bestimmt wurde, muss der Winkel zwischen dem Norden und jedem Ort bestimmt werden. Anschließend werden die Informationen verwendet, um die Orte in der Augmented-Reality-Position in der richtigen Richtung zu positionieren.

Um die Überschrift zu berechnen, verwenden Sie die Maps SDK for Android-Dienstprogrammbibliothek, die einige hilfreiche Funktionen zum Berechnen von Entfernungen und Richtungen über sphärische Geometrie enthält. Weitere Informationen finden Sie in dieser Übersicht der Bibliothek.

Als Nächstes verwendest du die Methode sphericalHeading in der Dienstprogrammbibliothek, mit der die Überschrift/der Lage zwischen zwei LatLng-Objekten berechnet wird. Diese Informationen sind in der getPositionVector-Methode erforderlich, die in Place.kt definiert ist. Bei dieser Methode wird schließlich ein Vector3-Objekt zurückgegeben, das dann von jedem PlaceNode-Element als lokale Position im AR-Bereich verwendet wird.

Ersetzen Sie einfach die Überschriftdefinition in dieser Methode durch Folgendes:

val heading = latLng.sphericalHeading(placeLatLng)

Das Ergebnis sollte die folgende Methodendefinition haben:

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)
}

Lokale Position

Der letzte Schritt für die korrekte Ausrichtung in AR ist die Verwendung des Ergebnisses getPositionVector, wenn PlaceNode-Objekte zur Szene hinzugefügt werden. Gehen Sie zu addPlaces in MainActivity direkt unter der Zeile, in der das Element auf den einzelnen placeNode festgelegt ist (direkt unter placeNode.setParent(anchorNode)). Legen Sie die localPosition von placeNode auf das Ergebnis von getPositionVector fest:

val placeNode = PlaceNode(this, place)
placeNode.setParent(anchorNode)
placeNode.localPosition = place.getPositionVector(orientationAngles[0], currentLocation.latLng)

Standardmäßig wird von der Methode getPositionVector die y-Entfernung des Knotens auf 1 Meter festgelegt, wie durch den Wert y in der Methode getPositionVector festgelegt. Wenn du den Abstand ändern möchtest, etwa auf 2 Meter, ändere diesen Wert nach Bedarf.

Durch diese Änderung müssen hinzugefügte PlaceNode-Objekte jetzt in der richtigen Überschrift ausgerichtet sein. Führe jetzt die App aus, um das Ergebnis zu sehen.

9. Glückwunsch

Glückwunsch!

Weitere Informationen