第09章-瓦片图层与在线地图
第09章:瓦片图层与在线地图
9.1 瓦片地图概述
9.1.1 瓦片金字塔结构
瓦片地图将地图数据预先切割成小块图片,按照金字塔结构组织:
缩放级别 0: 1 张瓦片 (整个世界)
缩放级别 1: 4 张瓦片 (2x2)
缩放级别 2: 16 张瓦片 (4x4)
缩放级别 n: 4^n 张瓦片 (2^n x 2^n)
9.1.2 常用瓦片坐标系
| 方案 | 原点 | Y轴方向 | 使用者 |
|---|---|---|---|
| TMS | 左下 | 上 | OpenLayers, TileCache |
| Google/OSM | 左上 | 下 | Google Maps, OpenStreetMap |
| QuadKey | 左上 | 下 | Bing Maps |
9.1.3 Web Mercator 投影
大多数在线地图使用 EPSG:3857 (Web Mercator) 投影:
- 范围: -20037508.34 到 20037508.34 米
- 纬度限制: 约 -85.05° 到 85.05°
9.2 BruTile 集成
9.2.1 安装配置
# 安装 NuGet 包
Install-Package SharpMap.Layers.BruTile
Install-Package BruTile
9.2.2 创建瓦片图层
using BruTile;
using BruTile.Web;
using BruTile.Predefined;
using SharpMap.Layers;
// 使用预定义的 OpenStreetMap
var osmTileSource = KnownTileSources.Create(KnownTileSource.OpenStreetMap);
var osmLayer = new TileLayer(osmTileSource, "OpenStreetMap");
map.BackgroundLayer.Add(osmLayer);
// 自定义瓦片源
var customTileSource = new HttpTileSource(
new GlobalSphericalMercator(0, 18),
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
new[] { "a", "b", "c" },
"OSM");
var customLayer = new TileLayer(customTileSource, "Custom OSM");
9.2.3 常用瓦片源
// OpenStreetMap
var osm = KnownTileSources.Create(KnownTileSource.OpenStreetMap);
// OpenCycleMap
var ocm = KnownTileSources.Create(KnownTileSource.OpenCycleMap);
// Stamen Terrain
var stamenTerrain = new HttpTileSource(
new GlobalSphericalMercator(0, 18),
"https://stamen-tiles.a.ssl.fastly.net/terrain/{z}/{x}/{y}.jpg",
null,
"Stamen Terrain");
// ESRI World Imagery
var esriImagery = new HttpTileSource(
new GlobalSphericalMercator(0, 19),
"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
null,
"ESRI World Imagery");
// CartoDB Positron
var cartoLight = new HttpTileSource(
new GlobalSphericalMercator(0, 19),
"https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",
new[] { "a", "b", "c", "d" },
"CartoDB Positron");
9.2.4 天地图集成
// 天地图需要申请 key: https://console.tianditu.gov.cn/
string tiandituKey = "YOUR_API_KEY";
// 天地图矢量底图
var tiandituVec = new HttpTileSource(
new GlobalSphericalMercator(1, 18),
$"https://t{{s}}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={{z}}&TILEROW={{y}}&TILECOL={{x}}&tk={tiandituKey}",
new[] { "0", "1", "2", "3", "4", "5", "6", "7" },
"天地图矢量");
// 天地图矢量注记
var tiandituCva = new HttpTileSource(
new GlobalSphericalMercator(1, 18),
$"https://t{{s}}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={{z}}&TILEROW={{y}}&TILECOL={{x}}&tk={tiandituKey}",
new[] { "0", "1", "2", "3", "4", "5", "6", "7" },
"天地图注记");
// 天地图影像底图
var tiandituImg = new HttpTileSource(
new GlobalSphericalMercator(1, 18),
$"https://t{{s}}.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={{z}}&TILEROW={{y}}&TILECOL={{x}}&tk={tiandituKey}",
new[] { "0", "1", "2", "3", "4", "5", "6", "7" },
"天地图影像");
// 添加图层
map.BackgroundLayer.Add(new TileLayer(tiandituVec, "天地图矢量"));
map.BackgroundLayer.Add(new TileLayer(tiandituCva, "天地图注记"));
9.3 瓦片缓存
9.3.1 文件缓存
using BruTile.Cache;
// 创建文件缓存
var fileCache = new FileCache(@"C:\TileCache\OSM", "png");
// 创建带缓存的瓦片源
var osmSource = new HttpTileSource(
new GlobalSphericalMercator(),
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
new[] { "a", "b", "c" },
"OSM",
fileCache);
var cachedLayer = new TileLayer(osmSource, "Cached OSM");
9.3.2 内存缓存
// 创建内存缓存
var memoryCache = new MemoryCache<byte[]>(100, 500); // 最小100,最大500
// 使用内存缓存
var tileSource = new HttpTileSource(
new GlobalSphericalMercator(),
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
new[] { "a", "b", "c" },
"OSM",
memoryCache);
9.3.3 自定义缓存
public class DatabaseTileCache : ITileCache<byte[]>
{
private readonly string _connectionString;
public DatabaseTileCache(string connectionString)
{
_connectionString = connectionString;
}
public void Add(TileIndex index, byte[] tile)
{
using (var conn = new SqlConnection(_connectionString))
{
conn.Open();
var cmd = new SqlCommand(
"INSERT INTO TileCache (Z, X, Y, Tile) VALUES (@z, @x, @y, @tile)", conn);
cmd.Parameters.AddWithValue("@z", index.Level);
cmd.Parameters.AddWithValue("@x", index.Col);
cmd.Parameters.AddWithValue("@y", index.Row);
cmd.Parameters.AddWithValue("@tile", tile);
cmd.ExecuteNonQuery();
}
}
public byte[] Find(TileIndex index)
{
using (var conn = new SqlConnection(_connectionString))
{
conn.Open();
var cmd = new SqlCommand(
"SELECT Tile FROM TileCache WHERE Z = @z AND X = @x AND Y = @y", conn);
cmd.Parameters.AddWithValue("@z", index.Level);
cmd.Parameters.AddWithValue("@x", index.Col);
cmd.Parameters.AddWithValue("@y", index.Row);
return cmd.ExecuteScalar() as byte[];
}
}
public void Remove(TileIndex index)
{
using (var conn = new SqlConnection(_connectionString))
{
conn.Open();
var cmd = new SqlCommand(
"DELETE FROM TileCache WHERE Z = @z AND X = @x AND Y = @y", conn);
cmd.Parameters.AddWithValue("@z", index.Level);
cmd.Parameters.AddWithValue("@x", index.Col);
cmd.Parameters.AddWithValue("@y", index.Row);
cmd.ExecuteNonQuery();
}
}
}
9.4 WMS 图层
9.4.1 基本使用
using SharpMap.Layers;
using SharpMap.Web.Wms;
// 创建 WMS 图层
var wmsLayer = new WmsLayer("GeoServer WMS", "http://localhost:8080/geoserver/wms");
// 添加图层
wmsLayer.AddLayer("topp:states");
wmsLayer.AddLayer("topp:roads");
// 设置格式
wmsLayer.SetImageFormat("image/png");
// 设置透明
wmsLayer.Transparent = true;
// 设置坐标系
wmsLayer.SRID = 4326;
// 添加到地图
map.Layers.Add(wmsLayer);
9.4.2 WMS 客户端
// 获取 WMS 能力文档
var client = new Client("http://localhost:8080/geoserver/wms");
// 获取服务信息
Console.WriteLine($"服务标题: {client.ServiceDescription.Title}");
Console.WriteLine($"服务摘要: {client.ServiceDescription.Abstract}");
// 获取可用图层
foreach (var layer in client.Layers)
{
Console.WriteLine($"图层: {layer.Name}");
Console.WriteLine($"标题: {layer.Title}");
Console.WriteLine($"可查询: {layer.Queryable}");
if (layer.LatLonBoundingBox != null)
{
Console.WriteLine($"范围: {layer.LatLonBoundingBox}");
}
}
// 获取支持的格式
Console.WriteLine("支持的格式:");
foreach (var format in client.GetMapFormats)
{
Console.WriteLine($" {format}");
}
// 获取支持的坐标系
Console.WriteLine("支持的坐标系:");
foreach (var crs in client.CRS)
{
Console.WriteLine($" {crs}");
}
9.4.3 WMS 查询(GetFeatureInfo)
public class WmsFeatureInfo
{
public static string GetFeatureInfo(string wmsUrl, string layer,
Coordinate point, int width, int height, Envelope bbox)
{
// 计算点击位置的像素坐标
int pixelX = (int)((point.X - bbox.MinX) / bbox.Width * width);
int pixelY = (int)((bbox.MaxY - point.Y) / bbox.Height * height);
// 构建 GetFeatureInfo 请求
var url = $"{wmsUrl}?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetFeatureInfo" +
$"&LAYERS={layer}&QUERY_LAYERS={layer}" +
$"&INFO_FORMAT=application/json" +
$"&SRS=EPSG:4326" +
$"&BBOX={bbox.MinX},{bbox.MinY},{bbox.MaxX},{bbox.MaxY}" +
$"&WIDTH={width}&HEIGHT={height}" +
$"&X={pixelX}&Y={pixelY}";
using (var client = new WebClient())
{
return client.DownloadString(url);
}
}
}
9.5 WMTS 图层
9.5.1 使用 BruTile 访问 WMTS
using BruTile.Wmts;
// 获取 WMTS 能力文档
var wmtsUrl = "https://example.com/wmts?SERVICE=WMTS&REQUEST=GetCapabilities";
var stream = new MemoryStream(new WebClient().DownloadData(wmtsUrl));
var tileSources = WmtsParser.Parse(stream);
// 选择图层
var tileSource = tileSources.FirstOrDefault(ts => ts.Name == "layer_name");
if (tileSource != null)
{
var wmtsLayer = new TileLayer(tileSource, "WMTS Layer");
map.BackgroundLayer.Add(wmtsLayer);
}
9.5.2 天地图 WMTS
// 天地图 WMTS 已在 9.2.4 节介绍
// 天地图使用 WMTS 协议,通过 HttpTileSource 直接访问
string key = "YOUR_KEY";
// WMTS 风格的 URL
var wmtsUrl = $"https://t{{s}}.tianditu.gov.cn/vec_w/wmts?" +
"SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&" +
$"TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={{z}}&TILEROW={{y}}&TILECOL={{x}}&tk={key}";
var tileSource = new HttpTileSource(
new GlobalSphericalMercator(1, 18),
wmtsUrl,
new[] { "0", "1", "2", "3", "4", "5", "6", "7" },
"天地图");
9.6 异步瓦片加载
9.6.1 TileAsyncLayer
using SharpMap.Layers;
// 创建异步瓦片图层
var asyncLayer = new TileAsyncLayer(osmTileSource, "Async OSM");
// 配置异步选项
asyncLayer.OnlyRedrawWhenComplete = false; // 瓦片加载时逐步显示
// 添加到地图
map.BackgroundLayer.Add(asyncLayer);
9.6.2 自定义异步加载
public class CustomAsyncTileLayer : TileLayer
{
private readonly ConcurrentDictionary<TileIndex, byte[]> _pendingTiles;
private readonly SemaphoreSlim _semaphore;
public CustomAsyncTileLayer(ITileSource tileSource, string layerName)
: base(tileSource, layerName)
{
_pendingTiles = new ConcurrentDictionary<TileIndex, byte[]>();
_semaphore = new SemaphoreSlim(4); // 最多 4 个并发请求
}
public async Task LoadTilesAsync(IEnumerable<TileIndex> indices)
{
var tasks = indices.Select(async index =>
{
await _semaphore.WaitAsync();
try
{
var tile = await Task.Run(() => TileSource.GetTile(new TileInfo { Index = index }));
_pendingTiles[index] = tile;
}
finally
{
_semaphore.Release();
}
});
await Task.WhenAll(tasks);
}
}
9.7 离线瓦片
9.7.1 MBTiles 支持
using BruTile.MbTiles;
// 打开 MBTiles 文件
var mbtilesPath = @"C:\Data\map.mbtiles";
var mbTilesTileSource = new MbTilesTileSource(mbtilesPath);
var offlineLayer = new TileLayer(mbTilesTileSource, "Offline Map");
map.BackgroundLayer.Add(offlineLayer);
9.7.2 预下载瓦片
public class TileDownloader
{
public async Task DownloadTilesAsync(ITileSource tileSource, string cachePath,
Envelope extent, int minZoom, int maxZoom)
{
var fileCache = new FileCache(cachePath, "png");
for (int zoom = minZoom; zoom <= maxZoom; zoom++)
{
var tileRange = TileTransform.WorldToTile(extent, zoom);
for (int x = tileRange.MinCol; x <= tileRange.MaxCol; x++)
{
for (int y = tileRange.MinRow; y <= tileRange.MaxRow; y++)
{
var index = new TileIndex(x, y, zoom);
try
{
var tile = tileSource.GetTile(new TileInfo { Index = index });
if (tile != null)
{
fileCache.Add(index, tile);
Console.WriteLine($"Downloaded: z={zoom}, x={x}, y={y}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Failed: z={zoom}, x={x}, y={y} - {ex.Message}");
}
await Task.Delay(100); // 避免请求过快
}
}
}
}
}
9.8 坐标系处理
9.8.1 瓦片图层与矢量图层混合
// 瓦片图层使用 Web Mercator (EPSG:3857)
var tileLayer = new TileLayer(osmTileSource, "OSM");
map.BackgroundLayer.Add(tileLayer);
// 矢量数据使用 WGS84 (EPSG:4326)
var vectorLayer = new VectorLayer("Data");
vectorLayer.DataSource = new ShapeFile("data.shp"); // WGS84 数据
// 设置坐标转换
var ctFactory = new CoordinateTransformationFactory();
var csFactory = new CoordinateSystemFactory();
var wgs84 = GeographicCoordinateSystem.WGS84;
var webMercator = csFactory.CreateFromWkt(webMercatorWkt);
var transform = ctFactory.CreateFromCoordinateSystems(wgs84, webMercator);
vectorLayer.CoordinateTransformation = transform;
vectorLayer.ReverseCoordinateTransformation =
ctFactory.CreateFromCoordinateSystems(webMercator, wgs84);
map.Layers.Add(vectorLayer);
// 设置地图坐标系
map.SRID = 3857;
9.9 本章小结
本章详细介绍了 SharpMap 的瓦片图层和在线地图集成:
- 瓦片概述:了解了瓦片地图的结构和坐标系
- BruTile 集成:掌握了使用 BruTile 创建瓦片图层
- 常用瓦片源:学习了 OSM、天地图等瓦片源的配置
- 瓦片缓存:了解了文件缓存和内存缓存的使用
- WMS 图层:掌握了 WMS 服务的集成方法
- 异步加载:学习了异步瓦片加载的实现
- 离线瓦片:了解了 MBTiles 和瓦片预下载
9.10 参考资源
下一章预告:第10章将详细介绍坐标系统与投影转换,包括 ProjNet 的使用和常见坐标系的配置。

浙公网安备 33010602011771号