第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 的瓦片图层和在线地图集成:

  1. 瓦片概述:了解了瓦片地图的结构和坐标系
  2. BruTile 集成:掌握了使用 BruTile 创建瓦片图层
  3. 常用瓦片源:学习了 OSM、天地图等瓦片源的配置
  4. 瓦片缓存:了解了文件缓存和内存缓存的使用
  5. WMS 图层:掌握了 WMS 服务的集成方法
  6. 异步加载:学习了异步瓦片加载的实现
  7. 离线瓦片:了解了 MBTiles 和瓦片预下载

9.10 参考资源


下一章预告:第10章将详细介绍坐标系统与投影转换,包括 ProjNet 的使用和常见坐标系的配置。

posted @ 2026-01-08 14:09  我才是银古  阅读(36)  评论(0)    收藏  举报