1. לפני שמתחילים
תקציר
בשיעור הזה תלמדו איך להשתמש בנתונים מהפלטפורמה של מפות Google כדי להציג מקומות בסביבה במציאות רבודה (AR) ב-Android.
דרישות מוקדמות
- הבנה בסיסית של פיתוח ל-Android באמצעות Android Studio
- היכרות עם Kotlin
מה תלמדו
- בקשת הרשאה מהמשתמש לגשת למצלמה ולמיקום של המכשיר.
- אפשר לשלב עם Places API כדי לאחזר מקומות בקרבת המיקום של המכשיר.
- שילוב עם ARCore כדי למצוא משטחים במישור האופקי, כך שאפשר לעגן ולהציב אובייקטים וירטואליים במרחב תלת-ממדי באמצעות Sceneform.
- איסוף מידע על המיקום של המכשיר במרחב באמצעות SensorManager ושימוש ב-Maps SDK for Android Utility Library כדי למקם אובייקטים וירטואליים בכיוון הנכון.
מה צריך להכין
- Android Studio 2020.3.1 ואילך
- מחשב פיתוח שתומך ב-OpenGL ES 3.0 ומעלה
- מכשיר שתומך ב-ARCore או אמולטור Android עם ARCore (ההוראות מפורטות בשלב הבא)
2. להגדרה
Android Studio
ב-codelab הזה נעשה שימוש ב-Android 10.0 (רמת API 29), ונדרשת התקנה של Google Play Services ב-Android Studio. כדי להתקין את שני יחסי התלות האלה, מבצעים את השלבים הבאים:
- עוברים אל SDK Manager (כלי לניהול SDK). כדי לגשת אליו, לוחצים על Tools (כלים) > SDK Manager (כלי לניהול SDK).
- בודקים אם גרסה Android 10.0 מותקנת. אם לא, מסמנים את התיבה לצד Android 10.0 (Q), לוחצים על אישור ואז שוב על אישור בתיבת הדו-שיח שמופיעה.
- לבסוף, כדי להתקין את Google Play Services, עוברים לכרטיסייה SDK Tools, מסמנים את תיבת הסימון לצד Google Play Services, לוחצים על OK ואז שוב על OK בתיבת הדו-שיח שמופיעה**.**
ממשקי API נדרשים
בשלב 3 בקטע הבא, מפעילים את Maps SDK ל-Android ואת Places API בשביל ה-codelab הזה.
תחילת העבודה עם הפלטפורמה של מפות Google
אם לא השתמשתם בפלטפורמה של מפות Google בעבר, תוכלו לעיין במדריך לתחילת העבודה עם הפלטפורמה של מפות Google או לצפות בפלייליסט לתחילת העבודה עם הפלטפורמה של מפות Google כדי לבצע את השלבים הבאים:
- יוצרים חשבון לחיוב.
- יוצרים פרויקט.
- מפעילים את ממשקי ה-API וערכות ה-SDK של הפלטפורמה של מפות Google (שמופיעים בקטע הקודם).
- יוצרים מפתח API.
אופציונלי: אמולטור Android
אם אין לכם מכשיר שתומך ב-ARCore, אתם יכולים להשתמש במקום זאת באמולטור של Android כדי לדמות סצנת AR וגם לזייף את המיקום של המכשיר. בנוסף, מכיוון שתשתמשו גם ב-Sceneform בתרגיל הזה, תצטרכו לוודא שפעלתם לפי השלבים שבקטע 'הגדרת האמולטור לתמיכה ב-Sceneform'.
3. התחלה מהירה
כדי שתוכלו להתחיל כמה שיותר מהר, הנה קוד התחלתי שיעזור לכם לעקוב אחרי ההוראות ב-codelab הזה. אתם יכולים לדלג לפתרון, אבל אם אתם רוצים לראות את כל השלבים, המשיכו לקרוא.
אפשר לשכפל את המאגר אם git
מותקן.
git clone https://github.com/googlecodelabs/display-nearby-places-ar-android.git
אפשר גם ללחוץ על הכפתור שלמטה כדי להוריד את קוד המקור.
אחרי שמקבלים את הקוד, פותחים את הפרויקט שנמצא בספרייה starter
.
4. סקירה כללית של הפרויקט
בודקים את הקוד שהורדתם בשלב הקודם. במאגר הזה אמור להיות מודול יחיד בשם app
, שמכיל את החבילה com.google.codelabs.findnearbyplacesar
.
AndroidManifest.xml
המאפיינים הבאים מוצהרים בקובץ AndroidManifest.xml
כדי לאפשר לכם להשתמש בתכונות שנדרשות ב-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" />
ל-uses-permission
, שמציין אילו הרשאות המשתמש צריך להעניק לפני שניתן להשתמש ביכולות האלה, מוצהר על הדברים הבאים:
-
android.permission.INTERNET
– כדי שהאפליקציה תוכל לבצע פעולות ברשת ולאחזר נתונים באינטרנט, כמו מידע על מקומות באמצעות Places API. -
android.permission.CAMERA
– נדרשת גישה למצלמה כדי שתוכלו להשתמש במצלמה של המכשיר כדי להציג אובייקטים במציאות רבודה. android.permission.ACCESS_FINE_LOCATION
– נדרשת גישה למיקום כדי שתוכלו לאתר מקומות בקרבת מקום ביחס למיקום המכשיר.
עבור uses-feature
, שבו מצוין אילו תכונות חומרה נדרשות לאפליקציה הזו, מוצהר על הדברים הבאים:
- נדרשת גרסה 3.0 של OpenGL ES.
- נדרש מכשיר עם תמיכה ב-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. -
com.google.maps.android:maps-utils-ktx
היא ספריית ההרחבות של Kotlin (KTX) לספריית כלי העזר של Maps SDK ל-Android. הפונקציונליות הזו תשמש בספרייה הזו כדי למקם אובייקטים וירטואליים במרחב אמיתי בשלב מאוחר יותר. -
com.google.ar.sceneform.ux:sceneform-ux
היא ספריית Sceneform, שתאפשר לכם לעבד סצנות תלת-ממדיות ריאליסטיות בלי שתצטרכו ללמוד OpenGL. - יחסי התלות במזהה הקבוצה
com.squareup.retrofit2
הם יחסי התלות של Retrofit, שמאפשרים לכתוב במהירות לקוח HTTP כדי ליצור אינטראקציה עם Places API.
מבנה הפרויקט
כאן מופיעים הקבצים והחבילות הבאים:
- **api –**החבילה הזו מכילה מחלקות שמשמשות לאינטראקציה עם Places API באמצעות Retrofit.
- **ar—**החבילה הזו מכילה את כל הקבצים שקשורים ל-ARCore.
- **model –**החבילה הזו מכילה מחלקה אחת של נתונים
Place
, שמשמשת להצפנה של מקום יחיד שמוחזר על ידי Places API. - 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. אם לוחצים על Run (הפעלה) > Run ‘app' (הפעלת האפליקציה) בסרגל הכלים ומפעילים את האפליקציה במכשיר או באמולטור, קודם תתבקשו להפעיל את הרשאת המיקום והמצלמה. אפשר ללחוץ על אישור. אחרי שתעשו את זה, אמורות להופיע תצוגת מצלמה ותצוגת מפה זו לצד זו, כמו בתמונה הבאה:
זיהוי מטוסים
כשמסתכלים עם המצלמה על הסביבה, יכול להיות שתראו כמה נקודות לבנות שמופיעות על משטחים אופקיים, כמו הנקודות הלבנות על השטיח בתמונה הזו.
הנקודות הלבנות האלה הן הנחיות ש-ARCore מספק כדי לציין שזוהה מישור אופקי. המישורים שזוהו מאפשרים ליצור מה שנקרא 'עוגן', כדי שתוכלו למקם אובייקטים וירטואליים במרחב אמיתי.
למידע נוסף על ARCore ועל האופן שבו הוא מבין את הסביבה שלכם, אפשר לקרוא על המושגים הבסיסיים שלו.
6. חיפוש מקומות בסביבה
לאחר מכן, תצטרכו לגשת למיקום הנוכחי של המכשיר ולהציג אותו, ואז לאחזר מקומות בקרבת מקום באמצעות Places API.
הגדרת מפות
מפתח Google Maps Platform API
קודם לכן יצרתם מפתח Google Maps Platform API כדי להפעיל שאילתות ב-Places API וכדי שתוכלו להשתמש ב-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
, שמקבלת ארגומנט lambda כדי שיהיה אפשר להעביר מיקום למבצע הקריאה של השיטה הזו.
כדי להשלים את השיטה הזו, אפשר לגשת למאפיין lastLocation
של האובייקט FusedLocationProviderClient
ואז להוסיף addOnSuccessListener
באופן הבא:
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
currentLocation = location
onSuccess(location)
}.addOnFailureListener {
Log.e(TAG, "Could not get location")
}
השיטה getCurrentLocation
מופעלת מתוך ה-lambda שמוגדר ב-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
- קבלת המיקום הנוכחי של המכשיר
- אחזור מקומות בסביבה (במיוחד פארקים) באמצעות Places API
השלב האחרון בתרגיל הזה הוא למקם את המקומות שאתם מאחזרים במציאות רבודה.
הבנת סצנות
ARCore יכול להבין את הסצנה בעולם האמיתי באמצעות המצלמה של המכשיר, על ידי זיהוי נקודות מעניינות וייחודיות שנקראות נקודות מאפיין בכל פריים של התמונה. כשנקודות התכונה האלה מקובצות ונראות כאילו הן נמצאות במישור אופקי משותף, כמו שולחנות ורצפות, ARCore יכול להפוך את התכונה הזו לזמינה לאפליקציה כמישור אופקי.
כפי שראיתם קודם, ARCore עוזר למשתמשים כשהמערכת מזהה משטח על ידי הצגת נקודות לבנות.
הוספת מודעות עוגן
אחרי שמזהים מישור, אפשר לצרף אליו אובייקט שנקרא עוגן. באמצעות עוגן, אפשר למקם אובייקטים וירטואליים ולהבטיח שהאובייקטים האלה יישארו באותו מיקום במרחב. אפשר לשנות את הקוד כדי לצרף מטוס ברגע שהוא מזוהה.
ב-setUpAr
, קובץ OnTapArPlaneListener
מצורף אל PlacesArFragment
. המאזין הזה מופעל בכל פעם שמקישים על מישור בסצנת ה-AR. בתוך הקריאה הזו, אפשר ליצור 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 Utility Library וב-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
ישתמש בו כמיקום המקומי שלו במרחב ה-AR.
מחליפים את הגדרת הכותרת בשיטה הזו בהגדרה הבאה:
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)
}
מיקום מקומי
השלב האחרון כדי למקם את המקומות בצורה נכונה ב-AR הוא להשתמש בתוצאה של 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
בשיטה getPositionVector
. אם רוצים לשנות את המרחק הזה, למשל ל-2 מטרים, אפשר לשנות את הערך לפי הצורך.
בעקבות השינוי, אובייקטים של PlaceNode
שנוספו אמורים להיות מכוונים לכותרת הנכונה. עכשיו אפשר להריץ את האפליקציה ולראות את התוצאה.
9. מזל טוב
כל הכבוד שהגעת עד לכאן!