From 96012f5afa64df61d6ef8b7f1154e8237da34358 Mon Sep 17 00:00:00 2001 From: xweiba Date: Wed, 5 Aug 2026 23:20:02 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=93=A6=E7=89=87=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E9=80=9A=E8=BF=87=20MKLocalSearch=20=E5=8F=8D=E6=9F=A5?= =?UTF-8?q?=E5=9C=B0=E5=90=8D=E5=90=AB=E4=B8=AD=E6=96=87=E5=88=A4=E6=96=AD?= =?UTF-8?q?=EF=BC=8830s=E7=BC=93=E5=AD=98=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App/MapHomeView.swift | 20 ++-------- Shared/CoordinateConverter.swift | 64 ++++++++++++++++---------------- 2 files changed, 36 insertions(+), 48 deletions(-) diff --git a/App/MapHomeView.swift b/App/MapHomeView.swift index bee6f54..8ead538 100644 --- a/App/MapHomeView.swift +++ b/App/MapHomeView.swift @@ -90,8 +90,7 @@ struct MapHomeView: View { }, onUserCenterChanged: { coordinate, distance in 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) guard revision != previousRevision else { return } let wgs = CoordinateConverter.toStored(lat: coordinate.latitude, lon: coordinate.longitude) @@ -104,8 +103,7 @@ struct MapHomeView: View { }, onMapTap: { coordinate in 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) LastCoordinateStore.save(lat: wgs.lat, lon: wgs.lon) scheduleGeocode(coordinate: coordinate, revision: revision) @@ -795,18 +793,7 @@ struct MapHomeView: View { searchError = error.localizedDescription return } - let items = (response?.mapItems ?? []).prefix(6) - // 用首个搜索结果坐标推算瓦片类型 - 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 + searchResults = (response?.mapItems ?? []).prefix(6).map { item in let r = SearchLocationResult( name: item.name ?? "未命名", subtitle: [item.placemark.locality, item.placemark.subLocality, item.placemark.thoroughfare] @@ -830,7 +817,6 @@ struct MapHomeView: View { geocodeDebounceTask?.cancel() reverseGeocodeTask?.cancel() favorites.select(nil) - CoordinateConverter.updateTileType(lat: result.coordinate.latitude, lon: result.coordinate.longitude) mapState.selectSearchResult(result.coordinate, name: result.name) let wgs = CoordinateConverter.toStored(lat: result.coordinate.latitude, lon: result.coordinate.longitude) LastCoordinateStore.save(lat: wgs.lat, lon: wgs.lon) diff --git a/Shared/CoordinateConverter.swift b/Shared/CoordinateConverter.swift index af5fcc8..e1ffd5b 100644 --- a/Shared/CoordinateConverter.swift +++ b/Shared/CoordinateConverter.swift @@ -1,5 +1,6 @@ import Foundation import CoreLocation +import MapKit /// GCJ-02 (火星坐标) ↔ WGS-84 坐标转换。 /// @@ -18,18 +19,38 @@ enum CoordinateConverter { // MARK: - 全局瓦片类型 - /// 当前地图瓦片坐标系(每次从地图拿到坐标后更新) + /// 当前地图瓦片坐标系 @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 - static func updateTileType(lat: Double, lon: Double) { - let type = detectType(lat: lat, lon: lon) - if type != currentTileType { - currentTileType = type - RuntimeLogger.info("APP", "坐标转换", "地图瓦片切换 → \(type.rawValue)", details: [ - "坐标": "\(lat), \(lon)" - ]) + private static func detectTileByGeocode(lat: Double, lon: Double) { + guard !tileCheckPending else { return } + if let last = lastTileCheck, -last.timeIntervalSinceNow < 30 { return } + tileCheckPending = true + let loc = CLLocation(latitude: lat, longitude: 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 存储 @MainActor 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 { RuntimeLogger.info("APP", "坐标转换", "toStored: 不转 瓦片=\(currentTileType.rawValue)", details: [ "lat": String(lat), "lon": String(lon) @@ -58,6 +79,7 @@ enum CoordinateConverter { /// WGS-84 存储 → 当前地图瓦片坐标系(显示用) @MainActor static func toDisplay(lat: Double, lon: Double) -> (lat: Double, lon: Double) { + detectTileByGeocode(lat: lat, lon: lon) guard currentTileType == .gcj02 else { RuntimeLogger.info("APP", "坐标转换", "toDisplay: WGS-84 → 不转 瓦片=\(currentTileType.rawValue)", details: [ "lat": String(lat), "lon": String(lon) @@ -74,7 +96,7 @@ enum CoordinateConverter { return gcj } - // MARK: - 类型检测 + // MARK: - 工具 /// Haversine 距离(米) 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)) } - /// 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: - 核心转换 /// GCJ-02 → WGS-84(迭代法,精度优于 0.5 米)