无需渲染3D地形,如何获取地图可见点的海拔高度?
无需渲染3D地形即可查询DEM高程的方案
核心思路是:保留DEM数据源的加载,但不启用地形渲染(即不调用setTerrain),手动获取对应坐标的DEM瓦片并解析高程值,这样既可以离线使用,也不用依赖Tilequery API。
步骤1:添加DEM源但不启用地形渲染
只需要将RasterDemSource添加到样式中,不需要设置Terrain,地图会保持2D状态:
var demSource = RasterDemSource() demSource.url = "mapbox://mapbox.mapbox-terrain-dem-v1" demSource.tileSize = 512 // 对应Mapbox Terrain DEM的瓦片尺寸 do { try mapView.mapboxMap.style.addSource(demSource, id: "dem-source") } catch { print("添加DEM源失败: \(error.localizedDescription)") }
步骤2:计算坐标对应的DEM瓦片XYZ
要获取指定经纬度的DEM瓦片,需要先将经纬度转换为对应缩放级别的瓦片XYZ坐标(建议选择14级左右的缩放级别,平衡精度和瓦片大小):
func tileCoordinates(for coordinate: CLLocationCoordinate2D, zoomLevel: UInt) -> (x: UInt, y: UInt, z: UInt) { let zoom = Double(zoomLevel) let latRad = coordinate.latitude * .pi / 180.0 let x = UInt(floor((coordinate.longitude + 180.0) / 360.0 * pow(2.0, zoom))) let y = UInt(floor((1.0 - log(tan(latRad) + 1.0 / cos(latRad)) / .pi) / 2.0 * pow(2.0, zoom))) return (x, y, zoomLevel) }
步骤3:加载并解析DEM瓦片的高程值
从DEM源获取瓦片数据,将其转换为UIImage后,提取对应像素的RGB值计算高程(Mapbox Terrain DEM采用RGB编码规则:height = -10000 + ((R * 256 * 256 + G * 256 + B) * 0.1)):
func getElevation(at coordinate: CLLocationCoordinate2D, zoomLevel: UInt = 14, completion: @escaping (Double?) -> Void) { let (x, y, z) = tileCoordinates(for: coordinate, zoomLevel: zoomLevel) // 从DEM源请求瓦片 mapView.mapboxMap.style.source(withId: "dem-source") { [weak self] source in guard let demSource = source as? RasterDemSource, let self = self else { completion(nil) return } demSource.tile(forKind: .normal, x: x, y: y, zoomLevel: z) { result in switch result { case .success(let tile): guard let image = tile.image else { completion(nil) return } // 计算坐标在瓦片内的像素位置 let pixelPosition = self.pixelPositionInTile(for: coordinate, zoomLevel: z, tileSize: demSource.tileSize) guard let pixelColor = self.getPixelColor(from: image, at: pixelPosition) else { completion(nil) return } // 解析RGB到高程 let elevation = -10000.0 + (Double(pixelColor.red) * 256.0 * 256.0 + Double(pixelColor.green) * 256.0 + Double(pixelColor.blue)) * 0.1 completion(elevation) case .failure(let error): print("加载DEM瓦片失败: \(error.localizedDescription)") completion(nil) } } } } // 辅助函数:计算经纬度在瓦片内的像素坐标 func pixelPositionInTile(for coordinate: CLLocationCoordinate2D, zoomLevel: UInt, tileSize: UInt) -> CGPoint { let zoom = Double(zoomLevel) let latRad = coordinate.latitude * .pi / 180.0 let xFraction = (coordinate.longitude + 180.0) / 360.0 let yFraction = (1.0 - log(tan(latRad) + 1.0 / cos(latRad)) / .pi) / 2.0 let tileX = xFraction * pow(2.0, zoom) let tileY = yFraction * pow(2.0, zoom) let pixelX = (tileX - floor(tileX)) * Double(tileSize) let pixelY = (tileY - floor(tileY)) * Double(tileSize) return CGPoint(x: pixelX, y: pixelY) } // 辅助函数:获取图片指定位置的像素颜色 func getPixelColor(from image: UIImage, at point: CGPoint) -> (red: UInt8, green: UInt8, blue: UInt8)? { guard let cgImage = image.cgImage else { return nil } let width = cgImage.width let height = cgImage.height guard point.x >= 0, point.x < CGFloat(width), point.y >= 0, point.y < CGFloat(height) else { return nil } let bytesPerPixel = 4 let bytesPerRow = bytesPerPixel * width let bitsPerComponent = 8 var pixelData = [UInt8](repeating: 0, count: width * height * bytesPerPixel) guard let context = CGContext(data: &pixelData, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return nil } context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) let pixelIndex = (Int(point.y) * width + Int(point.x)) * bytesPerPixel let red = pixelData[pixelIndex] let green = pixelData[pixelIndex + 1] let blue = pixelData[pixelIndex + 2] return (red, green, blue) }
步骤4:使用方法
调用getElevation函数即可获取指定坐标的高程,支持离线使用(前提是已下载对应区域的DEM瓦片):
let targetCoordinate = CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194) getElevation(at: targetCoordinate) { elevation in if let elevation = elevation { print("目标坐标高程: \(elevation) 米") } else { print("无法获取高程数据") } }
注意事项
- 缩放级别选择:更高的缩放级别(如15-16)会提供更精确的高程,但瓦片数量更多,离线下载体积更大;12-14级适合大范围查询。
- 离线支持:使用Mapbox的离线下载功能,将
dem-source加入离线区域,即可在无网络时查询高程。 - 异步处理:瓦片加载是异步操作,需要通过completion handler获取结果,避免阻塞主线程。
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

