概述
这个工具是一个在Unity中基于天地图API,实现所定区域范围地图瓦片的自动下载和存储
设计初衷
- 解决了什么问题: 在GIS、智慧城市、模拟仿真等项目中,常常需要大范围、高精度的真实世界地图作为底图。手动下载成千上万张瓦片地图不仅效率低下,而且难以管理。此模块旨在自动化这一过程。
- 设计目标: 提供一个高效、可复用的开发工具,只需简单配置经纬度范围和缩放层级,即可快速生成一套可离线使用的本地地图瓦片缓存
核心特性
- 瓦片自动下载: 根据设定的经纬度范围和缩放级别(3-18级),自动计算瓦片矩阵,并请求天地图API下载对应的瓦片图片。
- 本地缓存机制: 首次下载的瓦片图片会以PNG格式保存到本地指定路径,后续可直接从本地加载,实现离线运行并节省网络资源。
- 异步高性能下载: 采用
UniTask和UnityWebRequest异步下载图片,下载过程完全不阻塞Unity主线程,保证了编辑期和运行时的流畅体验 - 高质量纹理保障: 脚本启动时会自动设置高质量的纹理参数(各向异性过滤、Mipmap等),避免地图贴图在打包后因压缩而变得模糊。
- 精准定位与范围选择: 自由输入地图中心的经纬度,并设定需要下载的精确范围
- 多级LOD按需加载: 支持自定义下载一个或多个地图缩放层级,从宏观概览到微观细节,实现按需加载。
- 天地图API深度集成: 提供专属字段预设API Token,并能一键切换地图类型(影像、地形等)与注记样式,满足多样化的需求下载。
- 资源管理自由化: 可以根据项目需求,自由定义下载瓦片的分辨率大小和本地存储路径
依赖与源代码
依赖项
-
Unity版本:
2021.3.x或更高 -
外部库:
UniTask (Cysharp.Threading.Tasks): 用于异步编程。
源代码
- 在您的项目
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):
| 参数名 | 类型 | 描述 |
|---|---|---|
mapCenter | Vector2 | 地图区域中心的经纬度 |
mapAreaSize | Vector2 | 地图区域的总大小(经纬度跨度) |
zoomLevels | List<ZoomLevel> | 配置所有需要下载的地图缩放层级 |
apiKey | string | 您的天地图 API 密钥 (tk) |
mapType | MapLayerType | 选择基础地图的类型(必选) |
annotationType | AnnotationLayerType | 选择要叠加的注记类型(可选) |
imageWidth | int | 下载的瓦片图像的宽度 |
imageHeight | int | 下载的瓦片图像的高度 |
downloadPath | string | 用于保存瓦片图片的本地目录的绝对路径 |
2. 公共方法
- 功能描述: 无,纯工具没有什么需要外部调用的情况,你们可以按自己需要修改这份代码