merge: integrate complete MapKit runtime refresh

# Conflicts:
#	App/MapHomeView.swift
#	Shared/CoordinateConverter.swift
#	Tests/PaopaoLocationSpooferTests/FavoriteLocationStoreTests.swift
#	Tests/map_refactor_contract_test.sh
This commit is contained in:
xweiba
2026-08-07 14:51:30 +08:00
7 changed files with 363 additions and 201 deletions
+64 -42
View File
@@ -85,6 +85,13 @@ enum CoordinateConverter {
let current: MapCoordinateSystem
}
enum RuntimeMapCoordinateSystemRefreshResult: Equatable {
case unchanged(MapCoordinateSystem)
case changed(MapCoordinateSystemChange)
case unavailable(reason: String)
case cancelled
}
/// Apple 使 GCJ-02
@MainActor static var currentMapCoordinateSystem = MapCoordinateSystem.gcj02
@MainActor private static var mapCoordinateSystemCheckPending = false
@@ -110,7 +117,7 @@ enum CoordinateConverter {
let nextType: MapCoordinateSystem
switch await fixedAnchorCoordinateSystemProbe() {
case let .response(name, count):
nextType = mapCoordinateSystem(anchorName: name)
nextType = mapCoordinateSystem(forFixedAnchorFirstResultName: name)
initialMapCoordinateSystemUsedFallback = false
RuntimeLogger.info("APP", "坐标转换", "地图坐标标准检测获得明确结果", details: [
"首条名称": name,
@@ -146,7 +153,7 @@ enum CoordinateConverter {
return currentMapCoordinateSystem
}
_ = applyDetectedMapCoordinateSystem(nextType)
currentMapCoordinateSystem = nextType
RuntimeLogger.info("APP", "坐标转换", "地图坐标标准已确定,允许创建地图", details: [
"最终标准": nextType.rawValue,
"使用兜底": String(initialMapCoordinateSystemUsedFallback),
@@ -155,64 +162,77 @@ enum CoordinateConverter {
return nextType
}
/// Re-runs the same location-independent fixed-anchor probe after a
/// spoofing environment change. Runtime callers must never infer MapKit's
/// representation from the geographic region of the current location: that
/// sample may itself already be virtual.
/// Re-runs the fixed-anchor MapKit behavior probe while the map is alive.
/// Runtime failures preserve the last confirmed type: a potentially spoofed
/// Core Location sample is not authoritative for MapKit's representation.
@MainActor
static func refreshMapCoordinateSystem() async -> MapCoordinateSystemChange? {
static func refreshRuntimeMapCoordinateSystem(reason: String) async -> RuntimeMapCoordinateSystemRefreshResult {
guard !mapCoordinateSystemCheckPending else {
RuntimeLogger.info("APP", "坐标转换", "运行期地图坐标标准刷新已在进行中")
return nil
RuntimeLogger.info("APP", "坐标转换", "地图坐标标准运行期检测合并到进行中请求", details: [
"触发原因": reason,
"当前标准": currentMapCoordinateSystem.rawValue
])
return .unchanged(currentMapCoordinateSystem)
}
mapCoordinateSystemCheckPending = true
defer { mapCoordinateSystemCheckPending = false }
RuntimeLogger.info("APP", "坐标转换", "运行期地图坐标标准刷新开始", details: [
"当前标准": currentMapCoordinateSystem.rawValue,
"锚点": "22.283819,114.158439"
let previous = currentMapCoordinateSystem
RuntimeLogger.info("APP", "坐标转换", "地图坐标标准运行期检测开始", details: [
"触发原因": reason,
"检测前标准": previous.rawValue,
"锚点": "22.283819,114.158439",
"缓存": "false"
])
switch await fixedAnchorCoordinateSystemProbe() {
case let .response(name, count):
let detected = mapCoordinateSystem(anchorName: name)
guard !Task.isCancelled else {
RuntimeLogger.info("APP", "坐标转换", "地图坐标标准运行期检测结果已过期,取消写入", details: [
"触发原因": reason,
"保留标准": previous.rawValue
])
return .cancelled
}
let detected = mapCoordinateSystem(forFixedAnchorFirstResultName: name)
initialMapCoordinateSystemUsedFallback = false
let change = applyDetectedMapCoordinateSystem(detected)
RuntimeLogger.info("APP", "坐标转换", "运行期地图坐标标准刷新完成", details: [
guard detected != previous else {
RuntimeLogger.info("APP", "坐标转换", "地图坐标标准运行期检测完成,标准未变化", details: [
"触发原因": reason,
"首条名称": name,
"结果数": String(count),
"确认标准": detected.rawValue
])
return .unchanged(detected)
}
let change = MapCoordinateSystemChange(previous: previous, current: detected)
currentMapCoordinateSystem = detected
RuntimeLogger.warning("APP", "坐标转换", "地图坐标标准运行期检测发现切换", details: [
"触发原因": reason,
"首条名称": name,
"结果数": String(count),
"探测标准": detected.rawValue,
"发生切换": String(change != nil)
"from": previous.rawValue,
"to": detected.rawValue
])
return change
case .unavailable(let reason), .timedOut(let reason):
RuntimeLogger.warning("APP", "坐标转换", "运行期地图坐标标准刷新不可用,保留当前标准", details: [
"原因": reason,
"当前标准": currentMapCoordinateSystem.rawValue
return .changed(change)
case .unavailable(let failureReason), .timedOut(let failureReason):
RuntimeLogger.warning("APP", "坐标转换", "地图坐标标准运行期检测失败,保留当前标准", details: [
"触发原因": reason,
"原因": failureReason,
"保留标准": previous.rawValue
])
return nil
return .unavailable(reason: failureReason)
case .cancelled:
RuntimeLogger.info("APP", "坐标转换", "运行期地图坐标标准刷新被取消,保留当前标准", details: [
"当前标准": currentMapCoordinateSystem.rawValue
RuntimeLogger.info("APP", "坐标转换", "地图坐标标准运行期检测已取消", details: [
"触发原因": reason,
"保留标准": previous.rawValue
])
return nil
return .cancelled
}
}
/// Applies a coordinate system that was resolved by the fixed-anchor
/// probe. Kept as a small deterministic seam for regression tests.
@MainActor
static func applyDetectedMapCoordinateSystem(_ detected: MapCoordinateSystem) -> MapCoordinateSystemChange? {
guard detected != currentMapCoordinateSystem else { return nil }
let change = MapCoordinateSystemChange(
previous: currentMapCoordinateSystem,
current: detected
)
currentMapCoordinateSystem = detected
return change
}
private static func mapCoordinateSystem(anchorName: String) -> MapCoordinateSystem {
anchorName == "林士街" ? .gcj02 : .wgs84
static func mapCoordinateSystem(forFixedAnchorFirstResultName name: String) -> MapCoordinateSystem {
name == "林士街" ? .gcj02 : .wgs84
}
/// A user-requested realtime sample is WGS-84 and can correct a provisional
@@ -227,12 +247,14 @@ enum CoordinateConverter {
return nil
}
let next: MapCoordinateSystem = usesGCJ02ServiceArea(lat: coordinate.latitude, lon: coordinate.longitude) ? .gcj02 : .wgs84
guard let change = applyDetectedMapCoordinateSystem(next) else {
guard next != currentMapCoordinateSystem else {
RuntimeLogger.info("APP", "坐标转换", "实时定位确认兜底地图坐标标准无需修正", details: [
"当前标准": currentMapCoordinateSystem.rawValue
])
return nil
}
let change = MapCoordinateSystemChange(previous: currentMapCoordinateSystem, current: next)
currentMapCoordinateSystem = next
RuntimeLogger.warning("APP", "坐标转换", "实时定位修正启动兜底地图坐标标准", details: [
"from": change.previous.rawValue,
"to": change.current.rawValue
+17 -4
View File
@@ -116,11 +116,24 @@ final class FavoriteLocationStore: ObservableObject {
mapCoordinateSystem: CoordinateConverter.MapCoordinateSystem,
accuracy: Int
) -> FavoriteLocation {
let favorite = FavoriteLocation(
name: name,
coordinatePair: .init(mapCoordinate: mapCoordinate, mapCoordinateSystem: mapCoordinateSystem),
accuracy: accuracy
save(
FavoriteLocation(
name: name,
coordinatePair: .init(mapCoordinate: mapCoordinate, mapCoordinateSystem: mapCoordinateSystem),
accuracy: accuracy
)
)
}
/// Saves a coordinate pair whose source representation was already typed
/// before an asynchronous map-coordinate-system refresh.
@discardableResult
func save(name: String, coordinatePair: CoordinatePair, accuracy: Int) -> FavoriteLocation {
save(FavoriteLocation(name: name, coordinatePair: coordinatePair, accuracy: accuracy))
}
@discardableResult
private func save(_ favorite: FavoriteLocation) -> FavoriteLocation {
favorites.removeAll {
abs($0.coordinatePair.wgs84.latitude - favorite.coordinatePair.wgs84.latitude) < 0.000001
&& abs($0.coordinatePair.wgs84.longitude - favorite.coordinatePair.wgs84.longitude) < 0.000001