如何在MapKit中接入谷歌地图切片(基于MKTileOverlay)
如何在MapKit中接入谷歌地图切片(基于MKTileOverlay)
嘿,完全懂你的处境——已经在MapKit上搭了一大堆自定义逻辑,肯定不想推倒重来换成Google Maps SDK。别慌,你想的没错,重写MKTileOverlay的url(forTilePath:)方法就是正确的解决方案,我来一步步给你捋清楚怎么实现。
首先得搞明白两种瓦片URL格式的核心差异:谷歌用的是视口经纬度范围(north/south/east/west)来请求切片,而MapKit给我们的是瓦片的缩放级别(z)、横向索引(x)、纵向索引(y)。所以核心工作就是把MapKit的x/y/z转换成谷歌需要的经纬度边界。
第一步:实现坐标转换逻辑
我们需要写两个辅助方法:先把MKTileOverlay的瓦片路径转换成对应的地图矩形范围,再把这个矩形转成经纬度的南北西东边界。直接上代码,你可以把这些逻辑封装在自定义的Overlay类里:
private func mapRect(for tilePath: MKTileOverlayPath) -> MKMapRect { // 瓦片默认尺寸是256x256,谷歌的2D切片也是这个规格 let tileSize = CGSize(width: 256, height: 256) // 计算当前瓦片在MapKit坐标系里的原点 let origin = MKMapPoint(x: Double(tilePath.x) * tileSize.width, y: Double(tilePath.y) * tileSize.height) let size = MKMapSize(width: tileSize.width, height: tileSize.height) return MKMapRect(origin: origin, size: size) } private func coordinateBounds(for mapRect: MKMapRect) -> (north: CLLocationDegrees, south: CLLocationDegrees, east: CLLocationDegrees, west: CLLocationDegrees) { // 把地图矩形的东北、西南角转成经纬度 let northeast = MKMapPoint(x: mapRect.maxX, y: mapRect.minY).coordinate let southwest = MKMapPoint(x: mapRect.minX, y: mapRect.maxY).coordinate return (north: northeast.latitude, south: southwest.latitude, east: northeast.longitude, west: southwest.longitude) }
第二步:自定义MKTileOverlay并重写URL生成方法
接下来我们创建一个自定义的Overlay类,把上面的转换逻辑整合进去,并且按照谷歌的要求拼接URL:
class GoogleTileOverlay: MKTileOverlay { let apiKey: String var sessionToken: String // 用var是因为session token需要定期刷新 init(apiKey: String, sessionToken: String) { self.apiKey = apiKey self.sessionToken = sessionToken // 因为我们要完全自定义URL,所以urlTemplate传nil super.init(urlTemplate: nil) } override func url(forTilePath path: MKTileOverlayPath) -> URL { // 先把瓦片路径转成经纬度边界 let mapRect = self.mapRect(for: path) let bounds = self.coordinateBounds(for: mapRect) // 用URLComponents来拼接参数,比直接字符串拼接更安全 var components = URLComponents() components.scheme = "https" components.host = "tile.googleapis.com" components.path = "/tile/v1/viewport" components.queryItems = [ URLQueryItem(name: "session", value: sessionToken), URLQueryItem(name: "key", value: apiKey), URLQueryItem(name: "zoom", value: "\(path.z)"), URLQueryItem(name: "north", value: "\(bounds.north)"), URLQueryItem(name: "south", value: "\(bounds.south)"), URLQueryItem(name: "east", value: "\(bounds.east)"), URLQueryItem(name: "west", value: "\(bounds.west)") ] guard let url = components.url else { fatalError("生成谷歌瓦片URL失败,请检查参数格式") } return url } // 把刚才的两个辅助方法放这里 private func mapRect(for tilePath: MKTileOverlayPath) -> MKMapRect { let tileSize = CGSize(width: 256, height: 256) let origin = MKMapPoint(x: Double(tilePath.x) * tileSize.width, y: Double(tilePath.y) * tileSize.height) let size = MKMapSize(width: tileSize.width, height: tileSize.height) return MKMapRect(origin: origin, size: size) } private func coordinateBounds(for mapRect: MKMapRect) -> (north: CLLocationDegrees, south: CLLocationDegrees, east: CLLocationDegrees, west: CLLocationDegrees) { let northeast = MKMapPoint(x: mapRect.maxX, y: mapRect.minY).coordinate let southwest = MKMapPoint(x: mapRect.minX, y: mapRect.maxY).coordinate return (north: northeast.latitude, south: southwest.latitude, east: northeast.longitude, west: southwest.longitude) } }
第三步:在MapKit中使用这个自定义Overlay
最后就是把这个Overlay加到你的地图视图里,注意先去谷歌控制台获取有效的API Key,并且生成session token(session token是短期有效的,需要定期刷新):
// 替换成你的谷歌API Key和Session Token let googleOverlay = GoogleTileOverlay(apiKey: "YOUR_API_KEY", sessionToken: "YOUR_SESSION_TOKEN") // 如果想完全替换MapKit的默认地图,就设为true;如果只是叠加在默认地图上,设为false googleOverlay.canReplaceMapContent = true // 添加到地图视图 mapView.addOverlay(googleOverlay)
几个需要注意的坑
- Session Token刷新:谷歌的session token有效期很短,所以你得写逻辑定期获取新的token,然后更新
googleOverlay.sessionToken的值,不然请求会失败。 - API权限与计费:确保你的谷歌API Key已经启用了「Maps Tile API」,而且这个服务是收费的,记得设置好预算提醒。
- 瓦片尺寸:如果谷歌的瓦片有其他尺寸(比如512x512),需要调整
tileSize的数值,不过默认都是256x256,一般不用改。 - 坐标系一致性:MapKit和谷歌的瓦片都是用Web Mercator投影,所以转换出来的坐标是准确的,不会有偏移问题。
备注:内容来源于stack exchange,提问作者sugar baron
相关产品推荐
相关产品推荐

