Informacje o tym ćwiczeniu (w Codelabs)
1. Zanim zaczniesz
Streszczenie
Dzięki nim dowiesz się, jak używać danych z Google Maps Platform do wyświetlania w Androidzie miejsc w pobliżu w rzeczywistości rozszerzonej (AR).
Wymagania wstępne
- Podstawowe informacje o programowaniu na Androida za pomocą Android Studio
- Znajomość Kotlina
Czego się nauczysz:
- Poproś użytkownika o dostęp do aparatu i lokalizacji.
- Zintegruj interfejs Places API, aby pobierać pobliskie miejsca w pobliżu urządzenia.
- Zintegruj narzędzie ARCore, aby znaleźć poziome powierzchnie płaszczyzn, dzięki którym można zakotwiczyć i umieścić obiekty wirtualne w przestrzeni 3D za pomocą Sceneform.
- Zbierz informacje o pozycji urządzenia w przestrzeni kosmicznej, korzystając z narzędzia SensorManager, i użyj biblioteki SDK pakietu SDK na Androida do umieszczenia wirtualnych obiektów we właściwym nagłówku.
Czego potrzebujesz
- Android Studio 2020.3.1 lub nowsza
- Maszyny programistyczne obsługujące platformę OpenGL ES 3.0 lub nowszą
- Urządzenie obsługujące ARCore lub Emulator Androida z obsługą ARCore (instrukcje znajdziesz w następnym kroku).
2. Konfiguracja
Android Studio,
To ćwiczenie wymaga korzystania z Androida 10.0 (poziom API 29) i wymaga zainstalowania Usług Google Play w Android Studio. Aby zainstalować obie te zależności:
- Przejdź do Menedżera SDK, do którego można uzyskać dostęp, klikając Narzędzia > Menedżer pakietów SDK.
- Sprawdź, czy masz zainstalowanego Androida 10.0. Jeśli nie, zainstaluj go, zaznaczając pole wyboru obok Androida 10.0 (Q), a następnie kliknij OK i jeszcze raz kliknij OK w wyświetlonym oknie.
- Na koniec zainstaluj Usługi Google Play. Aby to zrobić, otwórz kartę Narzędzia SDK, zaznacz pole wyboru obok pozycji Usługi Google Play, kliknij OK, a następnie ponownie kliknij OK w wyświetlonym oknie**.
Wymagane interfejsy API
W kroku 3 tej sekcji włącz te ćwiczenia z programowania: Maps SDK for Android i Places API.
Pierwsze kroki z Google Maps Platform
Jeśli korzystasz z Google Maps Platform po raz pierwszy, zapoznaj się z przewodnikiem dla początkujących po Google Maps Platform lub obejrzyj tę playlistę, aby poznać te wskazówki:
- Utwórz konto rozliczeniowe.
- Utwórz projekt.
- Włącz interfejsy API i pakiety SDK Google Maps Platform (znajdziesz je w poprzedniej sekcji).
- Wygeneruj klucz interfejsu API.
Opcjonalnie: emulator Androida
Jeśli nie masz urządzenia z obsługą ARCore, możesz też za pomocą emulatora Androida symulować scenę AR oraz fałszywą lokalizację urządzenia. Biorąc pod uwagę, że w tym ćwiczeniu użyjesz funkcji Sceneform, musisz też wykonać czynności opisane w sekcji „Konfigurowanie emulatora pod kątem obsługi narzędzia Sceneform”."
3. Szybki start
Aby ułatwić Ci rozpoczęcie tego ćwiczenia, skorzystaj z tego kodu, który ułatwia rozpoczęcie ćwiczeń z programowania. Zachęcamy do przejścia do rozwiązania, ale jeśli chcesz zapoznać się ze wszystkimi krokami, czytaj dalej.
Możesz skopiować repozytorium, jeśli masz zainstalowane oprogramowanie git
.
git clone https://github.com/googlecodelabs/display-nearby-places-ar-android.git
Możesz też kliknąć przycisk poniżej, aby pobrać kod źródłowy.
Po pobraniu kodu otwórz projekt znajdujący się w katalogu starter
.
4. Omówienie projektu
Przejrzyj kod pobrany z poprzedniego kroku. W repozytorium znajdziesz pojedynczy moduł o nazwie app
, który zawiera pakiet com.google.codelabs.findnearbyplacesar
.
AndroidManifest.xml
W pliku AndroidManifest.xml
zawarte są te atrybuty, aby umożliwić Ci korzystanie z funkcji wymaganych w tym ćwiczeniu z programowania:
<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" />
W przypadku zasady uses-permission
, która określa uprawnienia, które użytkownik musi przyznać, aby można było korzystać z tych funkcji, są deklarowane:
android.permission.INTERNET
– dzięki temu aplikacja może wykonywać działania sieciowe i pobierać dane, takie jak informacje o miejscach w interfejsie Places API.android.permission.CAMERA
– wymagany jest dostęp do aparatu, aby można było używać kamery urządzenia do wyświetlania obiektów w rzeczywistości rozszerzonej.android.permission.ACCESS_FINE_LOCATION
– potrzebny jest dostęp do lokalizacji, by pobierać miejsca w pobliżu według lokalizacji urządzenia.
W przypadku obiektu uses-feature
określasz, które funkcje sprzętowe są potrzebne do działania tej aplikacji, deklarowane są te elementy:
- Wymagana jest wersja OpenGL ES w wersji 3.0.
- Urządzenie obsługujące ARCore jest wymagane.
Dodatkowo w obiekcie aplikacji są dodawane te tagi metadanych:
<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>
Pierwszy wpis w metatagu zawiera informację, że uruchomienie tej aplikacji jest konieczne, a drugi jest sposób przekazania klucza interfejsu API Google Maps Platform do pakietu SDK Maps na Androida.
build.gradle
W build.gradle
określono te dodatkowe zależności:
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"
}
Oto krótki opis każdej z zależności:
- Biblioteki z identyfikatorem grupy
com.google.android.gms
,play-services-location
iplay-services-maps
, są używane do uzyskiwania dostępu do informacji o lokalizacji urządzenia i dostępu do funkcji związanych z Mapami Google. com.google.maps.android:maps-utils-ktx
to biblioteka Kotlin (KTX) dla Maps SDK for Android Utility Library. Za pomocą tej biblioteki możesz umieścić obiekty wirtualne w przestrzeni wirtualnej później.com.google.ar.sceneform.ux:sceneform-ux
to biblioteka Sceneform, która umożliwia renderowanie realistycznych scen 3D bez konieczności poznawania technologii OpenGL.- Zależności w identyfikatorze grupy
com.squareup.retrofit2
to zależności Retrofit, które umożliwiają szybkie zapisywanie klienta HTTP w celu interakcji z interfejsem Places API.
Struktura projektu
Tutaj znajdziesz te pakiety i pliki:
- **api—**pakiet zawiera klasy używane do interakcji z interfejsem Places API za pomocą Retrofit.
- **ar –**ten pakiet zawiera wszystkie pliki powiązane z ARCore.
- **model—**pakiet zawiera pojedynczą klasę danych
Place
, która służy do hermetyzacji pojedynczego miejsca zwróconego przez interfejs Places API. - MainActivity.kt – pojedynczy
Activity
znajdujący się w aplikacji, który wyświetla mapę i widok kamery.
5. Konfigurowanie sceny
Dowiedz się więcej o podstawowych elementach aplikacji, zaczynając od elementów w rzeczywistości rozszerzonej.
Obiekt MainActivity
zawiera obiekt SupportMapFragment
, który obsługuje wyświetlanie obiektu mapy, oraz podklasę ArFragment
–PlacesArFragment
, która obsługuje scenę w rzeczywistości rozszerzonej.
Konfiguracja rzeczywistości rozszerzonej
Oprócz wyświetlania sceny w rzeczywistości rozszerzonej aplikacja PlacesArFragment
obsługuje też prośby użytkownika o dostęp do aparatu, jeśli nie zostało to jeszcze zrobione. Możesz też poprosić o dodatkowe uprawnienia, zastępując metodę getAdditionalPermissions
. Jeśli potrzebujesz też dostępu do lokalizacji, określ go i zastąp metodę getAdditionalPermissions
:
class PlacesArFragment : ArFragment() {
override fun getAdditionalPermissions(): Array<String> =
listOf(Manifest.permission.ACCESS_FINE_LOCATION)
.toTypedArray()
}
Uruchom
Otwórz szkielet kodu w katalogu starter
w Android Studio. Jeśli na pasku narzędzi klikniesz Uruchom > Uruchom „app'”, a następnie wdrożysz aplikację na urządzeniu lub w emulatorze, zobaczysz prośbę o włączenie dostępu do lokalizacji i aparatu. Kliknij Zezwól. Zobaczysz wtedy widok z aparatu i widok mapy obok siebie:
Wykrywanie samolotów
Gdy patrzysz w otoczenie, możesz zauważyć kilka białych kropek na poziomych powierzchniach, podobnych do białych kropek na dywanu.
Te białe kropki to wskazówki opracowane przez ARCore, które informują o wykryciu płaszczyzny poziomej. Wykryte samoloty umożliwiają tworzenie tzw. &kotwic, dzięki czemu możesz umieszczać obiekty wirtualne w przestrzeni wirtualnej.
Więcej informacji o ARCore i sposobie, w jaki rozumie środowisko w pobliżu, znajdziesz w jego podstawowych koncepcjach.
6. Znajdź miejsca w pobliżu
Następnie trzeba uzyskać dostęp do bieżącej lokalizacji urządzenia i wyświetlić je, a następnie pobrać miejsca w pobliżu za pomocą interfejsu Places API.
Konfiguracja Map
Klucz interfejsu API Google Maps Platform
Wcześniej utworzyliśmy klucz interfejsu API Google Maps Platform, aby umożliwić wysyłanie zapytań do interfejsu Places API i możliwość korzystania z pakietu Maps SDK na Androida. Możesz otworzyć plik gradle.properties
i zastąpić ciąg "YOUR API KEY HERE"
utworzonym kluczem interfejsu API.
Wyświetlaj lokalizację urządzenia na mapie
Po dodaniu klucza interfejsu API dodaj pomocnika na mapie, aby pomóc w określaniu orientacji użytkowników względem tej mapy. Aby to zrobić, przejdź do metody setUpMaps
i w trakcie wywołania mapFragment.getMapAsync
ustaw googleMap.isMyLocationEnabled
na true.
. W ten sposób na mapie wyświetli się niebieska kropka.
private fun setUpMaps() {
mapFragment.getMapAsync { googleMap ->
googleMap.isMyLocationEnabled = true
// ...
}
}
Uzyskiwanie bieżącej lokalizacji
Aby poznać lokalizację urządzenia, musisz użyć klasy FusedLocationProviderClient
. Uzyskanie takiego wystąpienia zostało już wykonane za pomocą metody onCreate
metody MainActivity
. Aby skorzystać z tego obiektu, wypełnij metodę getCurrentLocation
, która akceptuje lambdę, tak by lokalizacja mogła zostać przekazana do wywołującego tę metodę.
Aby ukończyć tę metodę, przejdź do właściwości lastLocation
obiektu FusedLocationProviderClient
i dodaj addOnSuccessListener
w ten sposób:
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
currentLocation = location
onSuccess(location)
}.addOnFailureListener {
Log.e(TAG, "Could not get location")
}
Metoda getCurrentLocation
jest wywoływana z elementu lambda podanego w metodzie getMapAsync
w metodzie setUpMaps
, z której pobierane są miejsca w pobliżu.
Zainicjuj połączenie sieciowe
W wywołaniu metody getNearbyPlaces
pamiętaj, że do metody placesServices.nearbyPlaces
przekazywane są te parametry – klucz interfejsu API, lokalizacja urządzenia, promień w metrach (2 km) i typ miejsca (obecnie ustawiony na park
).
val apiKey = "YOUR API KEY"
placesService.nearbyPlaces(
apiKey = apiKey,
location = "${location.latitude},${location.longitude}",
radiusInMeters = 2000,
placeType = "park"
)
Aby wywołać wywołanie sieciowe, przejdź do klucza interfejsu API zdefiniowanego w pliku gradle.properties
. W pliku build.gradle
w konfiguracji android > defaultConfig jest zdefiniowany ten fragment kodu:
android {
defaultConfig {
resValue "string", "google_maps_key", (project.findProperty("GOOGLE_MAPS_API_KEY") ?: "")
}
}
Dzięki temu wartość zasobu ciągu znaków google_maps_key
będzie dostępna podczas kompilacji.
Aby zakończyć wywołanie sieciowe, możesz po prostu odczytać ten zasób ciągu znaków w obiekcie getString
w obiekcie Context
.
val apiKey = this.getString(R.string.google_maps_key)
7. Miejsca w AR
Do tej pory zostały przez Ciebie wykonane następujące czynności:
- Poprosił użytkownika o dostęp do aparatu i lokalizacji przy pierwszym uruchomieniu aplikacji
- Skonfiguruj ARCore, aby zacząć śledzić samoloty w poziomie
- Konfigurowanie pakietu SDK Map za pomocą klucza interfejsu API
- Otrzymano aktualną lokalizację urządzenia
- Pobrano informacje o miejscach w pobliżu (w szczególności parkach) przy użyciu interfejsu Places API.
Ostatnim krokiem na koniec ćwiczenia jest umieszczenie miejsc w rzeczywistości rozszerzonej.
Zrozumienie scenerii
ARCore jest w stanie zrozumieć rzeczywisty świat dzięki kamerze urządzenia, wykrywając ciekawe i odrębne punkty nazywane punktami cech w każdej klatce. Gdy te punkty cech są zgrupowane i wyglądają na leżące na wspólnej płaszczyźnie poziomej, takiej jak tabele i piętra, ARCore może udostępnić tę aplikację jako platformę poziomą.
Jak widzisz, ARCore pomaga kierować użytkownika po wykryciu samolotu za pomocą białych kropek.
Dodawanie kotwic
Po wykryciu samolotu możesz dołączyć do niego obiekt o nazwie kotwica. Za pomocą kotwicy możesz umieścić obiekty wirtualne i zagwarantować, że pozostaną tam w tym samym miejscu. Możesz zmienić kod, aby dołączyć go po wykryciu samolotu.
W: setUpAr
do elementu PlacesArFragment
jest dołączony OnTapArPlaneListener
. Ten detektor jest wywoływany za każdym razem, gdy na scenie AR jest klikany samolot. Podczas tej rozmowy możesz utworzyć Anchor
i AnchorNode
z podanego HitResult
w detektorze:
arFragment.setOnTapArPlaneListener { hitResult, _, _ ->
val anchor = hitResult.createAnchor()
anchorNode = AnchorNode(anchor)
anchorNode?.setParent(arFragment.arSceneView.scene)
addPlaces(anchorNode!!)
}
AnchorNode
to miejsce, w którym dołączasz obiekty węzłów podrzędnych – PlaceNode
– w scenie, która jest przetwarzana w wywołaniu metody addPlaces
.
Uruchom
Jeśli uruchamiasz aplikację z tymi modyfikacjami, rozejrzyj się wokół i poczekaj, aż zostanie wykryty samolot. Kliknij białe kropki wskazujące samolot. Gdy to zrobisz, na mapie powinny się pojawić znaczniki wszystkich pobliskich parków. Można jednak zauważyć, że obiekty wirtualne utknęły na utworzonej kotwicy i nie zostały umieszczone względem miejsca w parku.
W ostatnim kroku naprawisz to, używając biblioteki SDK Maps na Androida i narzędzia SensorManager na urządzeniu.
8. Pozycjonowanie miejsc
Aby móc umieścić ikonę wirtualnego miejsca w rzeczywistości rozszerzonej na dokładnym nagłówku, musisz podać dwie informacje:
- Gdzie jest północ
- Kąt między północą a poszczególnymi miejscami
Określanie kierunku na północ
Północ można określić za pomocą czujników pozycji (geomagnetyczne i akcelerometr) dostępnych w urządzeniu. Te dwa czujniki zbierają w czasie rzeczywistym informacje o pozycji urządzenia w przestrzeni kosmicznej. Więcej informacji o czujnikach położenia znajdziesz w artykule Obliczanie orientacji urządzenia.
Aby uzyskać dostęp do tych czujników, musisz uzyskać SensorManager
, a następnie zarejestrować SensorEventListener
dla tych czujników. Te kroki są już wykonane za pomocą metod cyklu życia 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)
}
W metodzie onSensorChanged
udostępniany jest obiekt SensorEvent
zawierający szczegółowe informacje o danym punkcie odniesienia w miarę jego upływu czasu. Możesz dodać kod do tej metody:
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)
}
Powyższy kod sprawdza typ czujnika i w zależności od typu aktualizuje odpowiednie odczytu z czujnika (akcelerometr lub magnetometr). Na podstawie tych odczytów z czujników można teraz określić, ile stopni w kierunku północnym jest w porównaniu z urządzeniem (wartość orientationAngles[0]
).
Nagłówek sferyczny
Gdy już ustalisz kierunek północny, kolejnym krokiem jest określenie kąta między północą i każdym miejscem oraz wykorzystanie tych informacji do położenia miejsc w odpowiednim kierunku w rzeczywistości rozszerzonej.
Do obliczenia nagłówka użyj pakietu SDK Map Google na Androida, który zawiera szereg funkcji pomocnych w obliczaniu odległości i nagłówków za pomocą geometrii sferycznej. Więcej informacji znajdziesz w tym omówieniu biblioteki.
Następnie użyjesz metody sphericalHeading
w bibliotece narzędzi, która oblicza nagłówek lub wartość 2 obiektów LatLng
. Te informacje są potrzebne w ramach metody getPositionVector
zdefiniowanej w narzędziu Place.kt
. Ta metoda spowoduje zwrócenie obiektu Vector3
, który będzie używany przez każdy obiekt PlaceNode
jako jego pozycję lokalną w przestrzeni AR.
Możesz zastąpić definicję nagłówka tą metodą:
val heading = latLng.sphericalHeading(placeLatLng)
Powinno to spowodować podanie następującej metody:
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)
}
Pozycja lokalna
Ostatnim krokiem do poprawnej orientacji miejsc w AR jest użycie wyniku getPositionVector
podczas dodawania obiektów PlaceNode
do sceny. Przejdź do sekcji addPlaces
w MainActivity
, tuż pod wierszem, w którym znajduje się element nadrzędny (każdy z nich poniżej placeNode.setParent(anchorNode)
). Ustaw localPosition
urządzenia placeNode
jako wynik wywołania getPositionVector
:
val placeNode = PlaceNode(this, place)
placeNode.setParent(anchorNode)
placeNode.localPosition = place.getPositionVector(orientationAngles[0], currentLocation.latLng)
Domyślnie metoda getPositionVector
ustawia odległość y węzła na 1 metr, określoną przez wartość y
w metodzie getPositionVector
. Jeśli chcesz dostosować tę odległość, powiedz na przykład 2 metry, zmień wartość w razie potrzeby.
Po tej zmianie dodane obiekty PlaceNode
powinny być teraz umieszczone we właściwym nagłówku. Teraz uruchom aplikację, aby sprawdzić wynik.