|
|
|
@ -9,42 +9,82 @@ import android.util.Log |
|
|
|
import androidx.core.content.ContextCompat |
|
|
|
import com.amap.api.location.AMapLocationClient |
|
|
|
import com.amap.api.location.AMapLocationClientOption |
|
|
|
import com.amap.api.services.core.LatLonPoint |
|
|
|
import com.amap.api.services.core.ServiceSettings |
|
|
|
import com.amap.api.services.geocoder.GeocodeSearch |
|
|
|
import com.amap.api.services.geocoder.RegeocodeQuery |
|
|
|
|
|
|
|
/** |
|
|
|
* 取一次「当前位置 + 省市区」,给百炼会话的 `${location}` 用(2026-09-25,车载香薰)。 |
|
|
|
* 取一次「当前位置 + 详细地址」,给百炼会话的 `${location}` 用(2026-09-25,车载香薰)。 |
|
|
|
* |
|
|
|
* 刻意不复用 [LocationService]:那套只收 50 米内的高精度结果、超时 60 秒,是给导航/签到用的; |
|
|
|
* 这里只要知道「在哪个城市哪个区」,网络定位就够,要的是快。 |
|
|
|
* 这里要路名和门牌,高德逆地理一次拿全,要的是快。 |
|
|
|
* `address` 优先用高德格式化地址;它为空时用省市区、街道、门牌拼一条,供上层直接念。 |
|
|
|
* |
|
|
|
* - 没有定位权限直接回 `no_permission`,**不在这里弹权限框**(由 Dart 侧决定何时申请); |
|
|
|
* - 高德定位自带逆地理(setNeedAddress),省/市/区一次拿全,华为无 GMS 的机器上也能用; |
|
|
|
* - 坐标是高德的 GCJ-02,只用于城市级描述,偏移无关紧要。 |
|
|
|
* - 定位回调经常只有市名(华为 ALN-AL80 上是「深圳市」)。没有路名时,用手里的经纬度 |
|
|
|
* 再调一次高德逆地理,把格式化地址填进 `address`; |
|
|
|
* - 坐标是高德的 GCJ-02,只放进 client_info,不放进要念出来的地址。 |
|
|
|
*/ |
|
|
|
object PlaceLocator { |
|
|
|
/** 系统 Geocoder 反查省市区;机器不支持 / 没网 / 出错都返回 null。在后台线程调用。 */ |
|
|
|
private fun systemGeocode(context: Context, lat: Double, lng: Double): Map<String, Any?>? { |
|
|
|
/** 定位结果里已经有路名或门牌,就不必再打一次逆地理。 */ |
|
|
|
private fun hasRoad(street: String?, streetNum: String?, address: String?): Boolean { |
|
|
|
if (!street.isNullOrBlank() || !streetNum.isNullOrBlank()) return true |
|
|
|
val a = address?.trim().orEmpty() |
|
|
|
return a.contains('路') || a.contains('巷') || a.contains('号') || a.contains("大道") |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 用定位拿到的经纬度向高德要格式化地址。 |
|
|
|
* 必须在后台线程调用:`getFromLocation` 是同步网络请求。 |
|
|
|
* 失败返回 null,调用方保留定位回调里已有的市名。 |
|
|
|
*/ |
|
|
|
private fun amapRegeocode(context: Context, lat: Double, lng: Double): Map<String, Any?>? { |
|
|
|
return try { |
|
|
|
if (!android.location.Geocoder.isPresent()) { |
|
|
|
Log.w(TAG, "系统 Geocoder 不可用") |
|
|
|
return null |
|
|
|
} |
|
|
|
@Suppress("DEPRECATION") |
|
|
|
val list = android.location.Geocoder(context, java.util.Locale.CHINA).getFromLocation(lat, lng, 1) |
|
|
|
val a = list?.firstOrNull() ?: return null |
|
|
|
Log.i(TAG, "系统 Geocoder: 省=${a.adminArea} 市=${a.locality} 区=${a.subLocality} 地址=${a.getAddressLine(0)}") |
|
|
|
ServiceSettings.updatePrivacyShow(context, true, true) |
|
|
|
ServiceSettings.updatePrivacyAgree(context, true) |
|
|
|
val search = GeocodeSearch(context) |
|
|
|
val query = RegeocodeQuery(LatLonPoint(lat, lng), 200f, GeocodeSearch.AMAP) |
|
|
|
val addr = search.getFromLocation(query) ?: return null |
|
|
|
val sn = addr.streetNumber |
|
|
|
val street = sn?.street?.trim().orEmpty() |
|
|
|
val number = sn?.number?.trim().orEmpty() |
|
|
|
val spoken = spokenAddress( |
|
|
|
addr.formatAddress, addr.province, addr.city, addr.district, street, number, |
|
|
|
) |
|
|
|
if (spoken.isEmpty()) return null |
|
|
|
Log.i(TAG, "高德逆地理补全: $spoken") |
|
|
|
mapOf( |
|
|
|
"province" to (a.adminArea ?: ""), |
|
|
|
"city" to (a.locality ?: a.adminArea ?: ""), |
|
|
|
"district" to (a.subLocality ?: ""), |
|
|
|
"street" to (a.thoroughfare ?: ""), |
|
|
|
"address" to (a.getAddressLine(0) ?: ""), |
|
|
|
"province" to addr.province.orEmpty(), |
|
|
|
"city" to addr.city.orEmpty().ifBlank { addr.province.orEmpty() }, |
|
|
|
"district" to addr.district.orEmpty(), |
|
|
|
"street" to listOf(street, number).filter { it.isNotEmpty() }.joinToString(""), |
|
|
|
"address" to spoken, |
|
|
|
) |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.w(TAG, "系统 Geocoder 失败: ${e.message}") |
|
|
|
Log.w(TAG, "高德逆地理失败: ${e.message}") |
|
|
|
null |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** 高德格式化地址优先;为空时用省市区、街道、门牌拼一条。不含经纬度。 */ |
|
|
|
private fun spokenAddress( |
|
|
|
amapAddress: String?, |
|
|
|
province: String?, |
|
|
|
city: String?, |
|
|
|
district: String?, |
|
|
|
street: String?, |
|
|
|
streetNum: String?, |
|
|
|
): String { |
|
|
|
val formatted = amapAddress?.trim().orEmpty() |
|
|
|
if (formatted.isNotEmpty()) return formatted |
|
|
|
return listOf(province, city, district, street, streetNum) |
|
|
|
.map { it?.trim().orEmpty() } |
|
|
|
.filter { it.isNotEmpty() } |
|
|
|
.distinct() |
|
|
|
.joinToString("") |
|
|
|
} |
|
|
|
|
|
|
|
private const val TAG = "PlaceLocator" |
|
|
|
private val main = Handler(Looper.getMainLooper()) |
|
|
|
|
|
|
|
@ -59,6 +99,8 @@ object PlaceLocator { |
|
|
|
main.post { |
|
|
|
var done = false |
|
|
|
var client: AMapLocationClient? = null |
|
|
|
// 已经拿到坐标、正在补逆地理时,超时把这份粗地址交出去,而不是整次失败。 |
|
|
|
var held: Map<String, Any?>? = null |
|
|
|
fun finish(result: Map<String, Any?>?, error: String?) { |
|
|
|
if (done) return |
|
|
|
done = true |
|
|
|
@ -93,6 +135,9 @@ object PlaceLocator { |
|
|
|
} else { |
|
|
|
Log.i(TAG, "定位成功 type=${loc.locationType} lat=${loc.latitude} lng=${loc.longitude} " + |
|
|
|
"省=${loc.province} 市=${loc.city} 区=${loc.district} 地址=${loc.address}") |
|
|
|
val spoken = spokenAddress( |
|
|
|
loc.address, loc.province, loc.city, loc.district, loc.street, loc.streetNum, |
|
|
|
) |
|
|
|
val base = mapOf<String, Any?>( |
|
|
|
"latitude" to loc.latitude, |
|
|
|
"longitude" to loc.longitude, |
|
|
|
@ -100,21 +145,24 @@ object PlaceLocator { |
|
|
|
"city" to loc.city, |
|
|
|
"district" to loc.district, |
|
|
|
"street" to loc.street, |
|
|
|
"address" to loc.address, |
|
|
|
"address" to spoken, |
|
|
|
) |
|
|
|
if (!loc.city.isNullOrBlank() || !loc.address.isNullOrBlank()) { |
|
|
|
if (hasRoad(loc.street, loc.streetNum, loc.address)) { |
|
|
|
finish(base, null) |
|
|
|
} else { |
|
|
|
// 2026-09-25 华为 ALN-AL80 实测:高德只回了坐标(type=12)、省市区全空。 |
|
|
|
// 退一步用系统 Geocoder 反查;再不行就只回坐标,由上层用坐标描述位置。 |
|
|
|
// 市名有了但没有路名(华为上常见只回「深圳市」)。 |
|
|
|
// 用同一对经纬度再向高德要一次格式化地址。 |
|
|
|
held = base |
|
|
|
Thread { |
|
|
|
val geo = systemGeocode(app, loc.latitude, loc.longitude) |
|
|
|
val geo = amapRegeocode(app, loc.latitude, loc.longitude) |
|
|
|
main.post { finish(if (geo != null) base + geo else base, null) } |
|
|
|
}.start() |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
main.postDelayed({ finish(null, "timeout") }, timeoutMs) |
|
|
|
main.postDelayed({ |
|
|
|
finish(held, if (held == null) "timeout" else null) |
|
|
|
}, timeoutMs) |
|
|
|
c.startLocation() |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.w(TAG, "定位异常: ${e.message}") |
|
|
|
|