הצגת מקומות בסביבה ב-AR ב-Android (קוטלין)

1. לפני שמתחילים

מופשט

שיעור Lab זה מלמד אתכם איך להשתמש בנתונים מפלטפורמה של מפות Google כדי להציג מקומות בסביבה במציאות רבודה (AR) ב-Android.

2344909dd9a52c60.png

דרישות מוקדמות

  • הבנה בסיסית של פיתוח ב-Android באמצעות Android Studio
  • היכרות עם קוטלין

מה תלמדו

  • בקשת הרשאה מהמשתמש לגשת למצלמה ולמיקום של המכשיר.
  • שילוב עם places API כדי לשלוף מקומות בקרבת מקום סביב המכשיר.
  • משתלבים עם ARCore כדי למצוא משטחים אופקיים במטוס, כך שאובייקטים וירטואליים יוכלו לעגן אותם ולמקם אותם בחלל תלת-ממדי באמצעות תרחיש.
  • אוספים מידע על מיקום המכשיר בחלל באמצעות SensorManager ומשתמשים ב-Maps SDK for Android Utility Directory כדי למקם אובייקטים וירטואליים בכותרת הנכונה.

מה צריך?

2. להגדרה

Android Studio

Lablab זה משתמש ב-Android 10.0 (רמת API 29) ומחייב התקנה של שירותי Google Play ב-Android Studio. כדי להתקין את שתי התלות האלה, צריך לבצע את השלבים הבאים:

  1. עוברים אל מנהל ה-SDK. כדי לגשת אליו, לוחצים על כלים &gt. SDK Manager.

6c44a9cb9cf6c236.png

  1. בודקים אם Android 10.0 מותקן. אם לא, מתקינים את התיבה על ידי סימון התיבה לצד Android 10.0 (Q) . לאחר מכן לוחצים על אישור ולבסוף לוחצים שוב על אישור בתיבת הדו-שיח שמופיעה.

368f17a974c75c73.png

  1. לסיום, מתקינים את שירותי Google Play דרך הכרטיסייה כלי SDK, מסמנים את התיבה לצד Google Play Services, לוחצים על אישור ולאחר מכן בוחרים שוב באישור בתיבת הדו-שיח שמופיעה**.**

497a954b82242f4b.png

ממשקי API נדרשים

בשלב 3 של הקטע הבא, יש להפעיל את SDK של מפות Google ל-Android ואת API של מקומות Google למעבד הקוד הזה.

תחילת העבודה עם הפלטפורמה של מפות Google

אם לא השתמשתם בעבר בפלטפורמה של מפות Google, יש לבצע את המדריך לתחילת העבודה עם מפות Google או לצפות בפלייליסט של הפלטפורמה של מפות Google כדי להשלים את השלבים הבאים:

  1. יוצרים חשבון לחיוב.
  2. יוצרים פרויקט.
  3. הפעלת ממשקי API וערכות SDK של מפות Google (מפורט בקטע הקודם).
  4. יצירת מפתח API.

אופציונלי: אמולטור Android

אם אין לך מכשיר עם ARCore, אפשר להשתמש באמולטור של Android כדי לדמות סצנה AR וגם לזייף את מיקום המכשיר. בתרגיל הזה עליכם להשתמש גם בתרחיש. עליכם גם לבצע את השלבים המפורטים בקטע ה&שקה (האמולטור) כדי לתמוך בסצנה.

3. התחלה מהירה

כדי לעזור לך להתחיל מהר ככל האפשר, הנה קוד למתחילים שיעזור לך לעקוב אחר שיעור Lab זה. אתם מוזמנים לדלג לפתרון, אבל אם אתם רוצים לראות את כל השלבים, המשיכו לקרוא.

