From 8acc33186c58339db2a7d95725f3d0c21fb115bc Mon Sep 17 00:00:00 2001 From: xweiba Date: Fri, 7 Aug 2026 14:13:25 +0800 Subject: [PATCH] fix: refresh MapKit coordinate type at runtime --- App/MapHomeView.swift | 271 ++++++++++++++---- App/MapLocationState.swift | 7 + Shared/CoordinateConverter.swift | 75 ++++- Shared/FavoriteLocationStore.swift | 21 +- .../FavoriteLocationStoreTests.swift | 30 ++ .../MapLocationStateTests.swift | 13 + Tests/map_refactor_contract_test.sh | 11 + 7 files changed, 371 insertions(+), 57 deletions(-) diff --git a/App/MapHomeView.swift b/App/MapHomeView.swift index aa0d1ee..ca2a720 100644 --- a/App/MapHomeView.swift +++ b/App/MapHomeView.swift @@ -78,10 +78,15 @@ struct MapHomeView: View { @State private var wifiChangeObserverToken: UUID? @State private var wifiVerificationTask: Task? @State private var wifiVerificationID: UUID? - @State private var copyConfirmed = false + @State private var copiedCoordinateSystem: CoordinateConverter.MapCoordinateSystem? @State private var spoofState: SpoofState = .idle @State private var locationOperationTask: Task? @State private var locationOperationID: UInt64 = 0 + @State private var mapCoordinateSystemRefreshTask: Task? + @State private var mapCoordinateSystemRefreshID: UInt64 = 0 + @State private var bluePointRefreshPending = false + @State private var realtimeButtonTask: Task? + @State private var favoriteSaveTask: Task? // 激活时的坐标(本地存,绕过 C 桥接层精度丢失) @State private var activeSpoofLat: Double? @State private var activeSpoofLon: Double? @@ -232,7 +237,7 @@ struct MapHomeView: View { .shadow(color: .black.opacity(0.2), radius: 6, y: 3) } } - .disabled(realtimeRequestTask != nil || realtime.isRequesting) + .disabled(realtimeButtonTask != nil || realtimeRequestTask != nil || realtime.isRequesting) } } .padding(.trailing, 16) @@ -279,6 +284,13 @@ struct MapHomeView: View { wifiVerificationTask?.cancel() wifiVerificationTask = nil wifiVerificationID = nil + mapCoordinateSystemRefreshTask?.cancel() + mapCoordinateSystemRefreshTask = nil + bluePointRefreshPending = false + realtimeButtonTask?.cancel() + realtimeButtonTask = nil + favoriteSaveTask?.cancel() + favoriteSaveTask = nil } .onChange(of: proxy.isRunning) { running in if runtimeMode.mode == .localWiFi, !running && spoofState == .active { @@ -412,27 +424,8 @@ struct MapHomeView: View { HStack { VStack(alignment: .leading, spacing: 3) { Text(mapState.displayName ?? "当前选点").font(.subheadline.weight(.semibold)).lineLimit(1) - Text(String(format: "%.6f, %.6f", mapState.selection.coordinate.latitude, mapState.selection.coordinate.longitude)) - .font(.caption.monospaced()) - .foregroundStyle(copyConfirmed ? .green : .secondary) - .onTapGesture { - let text = String(format: "%.6f, %.6f", mapState.selection.coordinate.latitude, mapState.selection.coordinate.longitude) - UIPasteboard.general.string = text - RuntimeLogger.info("APP", "地图", "已复制坐标") - copyConfirmed = true - DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { copyConfirmed = false } - } - .overlay(alignment: .top) { - if copyConfirmed { - Text("已复制") - .font(.caption2.bold()) - .foregroundStyle(.white) - .padding(.horizontal, 8) - .padding(.vertical, 2) - .background(.green, in: Capsule()) - .offset(y: -24) - } - } + coordinateRow(label: "国内标准 GCJ-02", system: .gcj02) + coordinateRow(label: "国际标准 WGS-84", system: .wgs84) } Spacer() // 帮助说明按钮 @@ -457,23 +450,7 @@ struct MapHomeView: View { favorites.select(nil) return } - let snapshot = currentSelectionFavorite - RuntimeLogger.info("APP", "坐标转换", "保存当前选点为收藏", details: [ - "当前地图标准": CoordinateConverter.currentMapCoordinateSystem.diagnosticName, - "输入字段": CoordinateConverter.currentMapCoordinateSystem.diagnosticName, - "持久化字段": "国际标准(WGS-84)+国内标准(GCJ-02)" - ]) - let favorite = favorites.save( - name: snapshot.name, - mapCoordinate: mapState.selection.coordinate, - mapCoordinateSystem: CoordinateConverter.currentMapCoordinateSystem, - accuracy: snapshot.accuracy - ) - mapState.selectFavorite( - favorite.coordinatePair.coordinate(for: CoordinateConverter.currentMapCoordinateSystem), - id: favorite.id, - name: favorite.name - ) + saveCurrentSelectionAsFavorite() } label: { Image(systemName: favorites.selectedFavoriteID != nil ? "star.fill" : "star") .font(.system(size: 18, weight: .semibold)) @@ -482,6 +459,7 @@ struct MapHomeView: View { } .buttonStyle(.plain) .foregroundStyle(favorites.selectedFavoriteID != nil ? .orange : .gray) + .disabled(favoriteSaveTask != nil) .accessibilityLabel(favorites.selectedFavoriteID != nil ? "已收藏,点击取消收藏" : "收藏当前选点") } // 收藏 @@ -761,6 +739,58 @@ struct MapHomeView: View { ) } + private var currentSelectionPair: CoordinatePair { + if let stored = LastCoordinateStore.load(), + stored.coordinate(for: CoordinateConverter.currentMapCoordinateSystem) + .isApproximatelyEqual(to: mapState.selection.coordinate) { + return stored.coordinatePair + } + return CoordinatePair( + mapCoordinate: mapState.selection.coordinate, + mapCoordinateSystem: CoordinateConverter.currentMapCoordinateSystem + ) + } + + private func coordinateRow( + label: String, + system: CoordinateConverter.MapCoordinateSystem + ) -> some View { + let coordinate = currentSelectionPair.coordinate(for: system) + let text = String(format: "%.6f, %.6f", coordinate.latitude, coordinate.longitude) + return HStack(spacing: 6) { + Text(label) + .font(.caption2.weight(.medium)) + .foregroundStyle(.secondary) + .frame(width: 112, alignment: .leading) + Text(text) + .font(.caption.monospaced()) + .foregroundStyle(copiedCoordinateSystem == system ? .green : .secondary) + .lineLimit(1) + } + .contentShape(Rectangle()) + .onTapGesture { + UIPasteboard.general.string = text + copiedCoordinateSystem = system + RuntimeLogger.info("APP", "地图", "已复制坐标", details: [ + "坐标标准": system.diagnosticName + ]) + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { + if copiedCoordinateSystem == system { copiedCoordinateSystem = nil } + } + } + .overlay(alignment: .topTrailing) { + if copiedCoordinateSystem == system { + Text("已复制") + .font(.caption2.bold()) + .foregroundStyle(.white) + .padding(.horizontal, 8) + .padding(.vertical, 2) + .background(.green, in: Capsule()) + .offset(y: -24) + } + } + } + private var testFavorite: FavoriteLocation { currentSelectionFavorite } private func startMapRuntimeOnce() { @@ -774,19 +804,152 @@ struct MapHomeView: View { scheduleGeocode(pair: pair, revision: mapState.selection.revision) } - private func reprojectMapSelection(for change: CoordinateConverter.MapCoordinateSystemChange) { + @discardableResult + private func reprojectMapSelection(for change: CoordinateConverter.MapCoordinateSystemChange) -> Bool { // Every current selection is persisted as a complete coordinate pair at // its input boundary. Replaying the matching stored representation // avoids a second GCJ/WGS conversion and its accumulated offset. guard let stored = LastCoordinateStore.load() else { RuntimeLogger.warning("APP", "坐标转换", "地图坐标标准切换时未找到当前选点缓存") - return + return false } mapState.reprojectSelectionForMapCoordinateSystemChange(stored.coordinate(for: change.current)) RuntimeLogger.info("APP", "坐标转换", "地图坐标标准切换后已使用缓存坐标对回显当前选点", details: [ "from": change.previous.rawValue, "to": change.current.rawValue ]) + return true + } + + private func applyRuntimeMapCoordinateSystemChange( + _ change: CoordinateConverter.MapCoordinateSystemChange, + reason: String + ) { + geocodeDebounceTask?.cancel() + reverseGeocodeTask?.cancel() + searchRequestID &+= 1 + isSearching = false + searchResults = [] + searchError = "" + realtimeRequestTask?.cancel() + realtimeRequestTask = nil + realtimeRequestContext = nil + mapState.clearRealtimeLocationForMapCoordinateSystemChange() + let pinWasReprojected = reprojectMapSelection(for: change) + if let stored = LastCoordinateStore.load() { + scheduleGeocode(pair: stored.coordinatePair, revision: mapState.selection.revision) + } + RuntimeLogger.warning("APP", "坐标转换", "地图坐标类型已变化", details: [ + "触发原因": reason, + "旧类型": change.previous.diagnosticName, + "新类型": change.current.diagnosticName, + "图钉已按新类型重设": String(pinWasReprojected), + "蓝点缓存": "已清理", + "搜索结果": "已清理", + "异步地理编码": "已重置" + ]) + } + + @discardableResult + private func refreshRuntimeMapCoordinateSystem(reason: String) async -> Bool { + let result = await CoordinateConverter.refreshRuntimeMapCoordinateSystem(reason: reason) + guard !Task.isCancelled else { return false } + switch result { + case .changed(let change): + applyRuntimeMapCoordinateSystemChange(change, reason: reason) + return true + case .unchanged: + return true + case .unavailable, .cancelled: + return false + } + } + + private func scheduleBluePointMapCoordinateSystemRefresh() { + guard mapCoordinateSystemRefreshTask == nil else { + bluePointRefreshPending = true + return + } + mapCoordinateSystemRefreshID &+= 1 + let refreshID = mapCoordinateSystemRefreshID + mapCoordinateSystemRefreshTask = Task { @MainActor in + defer { + if refreshID == mapCoordinateSystemRefreshID { + let needsAnotherRefresh = bluePointRefreshPending && !Task.isCancelled + mapCoordinateSystemRefreshTask = nil + bluePointRefreshPending = false + if needsAnotherRefresh { + scheduleBluePointMapCoordinateSystemRefresh() + } + } + } + // Coalesce the didUpdate/regionDidChange pair generated by one + // native location sample without caching the probe result. + try? await Task.sleep(nanoseconds: 350_000_000) + guard !Task.isCancelled, refreshID == mapCoordinateSystemRefreshID else { return } + var attempt = 0 + repeat { + bluePointRefreshPending = false + _ = await refreshRuntimeMapCoordinateSystem(reason: "MapKit蓝点新样本") + attempt += 1 + } while !Task.isCancelled + && refreshID == mapCoordinateSystemRefreshID + && bluePointRefreshPending + && attempt < 2 + } + } + + private func awaitCoordinatedMapCoordinateSystemRefresh(reason: String) async { + if let pendingRefresh = mapCoordinateSystemRefreshTask { + await pendingRefresh.value + return + } + mapCoordinateSystemRefreshID &+= 1 + let refreshID = mapCoordinateSystemRefreshID + let task = Task { @MainActor in + _ = await refreshRuntimeMapCoordinateSystem(reason: reason) + } + mapCoordinateSystemRefreshTask = task + await task.value + if refreshID == mapCoordinateSystemRefreshID { + mapCoordinateSystemRefreshTask = nil + } + } + + private func saveCurrentSelectionAsFavorite() { + guard favoriteSaveTask == nil else { return } + let snapshot = currentSelectionFavorite + // Preserve the pair created when this selection entered the map. If + // the runtime probe changes type, replay its other stored field instead + // of reinterpreting the old visible coordinate as the new type. + let pair = currentSelectionPair + let selectionRevision = mapState.selection.revision + favoriteSaveTask = Task { @MainActor in + defer { favoriteSaveTask = nil } + await awaitCoordinatedMapCoordinateSystemRefresh(reason: "保存收藏") + guard !Task.isCancelled else { + return + } + guard mapState.selection.revision == selectionRevision else { + RuntimeLogger.info("APP", "坐标转换", "取消保存收藏:检测期间当前选点已变化") + return + } + RuntimeLogger.info("APP", "坐标转换", "保存当前选点为收藏", details: [ + "当前地图标准": CoordinateConverter.currentMapCoordinateSystem.diagnosticName, + "持久化字段": "国际标准(WGS-84)+国内标准(GCJ-02)" + ]) + let favorite = favorites.save( + name: snapshot.name, + coordinatePair: pair, + accuracy: snapshot.accuracy + ) + mapState.selectFavorite( + pair.coordinate(for: CoordinateConverter.currentMapCoordinateSystem), + id: favorite.id, + name: favorite.name + ) + LastCoordinateStore.save(coordinatePair: pair, zoomMeters: mapState.viewportMeters) + } } private func registerWiFiChangeObserver() { @@ -916,6 +1079,18 @@ struct MapHomeView: View { } private func requestRealtimeLocation() { + guard realtimeButtonTask == nil else { return } + realtimeButtonTask = Task { @MainActor in + defer { realtimeButtonTask = nil } + await awaitCoordinatedMapCoordinateSystemRefresh(reason: "点击实时定位") + guard !Task.isCancelled else { + return + } + performRealtimeLocationRequest() + } + } + + private func performRealtimeLocationRequest() { let intent = mapState.beginRealtimeIntent() RuntimeLogger.info("APP", "实时定位", "用户点击实时定位", details: [ "intentID": String(intent.id), @@ -951,6 +1126,7 @@ struct MapHomeView: View { private func handleNativeRealtimeLocation(_ location: CLLocation) { mapState.updateRealtimeLocation(location) logSpoofCoordinateDiagnosisIfNeeded(location) + scheduleBluePointMapCoordinateSystemRefresh() guard let context = realtimeRequestContext else { return } @@ -1079,14 +1255,7 @@ struct MapHomeView: View { sourceDescription: String ) { let currentViewport = mapState.viewportMeters - let previousMapCoordinateSystem = CoordinateConverter.currentMapCoordinateSystem let sourceCoordinateSystem = source.coordinateSystem - let mapCoordinateSystemChange = source == .coreLocation - ? CoordinateConverter.correctMapCoordinateSystemUsingRealtime(coordinate) - : nil - if let change = mapCoordinateSystemChange { - reprojectMapSelection(for: change) - } let pair = CoordinateConverter.coordinatePair( lat: coordinate.latitude, lon: coordinate.longitude, @@ -1103,9 +1272,7 @@ struct MapHomeView: View { "intentID": String(intent.id), "intent选点revision": String(intent.selectionRevision), "当前选点revision": String(mapState.selection.revision), - "修正前地图标准": previousMapCoordinateSystem.rawValue, - "修正后地图标准": CoordinateConverter.currentMapCoordinateSystem.rawValue, - "地图标准发生修正": String(mapCoordinateSystemChange != nil), + "App已确认地图标准": CoordinateConverter.currentMapCoordinateSystem.rawValue, "accepted": String(accepted), "显示坐标字段": CoordinateConverter.currentMapCoordinateSystem.rawValue, "持久化字段": "WGS-84+GCJ-02" diff --git a/App/MapLocationState.swift b/App/MapLocationState.swift index 043f902..514d644 100644 --- a/App/MapLocationState.swift +++ b/App/MapLocationState.swift @@ -222,6 +222,13 @@ final class MapLocationState: ObservableObject { if coordinate == nil { realtimeLocation = nil } } + /// Discards a blue-point sample represented in a superseded MapKit + /// coordinate system. The next native callback repopulates the cache. + func clearRealtimeLocationForMapCoordinateSystemChange() { + realtimeLocation = nil + realtimeCoordinate = nil + } + func updateExplicitName(_ name: String, forFavoriteID favoriteID: UUID) { guard selection.source == .favorite(favoriteID) else { return } selection = MapSelection( diff --git a/Shared/CoordinateConverter.swift b/Shared/CoordinateConverter.swift index a475a5e..776216c 100644 --- a/Shared/CoordinateConverter.swift +++ b/Shared/CoordinateConverter.swift @@ -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 = name == "林士街" ? .gcj02 : .wgs84 + nextType = mapCoordinateSystem(forFixedAnchorFirstResultName: name) initialMapCoordinateSystemUsedFallback = false RuntimeLogger.info("APP", "坐标转换", "地图坐标标准检测获得明确结果", details: [ "首条名称": name, @@ -155,6 +162,72 @@ enum CoordinateConverter { return nextType } + /// 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 refreshRuntimeMapCoordinateSystem(reason: String) async -> RuntimeMapCoordinateSystemRefreshResult { + guard !mapCoordinateSystemCheckPending else { + RuntimeLogger.info("APP", "坐标转换", "地图坐标标准运行期检测合并到进行中请求", details: [ + "触发原因": reason, + "当前标准": currentMapCoordinateSystem.rawValue + ]) + return .unchanged(currentMapCoordinateSystem) + } + mapCoordinateSystemCheckPending = true + defer { mapCoordinateSystemCheckPending = false } + + 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(forFixedAnchorFirstResultName: name) + initialMapCoordinateSystemUsedFallback = false + 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), + "from": previous.rawValue, + "to": detected.rawValue + ]) + return .changed(change) + case .unavailable(let failureReason), .timedOut(let failureReason): + RuntimeLogger.warning("APP", "坐标转换", "地图坐标标准运行期检测失败,保留当前标准", details: [ + "触发原因": reason, + "原因": failureReason, + "保留标准": previous.rawValue + ]) + return .unavailable(reason: failureReason) + case .cancelled: + RuntimeLogger.info("APP", "坐标转换", "地图坐标标准运行期检测已取消", details: [ + "触发原因": reason, + "保留标准": previous.rawValue + ]) + return .cancelled + } + } + + static func mapCoordinateSystem(forFixedAnchorFirstResultName name: String) -> MapCoordinateSystem { + name == "林士街" ? .gcj02 : .wgs84 + } + /// A user-requested realtime sample is WGS-84 and can correct a provisional /// startup map coordinate system without altering persisted coordinate pairs. @MainActor diff --git a/Shared/FavoriteLocationStore.swift b/Shared/FavoriteLocationStore.swift index a62d506..008bad5 100644 --- a/Shared/FavoriteLocationStore.swift +++ b/Shared/FavoriteLocationStore.swift @@ -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 diff --git a/Tests/PaopaoLocationSpooferTests/FavoriteLocationStoreTests.swift b/Tests/PaopaoLocationSpooferTests/FavoriteLocationStoreTests.swift index d687209..e1186db 100644 --- a/Tests/PaopaoLocationSpooferTests/FavoriteLocationStoreTests.swift +++ b/Tests/PaopaoLocationSpooferTests/FavoriteLocationStoreTests.swift @@ -36,6 +36,25 @@ final class FavoriteLocationStoreTests: XCTestCase { XCTAssertNotEqual(favorite.coordinatePair.gcj02.longitude, wgs.longitude) } + func testSavingPrecomputedPairDoesNotReinterpretItAfterMapTypeRefresh() { + let suite = "FavoriteLocationStoreTests.\(UUID().uuidString)" + let defaults = UserDefaults(suiteName: suite)! + defer { defaults.removePersistentDomain(forName: suite) } + let pair = CoordinateConverter.coordinatePair( + lat: 22.296_642, + lon: 114.172_175, + mapCoordinateSystem: .wgs84 + ) + + let favorite = FavoriteLocationStore(defaults: defaults).save( + name: "香港天文台", + coordinatePair: pair, + accuracy: 25 + ) + + XCTAssertEqual(favorite.coordinatePair, pair) + } + func testLegacyFavoriteIsUpgradedAsDomesticGCJAndRewritten() throws { let suite = "FavoriteLocationStoreTests.\(UUID().uuidString)" let defaults = UserDefaults(suiteName: suite)! @@ -122,6 +141,17 @@ final class FavoriteLocationStoreTests: XCTestCase { XCTAssertNil(CoordinateConverter.diagnoseRepresentation(sample: unrelated, pair: pair).inferredSystem) } + func testFixedAnchorResultNameUsesOneSharedMapTypeRule() { + XCTAssertEqual( + CoordinateConverter.mapCoordinateSystem(forFixedAnchorFirstResultName: "林士街"), + .gcj02 + ) + XCTAssertEqual( + CoordinateConverter.mapCoordinateSystem(forFixedAnchorFirstResultName: "Connaught Road West"), + .wgs84 + ) + } + func testMapConfigurationNeverRequestsRealUserLocation() { XCTAssertFalse(MapConfiguration.default.showsUserLocation) XCTAssertFalse(MapConfiguration.default.allowsCurrentLocationRequest) diff --git a/Tests/PaopaoLocationSpooferTests/MapLocationStateTests.swift b/Tests/PaopaoLocationSpooferTests/MapLocationStateTests.swift index 750231f..6bd7c72 100644 --- a/Tests/PaopaoLocationSpooferTests/MapLocationStateTests.swift +++ b/Tests/PaopaoLocationSpooferTests/MapLocationStateTests.swift @@ -173,6 +173,19 @@ final class MapLocationStateTests: XCTestCase { XCTAssertEqual(state.selection.source, .search) } + func testMapCoordinateSystemChangeClearsSupersededRealtimeSampleWithoutMovingSelection() { + let state = MapLocationState(initialCoordinate: initial) + state.selectSearchResult(.init(latitude: 31.23, longitude: 121.47), name: "外滩") + let selection = state.selection + state.updateRealtimeLocation(CLLocation(latitude: 30.42, longitude: 114.25)) + + state.clearRealtimeLocationForMapCoordinateSystemChange() + + XCTAssertNil(state.realtimeLocation) + XCTAssertNil(state.realtimeCoordinate) + XCTAssertEqual(state.selection, selection) + } + func testRealtimeIntentCanImmediatelyAcceptNativeLocation() { let state = MapLocationState(initialCoordinate: initial) let nativeLocation = CLLocation(latitude: 30.42, longitude: 114.25) diff --git a/Tests/map_refactor_contract_test.sh b/Tests/map_refactor_contract_test.sh index 1d3ed03..d21a0de 100755 --- a/Tests/map_refactor_contract_test.sh +++ b/Tests/map_refactor_contract_test.sh @@ -65,6 +65,17 @@ if grep -q 'logEvent("CONNECT " + host + " -> passthrough")' "$ROOT/Core/proxy.g fi grep -q 'enum SystemSettingsNavigator' "$SETTINGS_NAVIGATOR" || fail "shared settings navigator is missing" grep -q 'await CoordinateConverter.resolveInitialMapCoordinateSystem()' "$CONTENT" || fail "map type must resolve before MapHomeView construction" +grep -q 'refreshRuntimeMapCoordinateSystem(reason:' "$CONVERTER" || fail "fixed-anchor map type must support runtime refresh" +grep -q 'scheduleBluePointMapCoordinateSystemRefresh()' "$MAP_HOME" || fail "native blue-point samples must trigger runtime map-type refresh while spoofing" +! grep -A3 'private func scheduleBluePointMapCoordinateSystemRefresh' "$MAP_HOME" | grep -q 'spoofState == .active' || fail "blue-point map-type refresh must also detect the return to physical location" +grep -q 'awaitCoordinatedMapCoordinateSystemRefresh(reason: "点击实时定位")' "$MAP_HOME" || fail "realtime button must await the coordinated map-type refresh" +grep -q 'awaitCoordinatedMapCoordinateSystemRefresh(reason: "保存收藏")' "$MAP_HOME" || fail "favorite save must await the coordinated map-type refresh" +! grep -q 'source == .coreLocation.*correctMapCoordinateSystemUsingRealtime' "$MAP_HOME" || fail "runtime Core Location samples must not infer MapKit type" +grep -q 'clearRealtimeLocationForMapCoordinateSystemChange' "$MAP_HOME" || fail "map-type changes must discard superseded blue-point samples" +grep -q '地图坐标类型已变化' "$MAP_HOME" || fail "map-type changes must emit an explicit searchable business log" +grep -q '图钉已按新类型重设' "$MAP_HOME" || fail "map-type change log must report pin reprojection" +grep -q '国内标准 GCJ-02' "$MAP_HOME" || fail "current selection panel must show GCJ-02" +grep -q '国际标准 WGS-84' "$MAP_HOME" || fail "current selection panel must show WGS-84" grep -q 'phase = .map' "$CONTENT" || fail "ContentView must explicitly gate MapHomeView construction" ! grep -q 'startTileProbe' "$MAP_HOME" || fail "MapHomeView must not start a second fixed-anchor coordinate-system probe" ! grep -q 'initializeMap()' "$MAP_HOME" || fail "MapHomeView must not replay a second map initialization from onAppear"