Android'de AR'de yakındaki yerleri göster (Kotlin)

1. Başlamadan önce

Özet (Abstract)

Bu codelab'de, Android'de artırılmış gerçeklik (AR) kullanarak yakındaki yerleri göstermek için Google Haritalar Platformu'ndaki verilerin nasıl kullanılacağı açıklanmaktadır.

2344909dd9a52c60.png

Ön koşullar

  • Android Studio ile Android geliştirme konusunda temel bilgi
  • Kotlin hakkında bilgi

Neler öğreneceksiniz?

  • Cihazın kamerasına ve konumuna erişmek için kullanıcıdan izin isteyin.
  • Cihazın konumunun etrafındaki yerleri getirmek için Places API ile entegre edin.
  • Sanal nesnelerin Sceneform kullanılarak 3D alana yerleştirilebilmesi ve sabitlenebilmesi için yatay düzlem yüzeylerini bulmak üzere ARCore ile entegrasyon.
  • SensorManager kullanarak cihazın uzaydaki konumu hakkında bilgi toplayın ve sanal nesneleri doğru yöne yerleştirmek için Android için Haritalar SDK'sı Yardımcı Kitaplığı'nı kullanın.

Gerekenler

  • Android Studio 2020.3.1 veya sonraki sürümler
  • OpenGL ES 3.0 veya sonraki sürümleri destekleyen bir geliştirme makinesi
  • ARCore destekli bir cihaz veya ARCore'un etkinleştirildiği bir Android emülatörü (talimatlar sonraki adımda verilmiştir)

2. Hazırlanın

Android Studio

Bu codelab'de Android 10.0 (API düzeyi 29) kullanılır ve Android Studio'da Google Play Hizmetleri'nin yüklü olması gerekir. Bu bağımlılıkların ikisini de yüklemek için aşağıdaki adımları tamamlayın:

  1. Tools (Araçlar) > SDK Manager'ı (SDK Yöneticisi) tıklayarak erişebileceğiniz SDK Yöneticisi'ne gidin.

6c44a9cb9cf6c236.png

  1. Android 10.0'ın yüklü olup olmadığını kontrol edin. Aksi takdirde, Android 10.0 (Q)'nun yanındaki onay kutusunu işaretleyip Tamam'ı, ardından açılan iletişim kutusunda tekrar Tamam'ı tıklayarak yükleyin.

368f17a974c75c73.png

  1. Son olarak, SDK Tools sekmesine giderek Google Play Hizmetleri'ni yükleyin, Google Play Hizmetleri'nin yanındaki onay kutusunu işaretleyin, Tamam'ı tıklayın ve ardından açılan iletişim kutusunda tekrar Tamam'ı seçin**.**

497a954b82242f4b.png

Gerekli API'ler

Aşağıdaki bölümün 3. adımında bu codelab için Android için Haritalar SDK'sı ve Places API'yi etkinleştirin.

Google Haritalar Platformu'nu kullanmaya başlayın

Google Haritalar Platformu'nu daha önce kullanmadıysanız aşağıdaki adımları tamamlamak için Google Haritalar Platformu'nu Kullanmaya Başlama Kılavuzu'nu inceleyin veya Google Haritalar Platformu'nu Kullanmaya Başlama oynatma listesini izleyin:

  1. Faturalandırma hesabı oluşturun.
  2. Proje oluşturun.
  3. Google Haritalar Platformu API'lerini ve SDK'larını (önceki bölümde listelenmiştir) etkinleştirin.
  4. API anahtarı oluşturun.

İsteğe bağlı: Android Emulator