אפשר לשכפל את המאגר אם התקנת את 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 — ההרשאה הזו מאפשרת לאפליקציה לבצע פעולות ברשת ולאחזר נתונים באינטרנט, כמו מידע על מקומות דרך API של מקומות Google.
  • 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 עבור ה-SDK של מפות Google ל-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) ל-SDK של מפות Google ל-Android Utility Directory. הפונקציות ישמשו בספרייה הזו כדי למקם מאוחר יותר אובייקטים וירטואליים בחלל אמיתי.
  • com.google.ar.sceneform.ux:sceneform-ux היא ספריית סצנה, שתאפשר לך לעבד סצנות תלת-ממדיות מציאותיות בלי ללמוד את OpenGL.
  • התלויות במזהה הקבוצה com.squareup.retrofit2 הן תלויות Retrofit, שמאפשרות לך לכתוב במהירות לקוח HTTP כדי לקיים אינטראקציה עם ה-API של 'מקומות'.

מבנה הפרויקט

כאן נמצאות החבילות והקבצים הבאים:

  • **API—**חבילה זו כוללת כיתות המשמשות לאינטראקציה עם ממשק ה-API של 'מקומות' באמצעות Retrofit.
  • **ar—**החבילה הזו מכילה את כל הקבצים הקשורים ל-ARCore.
  • **מודל—**חבילה זו כוללת מחלקת נתונים אחת Place, המשמשת למקיף מקום אחד שמוחזר על ידי ה-API של 'מקומות'.
  • MainActivity.kt – זו האפליקציה Activity היחידה הכלולה באפליקציה, שמציגה מפה ותצוגת מצלמה.

5. הגדרת הסצנה

מתעמקים ברכיבים הבסיסיים של האפליקציה מתחילים בחלקים של המציאות רבודה.

השדה MainActivity מכיל SupportMapFragment, שבו תוצג אובייקט המפה וסיווג משנה של ArFragmentPlacesArFragment, שמציג את סצנת המציאות רבודה.

הגדרת מציאות רבודה

פרט להצגת סצינת המציאות רבודה, PlacesArFragment יטפל גם בבקשה להרשאת מצלמה מהמשתמש אם הוא לא אושר על ידיך. ניתן לבקש הרשאות נוספות על ידי שינוי השיטה getAdditionalPermissions. בנוסף, כדי לקבל הרשאה למיקום, יש לציין זאת ולבטל את השיטה getAdditionalPermissions:

class PlacesArFragment : ArFragment() {

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

להפעלה

עכשיו צריך לפתוח את קוד השלד בספרייה starter ב-Android Studio. אם לוחצים על הפעלה > מפעילים את 'app&#39' מסרגל הכלים ופורסים את האפליקציה למכשיר או לאמולטור. אמורה להופיע בקשה להפעיל את הרשאת המיקום והמצלמה. לאחר מכן לוחצים על אישור. כשעושים זאת, אמורה להופיע תצוגת מצלמה ותצוגת מפה זה לצד זה:

e3e3073d5c86f427.png

זיהוי מטוסים

כשאתם בוחנים את הסביבה שבה אתם נמצאים עם המצלמה שלכם, אתם עשויים לראות שתי נקודות לבנות על המשטחים, כמו למשל הנקודות הלבנות על השטיח בתמונה.

2a9b6ea7dcb2e249.png

הנקודות הלבנות האלה הן הנחיות של ARCore שמצביעות על זיהוי של מטוס אופקי. המטוסים המזוהים האלה מאפשרים לכם ליצור מה ש&יקוּט;&r&עוגן; כך תוכלו למקם אובייקטים וירטואליים בחלל אמיתי.

כדי לקבל מידע נוסף על ARCore ועל האופן שבו הוא מבין את הסביבה שלכם, כדאי לקרוא את המאמר על העקרונות הבסיסיים שלו.

6. קבלת מקומות קרובים

בשלב הבא יהיה עליך לגשת למיקום הנוכחי של המכשיר ולהציג אותו, ולאחר מכן לשלוף מקומות בקרבת מקום באמצעות API של מקומות Google.

הגדרת מפות

מפתח API של מפות Google

מוקדם יותר, יצרת מפתח ל-API של מפות Google כדי להפעיל שאילתות בממשק API של מקומות ולאפשר לך להשתמש ב-SDK של מפות Google ל-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

עד עכשיו, ביצעת את הפעולות הבאות:

