mirror of
https://github.com/xweiba/location-spoofer.git
synced 2026-09-27 00:51:55 +08:00
fix: 瓦片类型通过 MKLocalSearch 反查地名含中文判断(30s缓存)
This commit is contained in:
+3
-17
@@ -90,8 +90,7 @@ struct MapHomeView: View {
|
|||||||
},
|
},
|
||||||
onUserCenterChanged: { coordinate, distance in
|
onUserCenterChanged: { coordinate, distance in
|
||||||
mapState.updateViewport(distanceMeters: distance)
|
mapState.updateViewport(distanceMeters: distance)
|
||||||
CoordinateConverter.updateTileType(lat: coordinate.latitude, lon: coordinate.longitude)
|
let previousRevision = mapState.selection.revision
|
||||||
let previousRevision = mapState.selection.revision
|
|
||||||
let revision = mapState.selectUserMapCenter(coordinate)
|
let revision = mapState.selectUserMapCenter(coordinate)
|
||||||
guard revision != previousRevision else { return }
|
guard revision != previousRevision else { return }
|
||||||
let wgs = CoordinateConverter.toStored(lat: coordinate.latitude, lon: coordinate.longitude)
|
let wgs = CoordinateConverter.toStored(lat: coordinate.latitude, lon: coordinate.longitude)
|
||||||
@@ -104,8 +103,7 @@ struct MapHomeView: View {
|
|||||||
},
|
},
|
||||||
onMapTap: { coordinate in
|
onMapTap: { coordinate in
|
||||||
favorites.select(nil)
|
favorites.select(nil)
|
||||||
CoordinateConverter.updateTileType(lat: coordinate.latitude, lon: coordinate.longitude)
|
let revision = mapState.selectMapTap(coordinate)
|
||||||
let revision = mapState.selectMapTap(coordinate)
|
|
||||||
let wgs = CoordinateConverter.toStored(lat: coordinate.latitude, lon: coordinate.longitude)
|
let wgs = CoordinateConverter.toStored(lat: coordinate.latitude, lon: coordinate.longitude)
|
||||||
LastCoordinateStore.save(lat: wgs.lat, lon: wgs.lon)
|
LastCoordinateStore.save(lat: wgs.lat, lon: wgs.lon)
|
||||||
scheduleGeocode(coordinate: coordinate, revision: revision)
|
scheduleGeocode(coordinate: coordinate, revision: revision)
|
||||||
@@ -795,18 +793,7 @@ struct MapHomeView: View {
|
|||||||
searchError = error.localizedDescription
|
searchError = error.localizedDescription
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let items = (response?.mapItems ?? []).prefix(6)
|
searchResults = (response?.mapItems ?? []).prefix(6).map { item in
|
||||||
// 用首个搜索结果坐标推算瓦片类型
|
|
||||||
if let first = items.first {
|
|
||||||
let c = first.placemark.coordinate
|
|
||||||
if let newType = CoordinateConverter.detectTile(
|
|
||||||
resultCoord: (c.latitude, c.longitude),
|
|
||||||
userLocation: mapState.realtimeLocation
|
|
||||||
) {
|
|
||||||
CoordinateConverter.currentTileType = newType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
searchResults = items.map { item in
|
|
||||||
let r = SearchLocationResult(
|
let r = SearchLocationResult(
|
||||||
name: item.name ?? "未命名",
|
name: item.name ?? "未命名",
|
||||||
subtitle: [item.placemark.locality, item.placemark.subLocality, item.placemark.thoroughfare]
|
subtitle: [item.placemark.locality, item.placemark.subLocality, item.placemark.thoroughfare]
|
||||||
@@ -830,7 +817,6 @@ struct MapHomeView: View {
|
|||||||
geocodeDebounceTask?.cancel()
|
geocodeDebounceTask?.cancel()
|
||||||
reverseGeocodeTask?.cancel()
|
reverseGeocodeTask?.cancel()
|
||||||
favorites.select(nil)
|
favorites.select(nil)
|
||||||
CoordinateConverter.updateTileType(lat: result.coordinate.latitude, lon: result.coordinate.longitude)
|
|
||||||
mapState.selectSearchResult(result.coordinate, name: result.name)
|
mapState.selectSearchResult(result.coordinate, name: result.name)
|
||||||
let wgs = CoordinateConverter.toStored(lat: result.coordinate.latitude, lon: result.coordinate.longitude)
|
let wgs = CoordinateConverter.toStored(lat: result.coordinate.latitude, lon: result.coordinate.longitude)
|
||||||
LastCoordinateStore.save(lat: wgs.lat, lon: wgs.lon)
|
LastCoordinateStore.save(lat: wgs.lat, lon: wgs.lon)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import CoreLocation
|
import CoreLocation
|
||||||
|
import MapKit
|
||||||
|
|
||||||
/// GCJ-02 (火星坐标) ↔ WGS-84 坐标转换。
|
/// GCJ-02 (火星坐标) ↔ WGS-84 坐标转换。
|
||||||
///
|
///
|
||||||
@@ -18,18 +19,38 @@ enum CoordinateConverter {
|
|||||||
|
|
||||||
// MARK: - 全局瓦片类型
|
// MARK: - 全局瓦片类型
|
||||||
|
|
||||||
/// 当前地图瓦片坐标系(每次从地图拿到坐标后更新)
|
/// 当前地图瓦片坐标系
|
||||||
@MainActor static var currentTileType = CoordType.gcj02
|
@MainActor static var currentTileType = CoordType.gcj02
|
||||||
|
/// 瓦片检测缓存
|
||||||
|
@MainActor private static var lastTileCheck: Date?
|
||||||
|
@MainActor private static var tileCheckPending = false
|
||||||
|
|
||||||
/// 根据坐标的地理位置判断 MKMapView 瓦片类型(中国境内 = GCJ-02,境外 = WGS-84)
|
/// 后台反查坐标地名:返回中文名称 → GCJ-02,否则 → WGS-84(30s 缓存)
|
||||||
@MainActor
|
@MainActor
|
||||||
static func updateTileType(lat: Double, lon: Double) {
|
private static func detectTileByGeocode(lat: Double, lon: Double) {
|
||||||
let type = detectType(lat: lat, lon: lon)
|
guard !tileCheckPending else { return }
|
||||||
if type != currentTileType {
|
if let last = lastTileCheck, -last.timeIntervalSinceNow < 30 { return }
|
||||||
currentTileType = type
|
tileCheckPending = true
|
||||||
RuntimeLogger.info("APP", "坐标转换", "地图瓦片切换 → \(type.rawValue)", details: [
|
let loc = CLLocation(latitude: lat, longitude: lon)
|
||||||
"坐标": "\(lat), \(lon)"
|
let req = MKLocalSearch.Request()
|
||||||
])
|
req.region = MKCoordinateRegion(center: loc.coordinate, latitudinalMeters: 200, longitudinalMeters: 200)
|
||||||
|
MKLocalSearch(request: req).start { response, _ in
|
||||||
|
Task { @MainActor in
|
||||||
|
defer { tileCheckPending = false }
|
||||||
|
guard let name = response?.mapItems.first?.name else {
|
||||||
|
RuntimeLogger.info("APP", "坐标转换", "反查地名: 无结果", details: ["坐标": "\(lat), \(lon)"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let hasChinese = name.unicodeScalars.contains { $0.value >= 0x4E00 && $0.value <= 0x9FFF }
|
||||||
|
let newType: CoordType = hasChinese ? .gcj02 : .wgs84
|
||||||
|
RuntimeLogger.info("APP", "坐标转换", "反查地名: \(name) → \(newType.rawValue)", details: [
|
||||||
|
"含中文": String(hasChinese), "坐标": "\(lat), \(lon)"
|
||||||
|
])
|
||||||
|
if newType != currentTileType {
|
||||||
|
currentTileType = newType
|
||||||
|
}
|
||||||
|
lastTileCheck = Date()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +59,7 @@ enum CoordinateConverter {
|
|||||||
/// 地图坐标 → WGS-84 存储
|
/// 地图坐标 → WGS-84 存储
|
||||||
@MainActor
|
@MainActor
|
||||||
static func toStored(lat: Double, lon: Double) -> (lat: Double, lon: Double) {
|
static func toStored(lat: Double, lon: Double) -> (lat: Double, lon: Double) {
|
||||||
updateTileType(lat: lat, lon: lon)
|
detectTileByGeocode(lat: lat, lon: lon)
|
||||||
guard currentTileType == .gcj02 else {
|
guard currentTileType == .gcj02 else {
|
||||||
RuntimeLogger.info("APP", "坐标转换", "toStored: 不转 瓦片=\(currentTileType.rawValue)", details: [
|
RuntimeLogger.info("APP", "坐标转换", "toStored: 不转 瓦片=\(currentTileType.rawValue)", details: [
|
||||||
"lat": String(lat), "lon": String(lon)
|
"lat": String(lat), "lon": String(lon)
|
||||||
@@ -58,6 +79,7 @@ enum CoordinateConverter {
|
|||||||
/// WGS-84 存储 → 当前地图瓦片坐标系(显示用)
|
/// WGS-84 存储 → 当前地图瓦片坐标系(显示用)
|
||||||
@MainActor
|
@MainActor
|
||||||
static func toDisplay(lat: Double, lon: Double) -> (lat: Double, lon: Double) {
|
static func toDisplay(lat: Double, lon: Double) -> (lat: Double, lon: Double) {
|
||||||
|
detectTileByGeocode(lat: lat, lon: lon)
|
||||||
guard currentTileType == .gcj02 else {
|
guard currentTileType == .gcj02 else {
|
||||||
RuntimeLogger.info("APP", "坐标转换", "toDisplay: WGS-84 → 不转 瓦片=\(currentTileType.rawValue)", details: [
|
RuntimeLogger.info("APP", "坐标转换", "toDisplay: WGS-84 → 不转 瓦片=\(currentTileType.rawValue)", details: [
|
||||||
"lat": String(lat), "lon": String(lon)
|
"lat": String(lat), "lon": String(lon)
|
||||||
@@ -74,7 +96,7 @@ enum CoordinateConverter {
|
|||||||
return gcj
|
return gcj
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 类型检测
|
// MARK: - 工具
|
||||||
|
|
||||||
/// Haversine 距离(米)
|
/// Haversine 距离(米)
|
||||||
static func distance(lat1: Double, lon1: Double, lat2: Double, lon2: Double) -> Double {
|
static func distance(lat1: Double, lon1: Double, lat2: Double, lon2: Double) -> Double {
|
||||||
@@ -87,26 +109,6 @@ enum CoordinateConverter {
|
|||||||
return r * 2 * atan2(sqrt(a), sqrt(1 - a))
|
return r * 2 * atan2(sqrt(a), sqrt(1 - a))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// MKMapView 瓦片坐标系:中国境内 GCJ-02,境外 WGS-84
|
|
||||||
static func detectType(lat: Double, lon: Double) -> CoordType {
|
|
||||||
// GCJ-02 加密只在中国境内生效,用地理边界判断
|
|
||||||
if lat > 17.5 && lat < 54.0 && lon > 72.5 && lon < 136.0 {
|
|
||||||
return .gcj02
|
|
||||||
}
|
|
||||||
return .wgs84
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 通过搜索结果坐标 + 蓝点推算瓦片类型(搜索结果在当期瓦片坐标系,蓝点是 WGS-84)
|
|
||||||
static func detectTile(resultCoord: (lat: Double, lon: Double), userLocation: CLLocation?) -> CoordType? {
|
|
||||||
guard let bl = userLocation,
|
|
||||||
CLLocationCoordinate2DIsValid(bl.coordinate),
|
|
||||||
bl.horizontalAccuracy >= 0 else { return nil }
|
|
||||||
let wgs = gcj02ToWgs84(lat: resultCoord.lat, lon: resultCoord.lon)
|
|
||||||
let dGCJ = distance(lat1: wgs.lat, lon1: wgs.lon, lat2: bl.coordinate.latitude, lon2: bl.coordinate.longitude)
|
|
||||||
let dRaw = distance(lat1: resultCoord.lat, lon1: resultCoord.lon, lat2: bl.coordinate.latitude, lon2: bl.coordinate.longitude)
|
|
||||||
return dGCJ < dRaw ? .gcj02 : .wgs84
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 核心转换
|
// MARK: - 核心转换
|
||||||
|
|
||||||
/// GCJ-02 → WGS-84(迭代法,精度优于 0.5 米)
|
/// GCJ-02 → WGS-84(迭代法,精度优于 0.5 米)
|
||||||
|
|||||||
Reference in New Issue
Block a user