Android 中的反向地理编码
反向地理编码是一个用于将坐标(纬度和经度)转换为人类可读地址的过程。这与地理编码并不完全相反。在地理编码中,地点与名称和固定坐标相关联。这些坐标本质上是双重的。这些坐标的微不足道的变化可能仍然指的是同一个地方,但我们永远不会得到地名,因为它只与那些固定坐标相关联。因此,在反向地理编码中我们肯定会得到完整的地址,但不能保证地名。通过本文,我们将向您展示如何在 Android 中执行反向地理编码的示例。但在继续之前,请参阅以下文章:
- Google Cloud Platform – 创建 Google Cloud Console 帐户和项目
- 生成用于使用任何 Google API 的 API 密钥
- 如何在 Android Studio 中隐藏 API 和密钥?
- 如何在 Android 中实现 Google 的 Places AutocompleteBar?
- 如何在 Android 的 Google 地图中实现当前位置按钮功能?
项目结构
对于反向地理编码,我们需要 Double 数据类型的纬度和经度。所以我们将实现一个谷歌地图并将其中心视为我们的主要纬度和经度。我们可以使用相机位置轻松获取中心坐标(参考主代码)。每当地图被拖动时,中心都会发生变化并导致经纬度发生变化。一旦地图空闲,即停止拖动或移动,反向地理编码算法将考虑中心坐标并对其进行处理以获得完整地址。这将立即发布到 textView。这将确认反向地理编码在代码中工作正常。按照以下步骤创建此项目。
我们将在本文中构建什么?
第 1 步:在 Android Studio 中创建一个新项目
要在 Android Studio 中创建新项目,请参阅如何在 Android Studio 中创建/启动新项目。我们在 Kotlin 中演示了该应用程序,因此请确保在创建新项目时选择 Kotlin 作为主要语言。
第 2 步:获取和隐藏 API 密钥
- 我们的应用程序使用 Google 的 Places API,因此我们需要从 Google 获取 Places API 密钥。要获取 API 密钥,请参阅生成用于使用任何 Google API 的 API 密钥。
- 隐藏 API 密钥是必不可少的,为此,请参阅如何在 Android Studio 中隐藏 API 和秘密密钥?。
第 3 步:添加这些依赖项
反向地理编码将需要此依赖项。
Kotlin
dependencies {
implementation 'com.google.android.libraries.places:places:2.4.0'
}
XML
XML
Kotlin
package org.geeksforgeeks.reversegeocoding
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.location.Address
import android.location.Geocoder
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.GoogleMap.OnCameraIdleListener
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng
import com.google.android.libraries.places.api.Places
import java.io.IOException
import java.util.*
class MainActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mMap: GoogleMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Fetching API_KEY which we wrapped
val ai: ApplicationInfo = applicationContext.packageManager.getApplicationInfo(applicationContext.packageName, PackageManager.GET_META_DATA)
val value = ai.metaData["com.google.android.geo.API_KEY"]
val apiKey = value.toString()
// Initializing the Places API
// with the help of our API_KEY
if (!Places.isInitialized()) {
Places.initialize(applicationContext, apiKey)
}
// Initializing map
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
}
override fun onMapReady(p0: GoogleMap) {
mMap = p0
// These are GeeksforGeeks Noida Office Coordinates.
val india = LatLng(28.5021359, 77.4054901)
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(india,17F))
mMap.setOnCameraIdleListener {
val lat = mMap.cameraPosition.target.latitude
val lng = mMap.cameraPosition.target.longitude
val addressTV = findViewById(R.id.tv)
// Initializing Geocoder
val mGeocoder = Geocoder(applicationContext, Locale.getDefault())
var addressString= ""
// Reverse-Geocoding starts
try {
val addressList: List = mGeocoder.getFromLocation(lat, lng, 1)
// use your lat, long value here
if (addressList != null && addressList.isNotEmpty()) {
val address = addressList[0]
val sb = StringBuilder()
for (i in 0 until address.maxAddressLineIndex) {
sb.append(address.getAddressLine(i)).append("\n")
}
// Various Parameters of an Address are appended
// to generate a complete Address
if (address.premises != null)
sb.append(address.premises).append(", ")
sb.append(address.subAdminArea).append("\n")
sb.append(address.locality).append(", ")
sb.append(address.adminArea).append(", ")
sb.append(address.countryName).append(", ")
sb.append(address.postalCode)
// StringBuilder sb is converted into a string
// and this value is assigned to the
// initially declared addressString string.
addressString = sb.toString()
}
} catch (e: IOException) {
Toast.makeText(applicationContext,"Unable connect to Geocoder",Toast.LENGTH_LONG).show()
}
// Finally, the address string is posted in the textView with LatLng.
addressTV.text = "Lat: $lat \nLng: $lng \nAddress: $addressString"
}
}
}
第 4 步:在布局 (activity_main.xml) 文件中添加地图片段、自定义位置标记和文本视图
XML
标记:
我们从互联网上以 PNG 格式下载了这张图片。它没有背景颜色,可以称为透明图像。下载后,您可以直接从存储的任何位置复制它,打开 Android Studio,然后将其粘贴到 res 文件夹中的 drawable 文件夹中。在执行此操作时,我们将其重命名为“标记”,您可以在activity_main.xml的 ImageView 属性中找到它。
Shape.xml 文件(activity_main.xml 中线性布局的背景)
我们已经设置了一个白色背景和带有一些值的角半径。这是为了使布局看起来更好。
XML
activity_main.xml 预览:
第 5 步:在后端工作 (MainActivity.kt)
我们获取屏幕中心位置的坐标并将它们转换为文本地址。一旦拖动屏幕,中心坐标和地址分别发生变化。一旦屏幕空闲且不运动,就会发生变化,这就是我们在 setOnCamera Idle Listener 中实现我们的反向地理编码算法的原因。下面的代码很容易理解。我们提供了一些评论以帮助您更好地理解。
科特林
package org.geeksforgeeks.reversegeocoding
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.location.Address
import android.location.Geocoder
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.GoogleMap.OnCameraIdleListener
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng
import com.google.android.libraries.places.api.Places
import java.io.IOException
import java.util.*
class MainActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mMap: GoogleMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Fetching API_KEY which we wrapped
val ai: ApplicationInfo = applicationContext.packageManager.getApplicationInfo(applicationContext.packageName, PackageManager.GET_META_DATA)
val value = ai.metaData["com.google.android.geo.API_KEY"]
val apiKey = value.toString()
// Initializing the Places API
// with the help of our API_KEY
if (!Places.isInitialized()) {
Places.initialize(applicationContext, apiKey)
}
// Initializing map
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
}
override fun onMapReady(p0: GoogleMap) {
mMap = p0
// These are GeeksforGeeks Noida Office Coordinates.
val india = LatLng(28.5021359, 77.4054901)
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(india,17F))
mMap.setOnCameraIdleListener {
val lat = mMap.cameraPosition.target.latitude
val lng = mMap.cameraPosition.target.longitude
val addressTV = findViewById(R.id.tv)
// Initializing Geocoder
val mGeocoder = Geocoder(applicationContext, Locale.getDefault())
var addressString= ""
// Reverse-Geocoding starts
try {
val addressList: List = mGeocoder.getFromLocation(lat, lng, 1)
// use your lat, long value here
if (addressList != null && addressList.isNotEmpty()) {
val address = addressList[0]
val sb = StringBuilder()
for (i in 0 until address.maxAddressLineIndex) {
sb.append(address.getAddressLine(i)).append("\n")
}
// Various Parameters of an Address are appended
// to generate a complete Address
if (address.premises != null)
sb.append(address.premises).append(", ")
sb.append(address.subAdminArea).append("\n")
sb.append(address.locality).append(", ")
sb.append(address.adminArea).append(", ")
sb.append(address.countryName).append(", ")
sb.append(address.postalCode)
// StringBuilder sb is converted into a string
// and this value is assigned to the
// initially declared addressString string.
addressString = sb.toString()
}
} catch (e: IOException) {
Toast.makeText(applicationContext,"Unable connect to Geocoder",Toast.LENGTH_LONG).show()
}
// Finally, the address string is posted in the textView with LatLng.
addressTV.text = "Lat: $lat \nLng: $lng \nAddress: $addressString"
}
}
}
输出:
观察,地址随着经纬度值的变化而变化。当用户拖动屏幕时,经纬度值会发生变化,因为中心会不断变化。