  1. המשתמש ביקש הרשאת גישה למצלמה ולמיקום כשהוא הפעיל את האפליקציה בפעם הראשונה
  2. צריך להגדיר את ARCore כדי להתחיל לעקוב אחר מטוסים אופקיים
  3. הגדרת ה-SDK של מפות Google באמצעות מפתח ה-API
  4. מתבצע איתור של המיקום הנוכחי של המכשיר
  5. אחזור מקומות בקרבת מקום (במיוחד פארקים) באמצעות ה-API של מקומות Google

השלב שנותר להשלמת תרגיל זה הוא למקם את המקומות שאתה מאחזר במציאות רבודה.

הבנת סביבת העבודה

באמצעות ARCore ניתן לזהות את הסצנה בעולם האמיתי דרך המצלמה של המכשיר. לשם כך, היא מזהה נקודות מעניינות וייחודיות שנקראות "נקודות" בכל מסגרת של תמונה. כשנקודות אלה מקובצות ומופיעות במטוס אופקי משותף, כמו טבלאות ורצפות, ה-ARCore יכולה להפוך את התכונה הזו לזמינה באפליקציה במטוס אופקי.

כפי שראינו קודם לכן, ARCore עוזרת להנחות את המשתמש כשזוהה מטוס באמצעות הצגת נקודות לבנות.

2a9b6ea7dcb2e249.png

הוספת עוגנים

לאחר זיהוי מטוס, אפשר לצרף אובייקט שנקרא עוגן. בעזרת עוגן אפשר למקם אובייקטים וירטואליים ולהבטיח שהאובייקטים האלה יישארו באותו מיקום בחלל. יש לשנות את הקוד כדי לצרף קוד לאחר זיהוי טיסה.

בsetUpAr, מצורף קובץ OnTapArPlaneListener אל PlacesArFragment. event listener מופעל בכל פעם שמקישים על מטוס בסצנת ה-AR. במהלך השיחה הזו, אפשר ליצור Anchor וAnchorNode מה-HitResult של הפונקציה שפונקציות ההאזנה מאפשרות לך:

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

ה-AnchorNode הוא המקום שבו תצרפו אובייקטים של צומת צאצא – PlaceNode מופעים – בסצנה שבה מתבצע הקריאה בשיטת addPlaces.

הפעלה

אם מפעילים את האפליקציה עם השינויים שלמעלה, צריך להסתכל מסביב עד שמזוהה טיסה. יש להקיש על הנקודות הלבנות שמציינות את המטוס. לאחר מכן, אמורים להופיע סמנים במפה עבור כל הפארקים הקרובים ביותר. עם זאת, אם תשימו לב לאובייקטים הווירטואליים, הם נתקעים בעוגן שנוצר ולא מוקמים במקום שבו הפארקים נמצאים בשטח.

f93eb87c98a0098d

בשלב האחרון, יש לתקן זאת באמצעות SDK של מפות Google ל-Android Utility Directory ו-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 שמכיל פרטים על נתון נתון של חיישן&#31/כפי שהוא משתנה לאורך הזמן. צריך להוסיף את הקוד הבא לשיטה הזו:

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]).

כותרת פוטוספרית

עכשיו, אחרי שצפון נקבע, השלב הבא הוא לקבוע את הזווית בין צפון לכל מקום ואחר כך להשתמש במידע הזה כדי למקם את המקומות בכותרת הנכונה במציאות רבודה.

כדי לחשב את הכותרת, אתם משתמשים ב-SDK של מפות Google ל-Android Utility Directory. הקובץ הזה מכיל כמה פונקציות מסייעות לחישוב מרחקים וכותרות באמצעות גיאומטריה כדורית. מידע נוסף זמין בסקירה הכללית של הספרייה.

לאחר מכן, יש להשתמש בשיטה 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 כך:

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

כברירת מחדל, השיטה getPositionVector מגדירה את מרחק ה-y של הצומת למטר אחד כפי שצוין בערך y בשיטה getPositionVector. אם אתם רוצים לשנות את המרחק הזה, למשל, 2 מטרים, נסו לשנות את הערך הזה לפי הצורך.

לאור השינוי הזה, עכשיו צריך להפנות את האובייקטים הנוספים PlaceNode לכותרת הנכונה. עכשיו אפשר להריץ את האפליקציה ולראות את התוצאה!

9. מזל טוב

כל הכבוד! הגעת ליעד!

למידע נוסף