ARCore destekli bir cihazınız yoksa alternatif olarak Android Emulator'ı kullanarak bir AR sahnesini simüle edebilir ve cihazınızın konumunu sahte olarak ayarlayabilirsiniz. Bu alıştırmada Sceneform'u da kullanacağınız için "Configure the emulator to support Sceneform " (Sceneform'u destekleyecek şekilde emülatörü yapılandırma) bölümündeki adımları da uygulamanız gerekir.

3. Hızlı başlangıç

Mümkün olduğunca hızlı bir şekilde başlamanıza yardımcı olmak için bu codelab'i takip etmenize yardımcı olacak başlangıç kodunu aşağıda bulabilirsiniz. Çözüme geçebilirsiniz ancak tüm adımları görmek istiyorsanız okumaya devam edin.

git yüklüyse depoyu klonlayabilirsiniz.

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

Alternatif olarak, kaynak kodunu indirmek için aşağıdaki düğmeyi tıklayabilirsiniz.

Kodu aldıktan sonra starter dizininde bulunan projeyi açın.

4. Projeye genel bakış

Önceki adımda indirdiğiniz kodu inceleyin. Bu deponun içinde, com.google.codelabs.findnearbyplacesar paketini içeren app adlı tek bir modül bulunur.

AndroidManifest.xml

Bu codelab'de gerekli özellikleri kullanabilmeniz için AndroidManifest.xml dosyasında aşağıdaki özellikler tanımlanır:

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

Bu özelliklerin kullanılabilmesi için kullanıcının hangi izinleri vermesi gerektiğini belirten uses-permission için aşağıdakiler beyan edilir:

  • android.permission.INTERNET: Bu izin, uygulamanızın ağ işlemleri yapabilmesi ve internet üzerinden veri (ör. Places API aracılığıyla yer bilgileri) alabilmesi için gereklidir.
  • android.permission.CAMERA: Cihazın kamerasını kullanarak nesneleri artırılmış gerçeklikte görüntüleyebilmeniz için kamera erişimi gerekir.
  • android.permission.ACCESS_FINE_LOCATION—Cihazın konumuna göre yakındaki yerleri getirebilmeniz için konum erişimi gerekir.

Bu uygulamanın hangi donanım özelliklerine ihtiyaç duyduğunu belirten uses-feature için aşağıdakiler beyan edilir:

  • OpenGL ES sürümü 3.0 gereklidir.
  • ARCore özellikli bir cihaz gereklidir.

Ayrıca, uygulama nesnesi altına aşağıdaki meta veri etiketleri eklenir:

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

İlk meta veri girişi, ARCore'un bu uygulamanın çalışması için gerekli olduğunu belirtmek, ikincisi ise Google Haritalar Platformu API anahtarınızı Android için Haritalar SDK'sına nasıl sağlayacağınızı belirtmek içindir.

build.gradle

build.gradle içinde aşağıdaki ek bağımlılıklar belirtilir:

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

Her bağımlılığın kısa açıklaması aşağıda verilmiştir:

  • com.google.android.gms grup kimliğine sahip kitaplıklar (play-services-location ve play-services-maps) cihazın konum bilgilerine ve Google Haritalar ile ilgili işlevlere erişmek için kullanılır.
  • com.google.maps.android:maps-utils-ktx, Android için Haritalar SDK'sı Yardımcı Kitaplığı'nın Kotlin uzantıları (KTX) kitaplığıdır. Bu kitaplıkta, sanal nesneleri daha sonra gerçek alana yerleştirmek için işlevsellik kullanılır.
  • com.google.ar.sceneform.ux:sceneform-ux, OpenGL'yi öğrenmenize gerek kalmadan gerçekçi 3D sahneler oluşturmanızı sağlayan Sceneform kitaplığıdır.
  • Grup kimliği com.squareup.retrofit2 içindeki bağımlılıklar, Places API ile etkileşim kurmak için hızlı bir şekilde HTTP istemcisi yazmanızı sağlayan Retrofit bağımlılıklarıdır.

Proje yapısı

Burada aşağıdaki paketleri ve dosyaları bulabilirsiniz:

  • **api:**Bu paket, Retrofit kullanılarak Places API ile etkileşimde bulunmak için kullanılan sınıfları içerir.
  • **ar:**Bu paket, ARCore ile ilgili tüm dosyaları içerir.
  • **model:**Bu pakette, Places API tarafından döndürülen tek bir yeri kapsüllemek için kullanılan tek bir veri sınıfı Place bulunur.
  • MainActivity.kt: Bu, uygulamanızda bulunan tek Activity olup harita ve kamera görünümü gösterir.

5. Sahneyi ayarlama

Artırılmış gerçeklik parçalarıyla başlayarak uygulamanın temel bileşenlerini inceleyin.

MainActivity, harita nesnesinin gösterilmesini sağlayan bir SupportMapFragment ve artırılmış gerçeklik sahnesinin gösterilmesini sağlayan bir ArFragment alt sınıfını—PlacesArFragment—içerir.

Artırılmış gerçeklik kurulumu

PlacesArFragment, artırılmış gerçeklik sahnesini göstermenin yanı sıra, daha önce verilmemişse kullanıcıdan kamera izni istemeyi de yönetir. Ek izinler, getAdditionalPermissions yöntemi geçersiz kılınarak da istenebilir. Konum izni de vermeniz gerektiğinden bunu belirtin ve getAdditionalPermissions yöntemini geçersiz kılın:

class PlacesArFragment : ArFragment() {

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

Çalıştırın

Android Studio'da starter dizinindeki iskelet kodunu açın. Araç çubuğundan Run (Çalıştır) > Run ‘app' (Uygulamayı çalıştır) seçeneğini tıklayıp uygulamayı cihazınıza veya emülatörünüze dağıtırsanız önce konum ve kamera iznini etkinleştirmeniz istenir. İzin ver'i tıkladığınızda yan yana kamera görünümü ve harita görünümü gösterilir:

e3e3073d5c86f427.png

Uçakları algılama

Kameranızla bulunduğunuz ortamda etrafa bakarken yatay yüzeylerin üzerinde, bu resimdeki halının üzerindeki beyaz noktalara benzeyen birkaç beyaz nokta görebilirsiniz.

2a9b6ea7dcb2e249.png

Bu beyaz noktalar, yatay bir düzlemin algılandığını belirtmek için ARCore tarafından sağlanan yönergelerdir. Bu düzlemler, sanal nesneleri gerçek alana yerleştirebilmeniz için "çapa" adı verilen bir öğe oluşturmanıza olanak tanır.

ARCore ve çevrenizi nasıl anladığı hakkında daha fazla bilgi için temel kavramları inceleyin.

6. Yakındaki yerleri görme

Ardından, cihazın mevcut konumuna erişip görüntülemeniz ve Places API'yi kullanarak yakındaki yerleri getirmeniz gerekir.

Haritalar kurulumu

Google Haritalar Platformu API anahtarı

Daha önce, Places API'yi sorgulamayı etkinleştirmek ve Android için Haritalar SDK'sını kullanabilmek için bir Google Haritalar Platformu API anahtarı oluşturmuştunuz. gradle.properties dosyasını açın ve "YOUR API KEY HERE" dizisini oluşturduğunuz API anahtarıyla değiştirin.

Cihaz konumunu haritada gösterme

API anahtarınızı ekledikten sonra, kullanıcıların haritaya göre nerede olduklarını anlamalarına yardımcı olmak için haritaya bir yardımcı ekleyin. Bunu yapmak için setUpMaps yöntemine gidin ve mapFragment.getMapAsync çağrısında googleMap.isMyLocationEnabled değerini true. olarak ayarlayın. Bu işlem, haritada mavi noktayı gösterir.

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

Geçerli konumu alma

Cihazın konumunu almak için FusedLocationProviderClient sınıfını kullanmanız gerekir. Bunun bir örneğini alma işlemi, MainActivity öğesinin onCreate yönteminde zaten yapılmıştır. Bu nesneyi kullanmak için getCurrentLocation yöntemini doldurun. Bu yöntem, bir lambda bağımsız değişkenini kabul eder. Böylece, bu yöntemin arayanına bir konum iletilebilir.

Bu yöntemi tamamlamak için FusedLocationProviderClient nesnesinin lastLocation özelliğine erişebilir ve ardından addOnSuccessListener ekleyebilirsiniz:

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

getCurrentLocation yöntemi, yakındaki yerlerin getirildiği setUpMaps yöntemindeki getMapAsync içinde sağlanan lambda'dan çağrılır.

Yer ağı görüşmesi başlatma

getNearbyPlaces yöntem çağrısında, placesServices.nearbyPlaces yöntemine bir API anahtarı, cihazın konumu, metre cinsinden bir yarıçap (2 km olarak ayarlanır) ve bir yer türü (şu anda park olarak ayarlanmıştır) parametrelerinin iletildiğini unutmayın.

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

Ağ çağrısını tamamlamak için gradle.properties dosyanızda tanımladığınız API anahtarını iletin. Aşağıdaki kod snippet'i, build.gradle dosyanızda android > defaultConfig yapılandırması altında tanımlanır:

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

Bu işlem, dize kaynağı değerini google_maps_key derleme zamanında kullanılabilir hale getirir.

Ağ çağrısını tamamlamak için Context nesnesinde getString aracılığıyla bu dize kaynağını okumanız yeterlidir.

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

7. AR'de yerler

Şu ana kadar şunları yaptınız:

  1. Uygulama ilk kez çalıştırıldığında kullanıcıdan kamera ve konum izinleri isteniyor
  2. Yatay düzlemleri izlemeye başlamak için ARCore'u ayarlama
  3. Maps SDK'sını API anahtarınızla ayarlama
  4. Cihazın mevcut konumunu alma
  5. Places API'yi kullanarak yakındaki yerleri (özellikle parkları) getirme

Bu alıştırmayı tamamlamak için kalan adım, getirdiğiniz yerleri artırılmış gerçeklikte konumlandırmaktır.

Sahne yorumlama

ARCore, her görüntü karesinde özellik noktaları adı verilen ilginç ve belirgin noktaları algılayarak cihazın kamerası aracılığıyla gerçek dünyadaki sahneyi anlayabilir. Bu özellik noktaları kümelendiğinde ve masalar ile zeminler gibi ortak bir yatay düzlem üzerinde yer alıyormuş gibi göründüğünde ARCore, bu özelliği uygulama için yatay düzlem olarak kullanılabilir hale getirebilir.

Daha önce gördüğünüz gibi ARCore, bir düzlem algılandığında beyaz noktalar göstererek kullanıcıya rehberlik eder.

2a9b6ea7dcb2e249.png

Bağlayıcı ekleme

Bir düzlem algılandığında anchor adı verilen bir nesne ekleyebilirsiniz. Bir sabitleme noktası aracılığıyla sanal nesneler yerleştirebilir ve bu nesnelerin uzayda aynı konumda kalmasını sağlayabilirsiniz. Bir uçak algılandığında eklemek için kodu değiştirebilirsiniz.

setUpAr içinde, PlacesArFragment öğesine OnTapArPlaneListener eklenir. Bu dinleyici, AR sahnesinde bir düzleme her dokunulduğunda çağrılır. Bu görüşmede, dinleyicideki HitResult kullanılarak şu şekilde bir Anchor ve AnchorNode oluşturabilirsiniz:

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

AnchorNode, addPlaces yöntem çağrısında işlenen sahnede alt düğüm nesnelerini (PlaceNode örnekleri) ekleyeceğiniz yerdir.

Çalıştırın

Uygulamayı yukarıdaki değişikliklerle çalıştırırsanız bir uçak algılanana kadar etrafınıza bakın. Uçakları gösteren beyaz noktalara dokunun. Bu işlemi yaptığınızda, yakınınızdaki tüm parkların işaretlerini haritada görürsünüz. Ancak sanal nesnelerin, oluşturulan ve bu parkların uzaydaki konumuna göre yerleştirilmeyen tutturucuya yapıştığını fark edeceksiniz.

f93eb87c98a0098d.png

Son adımda, cihazda Android için Haritalar SDK'sı Yardımcı Program Kitaplığı ve SensorManager'ı kullanarak bu durumu düzelteceksiniz.

8. Yerleri konumlandırma

Sanal yer simgesini artırılmış gerçeklikte doğru bir yöne yerleştirebilmek için iki bilgiye ihtiyacınız vardır:

  • Gerçek kuzeyin olduğu yer
  • Kuzey ile her yer arasındaki açı

Kuzeyi belirleme

Kuzey, cihazda bulunan konum sensörleri (jeomanyetik ve ivme ölçer) kullanılarak belirlenebilir. Bu iki sensörü kullanarak cihazın uzaydaki konumu hakkında gerçek zamanlı bilgiler toplayabilirsiniz. Konum sensörleri hakkında daha fazla bilgi için Cihazın yönünü hesaplama başlıklı makaleyi inceleyin.

Bu sensörlere erişmek için SensorManager edinmeniz ve ardından bu sensörlere SensorEventListener kaydetmeniz gerekir. Bu adımlar, MainActivity'nın yaşam döngüsü yöntemlerinde sizin için zaten yapılmıştır:

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 yönteminde, belirli bir sensörün verileriyle ilgili ayrıntıları içeren bir SensorEvent nesnesi sağlanır. Şimdi bu yönteme aşağıdaki kodu ekleyin:

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

Yukarıdaki kod, sensör türünü kontrol eder ve türe bağlı olarak uygun sensör ölçümünü (ivme ölçer veya manyetometre ölçümü) günceller. Bu sensör okumaları kullanılarak cihazın kuzeye göre kaç derece döndüğü (yani orientationAngles[0] değeri) belirlenebilir.

Küresel başlık

Kuzey belirlendikten sonraki adım, kuzey ile her yer arasındaki açıyı belirlemek ve ardından bu bilgileri kullanarak yerleri artırılmış gerçeklikte doğru yöne yerleştirmektir.

Başlığı hesaplamak için, küresel geometri aracılığıyla mesafeleri ve başlıkları hesaplamaya yönelik bir dizi yardımcı işlev içeren Android için Haritalar SDK'sı Yardımcı Kitaplığı'nı kullanacaksınız. Daha fazla bilgi için kitaplığa genel bakış başlıklı makaleyi okuyun.

Ardından, iki LatLng nesnesi arasındaki yönü/açıyı hesaplayan yardımcı kitaplıktaki sphericalHeading yöntemini kullanacaksınız. Bu bilgi, Place.kt içinde tanımlanan getPositionVector yöntemi için gereklidir. Bu yöntem sonuç olarak bir Vector3 nesnesi döndürür. Bu nesne daha sonra her PlaceNode tarafından AR alanındaki yerel konumu olarak kullanılır.

Bu yöntemde başlık tanımını aşağıdakiyle değiştirin:

val heading = latLng.sphericalHeading(placeLatLng)

Bu işlem sonucunda aşağıdaki yöntem tanımı elde edilir:

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

Yerel konum

Yerleri AR'de doğru şekilde yönlendirmek için son adım, sahneye PlaceNode nesneleri eklenirken getPositionVector sonucunu kullanmaktır. MainActivity içinde addPlaces'ya gidin. Bu bölüm, üst öğenin her placeNode üzerinde ayarlandığı satırın hemen altında (placeNode.setParent(anchorNode)'ün hemen altında) yer alır. placeNode'nin localPosition değerini, getPositionVector'ı aşağıdaki gibi çağırarak elde edilen sonuç olarak ayarlayın:

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

Varsayılan olarak, getPositionVector yöntemi, getPositionVector yöntemindeki y değeriyle belirtildiği gibi düğümün y mesafesini 1 metreye ayarlar. Bu mesafeyi ayarlamak isterseniz (ör. 2 metre) değeri gerektiği gibi değiştirebilirsiniz.

Bu değişiklikle birlikte, eklenen PlaceNode nesneleri artık doğru başlık yönünde olmalıdır. Şimdi uygulamayı çalıştırarak sonucu görebilirsiniz.

9. Tebrikler

Bu aşamaya ulaştığınız için tebrik ederiz.

Daha fazla bilgi