1. Прежде чем начать
Абстрактный
В этой лабораторной работе вы узнаете, как использовать данные платформы Google Карт для отображения близлежащих мест в дополненной реальности (AR) на Android.
Предпосылки
- Базовое понимание разработки Android с использованием Android Studio
- Знакомство с Kotlin
Чему вы научитесь
- Запросите у пользователя разрешение на доступ к камере и местоположению устройства.
- Интеграция с API Places для получения информации о местах поблизости от местонахождения устройства.
- Интеграция с ARCore для поиска горизонтальных плоских поверхностей, чтобы виртуальные объекты можно было закрепить и разместить в трехмерном пространстве с помощью Sceneform .
- Собирайте информацию о положении устройства в пространстве с помощью SensorManager и используйте библиотеку служебных программ Maps SDK for Android для позиционирования виртуальных объектов в правильном направлении.
Что вам понадобится
- Android Studio 2020.3.1 или выше
- Машина для разработки, поддерживающая OpenGL ES 3.0 или выше
- Устройство с поддержкой ARCore или эмулятор Android с поддержкой ARCore (инструкции приведены в следующем шаге)
2. Настройте
Android Studio
Эта лабораторная работа использует Android 10.0 (API уровня 29) и требует установки сервисов Google Play в Android Studio. Чтобы установить обе эти зависимости, выполните следующие действия:
- Перейдите в диспетчер SDK, доступ к которому можно получить, нажав «Инструменты» > «Диспетчер SDK» .
- Проверьте, установлена ли версия Android 10.0. Если нет, установите её, установив флажок рядом с Android 10.0 (Q) , затем нажмите «ОК» , а затем ещё раз нажмите «ОК» в появившемся диалоговом окне.
- Наконец, установите службы Google Play, перейдя на вкладку «Инструменты SDK» , установите флажок рядом с пунктом «Службы Google Play» , нажмите «ОК» , затем снова нажмите «ОК» в появившемся диалоговом окне**.**
Требуемые API
На шаге 3 следующего раздела включите Maps SDK для Android и Places API для этой кодовой лаборатории.
Начните работу с платформой Google Карт
Если вы ранее не использовали платформу Google Карт, следуйте руководству «Начало работы с платформой Google Карт» или посмотрите плейлист «Начало работы с платформой Google Карт», чтобы выполнить следующие шаги:
- Создайте платежный аккаунт.
- Создать проект.
- Включите API и SDK платформы Google Карт (перечислены в предыдущем разделе).
- Сгенерируйте ключ API.
Дополнительно: эмулятор Android
Если у вас нет устройства с поддержкой ARCore, вы можете использовать эмулятор Android для имитации сцены дополненной реальности и поддельного определения местоположения устройства. Поскольку в этом упражнении вы также будете использовать Sceneform , вам также необходимо выполнить шаги, описанные в разделе «Настройка эмулятора для поддержки Sceneform».
3. Быстрый старт
Чтобы вы могли начать как можно быстрее, вот пример кода, который поможет вам разобраться в этой лабораторной работе. Вы можете сразу перейти к решению, но если хотите увидеть все этапы, продолжайте читать.
Вы можете клонировать репозиторий, если у вас установлен git
.
git clone https://github.com/googlecodelabs/display-nearby-places-ar-android.git
Или вы можете нажать кнопку ниже, чтобы загрузить исходный код.
Получив код, откройте проект, находящийся в starter
каталоге.
4. Обзор проекта
Изучите код, скачанный на предыдущем шаге. В этом репозитории вы найдете единственный модуль с именем app
, содержащий пакет com.google.codelabs.findnearbyplacesar
.
AndroidManifest.xml
В файле AndroidManifest.xml
объявлены следующие атрибуты, позволяющие использовать функции, необходимые в этой лабораторной работе:
<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" />
Для uses-permission
, который определяет, какие разрешения должен предоставить пользователь, прежде чем эти возможности могут быть использованы, объявляются следующие:
-
android.permission.INTERNET
— это необходимо для того, чтобы ваше приложение могло выполнять сетевые операции и получать данные через Интернет, например информацию о местах через API Places. -
android.permission.CAMERA
— доступ к камере необходим, чтобы вы могли использовать камеру устройства для отображения объектов в дополненной реальности. -
android.permission.ACCESS_FINE_LOCATION
— доступ к местоположению необходим для получения информации о близлежащих местах относительно местоположения устройства.
Для uses-feature
, который указывает, какие аппаратные функции необходимы этому приложению, объявлены следующие:
- Требуется OpenGL ES версии 3.0.
- Требуется устройство с поддержкой ARCore.
Кроме того, в объект приложения добавляются следующие теги метаданных:
<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>
Первая запись метаданных указывает, что ARCore является обязательным условием для запуска этого приложения, а вторая — как вы предоставляете свой ключ API платформы Google Карт в Maps SDK для Android.
build.gradle
В build.gradle
указаны следующие дополнительные зависимости:
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"
}
Вот краткое описание каждой зависимости:
- Библиотеки с идентификатором группы
com.google.android.gms
, а именноplay-services-location
иplay-services-maps
, используются для доступа к информации о местоположении устройства и доступа к функциям, связанным с Google Maps. -
com.google.maps.android:maps-utils-ktx
— это библиотека расширений Kotlin (KTX) для библиотеки утилит Maps SDK for Android. Функциональность этой библиотеки будет использоваться для последующего позиционирования виртуальных объектов в реальном пространстве. -
com.google.ar.sceneform.ux:sceneform-ux
— это библиотека Sceneform , которая позволит вам визуализировать реалистичные 3D-сцены без необходимости изучения OpenGL. - Зависимости внутри идентификатора группы
com.squareup.retrofit2
— это зависимости Retrofit , которые позволяют быстро написать HTTP-клиент для взаимодействия с API Places.
Структура проекта
Здесь вы найдете следующие пакеты и файлы:
- **api—**этот пакет содержит классы, которые используются для взаимодействия с API Places с помощью Retrofit.
- **ar—**этот пакет содержит все файлы, связанные с ARCore.
- **model—**этот пакет содержит один класс данных
Place
, который используется для инкапсуляции одного места, возвращаемого API Places. - MainActivity.kt — это единственное
Activity
, содержащееся в вашем приложении, которое будет отображать карту и вид с камеры.
5. Подготовка сцены
Погрузитесь в основные компоненты приложения, начиная с элементов дополненной реальности.
MainActivity
содержит SupportMapFragment
, который будет обрабатывать отображение объекта карты, и подкласс ArFragment
— PlacesArFragment
, который обрабатывает отображение сцены дополненной реальности.
Настройка дополненной реальности
Помимо отображения сцены дополненной реальности, PlacesArFragment
также обрабатывает запросы на разрешение камеры у пользователя, если оно ещё не предоставлено. Дополнительные разрешения также можно запросить, переопределив метод getAdditionalPermissions
. Учитывая, что вам также необходимо разрешение на определение местоположения, укажите его и переопределите метод getAdditionalPermissions
:
class PlacesArFragment : ArFragment() {
override fun getAdditionalPermissions(): Array<String> =
listOf(Manifest.permission.ACCESS_FINE_LOCATION)
.toTypedArray()
}
Запусти это
Откройте исходный код в каталоге starter
в Android Studio. Если вы нажмёте «Запустить» > «Запустить приложение» на панели инструментов и развернёте приложение на устройстве или эмуляторе, вам сначала будет предложено включить доступ к данным о местоположении и камере. Нажмите «Разрешить» . После этого вы увидите вид с камеры и вид с карты рядом, как показано ниже:
Обнаружение самолетов
Осмотрев обстановку, в которой вы находитесь со своей камерой, вы можете заметить несколько белых точек, наложенных на горизонтальные поверхности, вроде белых точек на ковре на этом снимке.
Эти белые точки — направляющие, предоставляемые ARCore, указывающие на обнаружение горизонтальной плоскости. Эти обнаруженные плоскости позволяют создать так называемую «точку опоры» для позиционирования виртуальных объектов в реальном пространстве.
Для получения более подробной информации об ARCore и о том, как эта технология распознает окружающую среду, прочтите о ее основных концепциях .
6. Найдите места поблизости
Далее вам необходимо получить доступ к текущему местоположению устройства и отобразить его, а затем получить информацию о близлежащих местах с помощью API Places .
Настройка карт
API-ключ платформы Google Карт
Ранее вы создали ключ API платформы Google Карт, чтобы выполнять запросы к API Places и использовать Maps SDK для Android. Откройте файл gradle.properties
и замените строку "YOUR API KEY HERE"
на созданный вами ключ API.
Отображение местоположения устройства на карте
После добавления ключа API добавьте на карту вспомогательный элемент, который поможет пользователям ориентироваться на карте. Для этого перейдите к методу setUpMaps
и внутри вызова mapFragment.getMapAsync
установите для свойства googleMap.isMyLocationEnabled
значение true.
Это отобразит синюю точку на карте.
private fun setUpMaps() {
mapFragment.getMapAsync { googleMap ->
googleMap.isMyLocationEnabled = true
// ...
}
}
Получить текущее местоположение
Чтобы получить местоположение устройства, необходимо использовать класс FusedLocationProviderClient
. Получение его экземпляра уже выполнено в методе onCreate
объекта MainActivity
. Чтобы использовать этот объект, заполните метод getCurrentLocation
, который принимает лямбда-аргумент, чтобы местоположение можно было передать вызывающей стороне.
Для завершения этого метода вы можете получить доступ к свойству lastLocation
объекта FusedLocationProviderClient
, а затем добавить addOnSuccessListener
следующим образом:
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
currentLocation = location
onSuccess(location)
}.addOnFailureListener {
Log.e(TAG, "Could not get location")
}
Метод getCurrentLocation
вызывается из лямбда-функции, предоставленной в getMapAsync
в методе setUpMaps
, из которого извлекаются близлежащие места.
Инициировать сетевой вызов мест
Обратите внимание, что при вызове метода getNearbyPlaces
в метод placesServices.nearbyPlaces
передаются следующие параметры: ключ API, местоположение устройства, радиус в метрах (который установлен равным 2 км) и тип места (в настоящее время установлен как park
).
val apiKey = "YOUR API KEY"
placesService.nearbyPlaces(
apiKey = apiKey,
location = "${location.latitude},${location.longitude}",
radiusInMeters = 2000,
placeType = "park"
)
Чтобы завершить сетевой вызов, передайте ключ API, указанный в файле gradle.properties
. Следующий фрагмент кода определён в файле build.gradle
в разделе конфигурации android > defaultConfig :
android {
defaultConfig {
resValue "string", "google_maps_key", (project.findProperty("GOOGLE_MAPS_API_KEY") ?: "")
}
}
Это сделает значение строкового ресурса google_maps_key
доступным во время сборки.
Чтобы завершить сетевой вызов, вы можете просто прочитать этот строковый ресурс через getString
в объекте Context
.
val apiKey = this.getString(R.string.google_maps_key)
7. Места в AR
На данный момент вы сделали следующее:
- При первом запуске приложения у пользователя запрашиваются разрешения на доступ к камере и местоположению.
- Настройте ARCore для отслеживания горизонтальных плоскостей
- Настройте Maps SDK с помощью вашего ключа API
- Получено текущее местоположение устройства.
- Получены близлежащие места (в частности, парки) с помощью API Places.
Оставшийся шаг для завершения этого упражнения — позиционировать места, которые вы выбираете, в дополненной реальности.
Понимание сцены
ARCore способен анализировать реальную сцену с помощью камеры устройства, обнаруживая на каждом кадре изображения интересные и отличительные точки, называемые характерными точками. Когда эти характерные точки группируются и, по-видимому, лежат в одной горизонтальной плоскости, например, столы и полы, ARCore может сделать эту особенность доступной приложению в виде горизонтальной плоскости.
Как вы видели ранее, ARCore помогает пользователю ориентироваться при обнаружении самолета, отображая белые точки.
Добавление якорей
После обнаружения самолёта вы можете прикрепить объект, называемый якорем . С помощью якоря вы можете размещать виртуальные объекты, гарантируя, что они будут сохранять своё положение в пространстве. Измените код, чтобы прикрепить объект после обнаружения самолёта.
В setUpAr
к PlacesArFragment
прикреплён прослушиватель OnTapArPlaneListener
. Этот прослушиватель вызывается при каждом касании плоскости в сцене дополненной реальности. В этом вызове можно создать Anchor
и AnchorNode
на основе предоставленного HitResult
в прослушивателе следующим образом:
arFragment.setOnTapArPlaneListener { hitResult, _, _ ->
val anchor = hitResult.createAnchor()
anchorNode = AnchorNode(anchor)
anchorNode?.setParent(arFragment.arSceneView.scene)
addPlaces(anchorNode!!)
}
AnchorNode
— это место, куда вы будете прикреплять объекты дочерних узлов — экземпляры PlaceNode
— к сцене, что обрабатывается при вызове метода addPlaces
.
Запусти это
Если вы запустите приложение с указанными выше модификациями, осматривайтесь вокруг, пока не обнаружите самолёт. Нажмите на белые точки, обозначающие самолёт. После этого на карте должны появиться маркеры всех ближайших парков. Однако, если вы заметили, виртуальные объекты привязаны к созданному якорю, а не размещены относительно местоположения этих парков в пространстве.
На последнем шаге вы исправите это, используя библиотеку служебных программ Maps SDK for Android и SensorManager на устройстве.
8. Размещение мест
Чтобы иметь возможность позиционировать значок виртуального места в дополненной реальности по точному направлению, вам понадобятся две информации:
- Где находится настоящий север
- Угол между севером и каждым местом
Определение севера
Север можно определить с помощью датчиков положения (геомагнитного и акселерометра), имеющихся на устройстве. С помощью этих двух датчиков можно собирать информацию о положении устройства в пространстве в режиме реального времени. Подробнее о датчиках положения см. в статье «Вычисление ориентации устройства» .
Для доступа к этим датчикам вам необходимо получить SensorManager
, а затем зарегистрировать SensorEventListener
для этих датчиков. Эти шаги уже выполнены в методах жизненного цикла 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)
}
В методе onSensorChanged
предоставляется объект SensorEvent
, содержащий информацию об изменении данных датчика с течением времени. Добавьте в этот метод следующий код:
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)
}
Приведённый выше код проверяет тип датчика и, в зависимости от типа, обновляет показания соответствующего датчика (акселерометра или магнитометра). Используя эти показания, теперь можно определить значение угла в градусах от севера относительно устройства (т.е. значение orientationAngles[0]
).
Сферический заголовок
Теперь, когда север определен, следующим шагом будет определение угла между севером и каждым местом, а затем использование этой информации для позиционирования мест в правильном направлении в дополненной реальности.
Для вычисления направления движения вы воспользуетесь библиотекой утилит Maps SDK for Android, которая содержит ряд вспомогательных функций для вычисления расстояний и направлений с использованием сферической геометрии. Подробнее см. в этом обзоре библиотеки .
Далее мы воспользуемся sphericalHeading
из библиотеки утилит, который вычисляет курс/пеленг между двумя объектами LatLng
. Эта информация необходима для метода getPositionVector
, определённого в Place.kt
. Этот метод в конечном итоге вернёт объект Vector3
, который затем будет использоваться каждым PlaceNode
в качестве локального положения в пространстве дополненной реальности.
Продолжайте и замените определение заголовка в этом методе следующим:
val heading = latLng.sphericalHeading(placeLatLng)
В результате должно получиться следующее определение метода:
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)
}
Местная позиция
Последний шаг для правильной ориентации мест в дополненной реальности — использование результата getPositionVector
при добавлении объектов PlaceNode
на сцену. Перейдите к addPlaces
в MainActivity
, прямо под строкой, где задаётся родительский элемент для каждого placeNode
(сразу под placeNode.setParent(anchorNode)
). Установите localPosition
для placeNode
равным результату вызова getPositionVector
следующим образом:
val placeNode = PlaceNode(this, place)
placeNode.setParent(anchorNode)
placeNode.localPosition = place.getPositionVector(orientationAngles[0], currentLocation.latLng)
По умолчанию метод getPositionVector
устанавливает расстояние Y до узла равным 1 метру, как указано в значении y
в методе getPositionVector
. Если вы хотите изменить это расстояние, например, до 2 метров, измените это значение по мере необходимости.
Благодаря этому изменению добавленные объекты PlaceNode
теперь должны быть ориентированы в правильном направлении. Теперь запустите приложение и посмотрите на результат!
9. Поздравления
Поздравляю с тем, что вы добрались так далеко!