You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无需渲染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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 22:57:54