Unity基于天地图API自动下载瓦片地图【工具】

180 阅读5分钟

概述

这个工具是一个在Unity中基于天地图API,实现所定区域范围地图瓦片的自动下载和存储

设计初衷

  • 解决了什么问题: 在GIS、智慧城市、模拟仿真等项目中,常常需要大范围、高精度的真实世界地图作为底图。手动下载成千上万张瓦片地图不仅效率低下,而且难以管理。此模块旨在自动化这一过程。
  • 设计目标: 提供一个高效、可复用的开发工具,只需简单配置经纬度范围和缩放层级,即可快速生成一套可离线使用的本地地图瓦片缓存

核心特性

  • 瓦片自动下载: 根据设定的经纬度范围和缩放级别(3-18级),自动计算瓦片矩阵,并请求天地图API下载对应的瓦片图片。
  • 本地缓存机制: 首次下载的瓦片图片会以PNG格式保存到本地指定路径,后续可直接从本地加载,实现离线运行并节省网络资源。
  • 异步高性能下载: 采用 UniTaskUnityWebRequest 异步下载图片,下载过程完全不阻塞Unity主线程,保证了编辑期和运行时的流畅体验
  • 高质量纹理保障: 脚本启动时会自动设置高质量的纹理参数(各向异性过滤、Mipmap等),避免地图贴图在打包后因压缩而变得模糊。
  • 精准定位与范围选择: 自由输入地图中心的经纬度,并设定需要下载的精确范围
  • 多级LOD按需加载: 支持自定义下载一个或多个地图缩放层级,从宏观概览到微观细节,实现按需加载。
  • 天地图API深度集成: 提供专属字段预设API Token,并能一键切换地图类型(影像、地形等)与注记样式,满足多样化的需求下载。
  • 资源管理自由化: 可以根据项目需求,自由定义下载瓦片的分辨率大小和本地存储路径

依赖与源代码

依赖项

  • Unity版本: 2021.3.x 或更高

  • 外部库: UniTask (Cysharp.Threading.Tasks): 用于异步编程。

源代码

  1. 在您的项目 Scripts 文件夹中,创建TianMapDownloading脚本。
    • 创建 TianMapDownloading.cs 脚本** (随便挂载到场景中物体上)
public class TianMapDownloading : MonoBehaviour  
{
	public enum MapLayerType  
 {  
    [InspectorName("矢量地图")]  
    [Tooltip("vec_c: 标准矢量地图")]  
    Vector = 0,  
    [InspectorName("卫星影像")]  
    [Tooltip("img_c: 卫星影像地图")]  
    Satellite = 1,  
    [InspectorName("地形晕渲")]  
    [Tooltip("ter_c: 地形晕渲地图")]  
    Terrain = 2  
 }
	public enum AnnotationLayerType  
 {  
    [InspectorName("无注记")]  
    [Tooltip("不添加任何注记")]  
    None = 0,  
    [InspectorName("矢量注记")]  
    [Tooltip("cva_c: 矢量地图与卫星影像的注记")]  
    VectorAnnotation = 1,  
    [InspectorName("地形注记")]  
    [Tooltip("cta_c: 地形晕渲的注记")]  
    TerrainAnnotation = 2  
 }
 [SerializeField] private Vector2 mapCenter = new Vector2(111.72f, 40.76f);
 [SerializeField] private Vector2 mapAreaSize = new Vector2(0.2f, 0.2f);
 [SerializeField] private List<ZoomLevel> zoomLevels;  
 private const float BaseUnit = 0.0053f; 
 
 [SerializeField] private string apiKey = "";
 [SerializeField] private MapLayerType mapType = MapLayerType.Vector;
 [SerializeField] private AnnotationLayerType annotationType = AnnotationLayerType.VectorAnnotation;
 [SerializeField] private int imageWidth = 1024;
 [SerializeField] private int imageHeight = 1024;
 [SerializeField] private string downloadPath = "D:/Textures/";
 
 private readonly List<TileInfo> _tilesToDownload = new List<TileInfo>();
 [Serializable]  public class ZoomLevel { public int Zoom; }
 
 private class TileInfo  
{  
    public float lon; // 经度  
    public float lat; // 纬度  
    public int zoom;  // 缩放级别  
    public int index; // 图片索引号  
}

private void Start()  
{  
    StartDownload();  
}

private void StartDownload()  
{  
    Debug.Log("=== 开始下载任务 ===");  
    // 1. 根据配置,计算出所有需要下载的瓦片的URL和索引  
    GenerateTileDownloadList();  
    // 2. 异步下载所有瓦片  
    DownloadAllTileImages().Forget();  
}

private void GenerateTileDownloadList()  
{  
    _tilesToDownload.Clear();  
    var orderedZoomLevels = zoomLevels.OrderByDescending(zl => zl.Zoom).ToList();  
    float cumulativeOffset = 0f; float previousUnit = 0f;  
  
    for (int i = 0; i < orderedZoomLevels.Count; i++)  
    {        var zl = orderedZoomLevels[i];  
        if (i == 0) { cumulativeOffset = 0; } else if (i == 1) { cumulativeOffset = previousUnit / 2f; } else { cumulativeOffset += previousUnit; }  
        float currentUnit = BaseUnit * Mathf.Pow(2, 18 - zl.Zoom);  
        GenerateTilesForZoomLevel(zl.Zoom, cumulativeOffset, currentUnit);  
        previousUnit = currentUnit;    }    Debug.Log($"计算完成,共需下载 {_tilesToDownload.Count} 张瓦片图片。");  
}

private void GenerateTilesForZoomLevel(int zoom, float offset, float unit)  
{  
    float halfWidth = mapAreaSize.x / 2f; float halfHeight = mapAreaSize.y / 2f;  
    float startLon = mapCenter.x - halfWidth + offset; float endLon = mapCenter.x + halfWidth - offset;  
    float startLat = mapCenter.y + halfHeight - offset; float endLat = mapCenter.y - halfHeight - offset;  
  
    for (float lon = startLon; lon <= endLon; lon += unit)  
    {        for (float lat = startLat; lat > endLat; lat -= unit)  
        {            var tileInfo = new TileInfo  
            {  
                lon = lon,  
                lat = lat,  
                zoom = zoom,  
                index = _tilesToDownload.Count // 索引号按顺序递增  
            };  
            _tilesToDownload.Add(tileInfo);  
        }    }
}

private async UniTaskVoid DownloadAllTileImages()  
{  
    try { Directory.CreateDirectory(downloadPath); } // 确保目标目录存在  
    catch (Exception e) { Debug.LogError($"无法创建下载目录 '{downloadPath}': {e.Message}"); return; }  
  
    for (int i = 0; i < _tilesToDownload.Count; i++)  
    {        await DownloadAndSaveImageAsync(_tilesToDownload[i]);  
    }  
    Debug.Log("=== 所有瓦片下载任务已处理完毕! ===");  
}


private async UniTask DownloadAndSaveImageAsync(TileInfo tile)  
{  
    string mapLayers = GetMapLayersString();  
    string url = $"https://api.tianditu.gov.cn/staticimage?center={tile.lon},{tile.lat}&width={imageWidth}&height={imageHeight}&zoom={tile.zoom}&layers={mapLayers}&tk={apiKey}";  
  
    using (var uwr = UnityWebRequestTexture.GetTexture(url))  
    {        try { await uwr.SendWebRequest(); }  
        catch (Exception ex) { Debug.LogError($"下载瓦片 {tile.index} (URL: {url}) 时发生网络错误: {ex.Message}"); return; }  
  
        if (uwr.result != UnityWebRequest.Result.Success)  
        { Debug.LogError($"下载瓦片 {tile.index} 失败。URL: {url}, 错误: {uwr.error}"); return; }  
  
        Texture2D texture = DownloadHandlerTexture.GetContent(uwr);  
        byte[] pngData = texture.EncodeToPNG();  
        string filePath = Path.Combine(downloadPath, $"{tile.index}.png");  
  
        try  
        {  
            await File.WriteAllBytesAsync(filePath, pngData);  
            Debug.Log($"瓦片 {tile.index} 已成功保存至: {filePath}");  
        }        catch (IOException e) { Debug.LogError($"将瓦片 {tile.index} 保存至 '{filePath}' 失败: {e.Message}"); }  
        finally  
        {  
            // 清理创建的纹理以防止内存泄漏  
            if (Application.isPlaying) Destroy(texture); else DestroyImmediate(texture);  
        }    }
}

private string GetMapLayersString()  
{  
    string mapLayerStr = "";  
    switch (mapType)  
    {        case MapLayerType.Vector:    mapLayerStr = "vec_c"; break;  
        case MapLayerType.Satellite: mapLayerStr = "img_c"; break;  
        case MapLayerType.Terrain:   mapLayerStr = "ter_c"; break;  
    }  
    string annotationLayerStr = "";  
    switch (annotationType)  
    {        case AnnotationLayerType.VectorAnnotation:  annotationLayerStr = "cva_c"; break;  
        case AnnotationLayerType.TerrainAnnotation: annotationLayerStr = "cta_c"; break;  
        case AnnotationLayerType.None:  
        default:  
            break; // 注记为 None 时,字符串为空  
    }  
  
    // 如果有注记,则用逗号拼接  
    if (!string.IsNullOrEmpty(annotationLayerStr))  
    {        return $"{mapLayerStr},{annotationLayerStr}";  
    }    // 否则只返回底图  
    return mapLayerStr;  
}
}

API 使用文档

TianMapDownloading.cs 脚本的核心功能主要通过 Unity 的生命周期方法 Start() 自动触发,并通过 Inspector 面板进行配置。以下是其对外暴露的公共属性和可供外部调用的公共方法。

1. 公共属性

  • 功能描述: 这些属性会显示在 Unity 的 Inspector 面板中,用于配置脚本的行为和引用。
  • 参数 (Parameters):
参数名类型描述
mapCenterVector2地图区域中心的经纬度
mapAreaSizeVector2地图区域的总大小(经纬度跨度)
zoomLevelsList<ZoomLevel>配置所有需要下载的地图缩放层级
apiKeystring您的天地图 API 密钥 (tk)
mapTypeMapLayerType选择基础地图的类型(必选)
annotationTypeAnnotationLayerType选择要叠加的注记类型(可选)
imageWidthint下载的瓦片图像的宽度
imageHeightint下载的瓦片图像的高度
downloadPathstring用于保存瓦片图片的本地目录的绝对路径

2. 公共方法

  • 功能描述: 无,纯工具没有什么需要外部调用的情况,你们可以按自己需要修改这份代码

使用示例

image.png