第06章 - 数据提供者与数据源
第06章:数据提供者与数据源
6.1 数据提供者概述
6.1.1 IProvider 接口
数据提供者(Provider)是 Mapsui 获取地理数据的核心组件:
public interface IProvider
{
/// <summary>
/// 坐标参考系统标识符
/// </summary>
string? CRS { get; set; }
/// <summary>
/// 数据范围
/// </summary>
MRect? Extent { get; }
/// <summary>
/// 获取指定范围内的要素
/// </summary>
IEnumerable<IFeature> GetFeatures(FetchInfo fetchInfo);
}
6.1.2 数据提供者类型
┌─────────────────────────────────────────────────────────────────┐
│ IProvider 实现 │
├─────────────────────────────────────────────────────────────────┤
│ 本地数据 │ 网络服务 │ 内存数据 │
│ ───────── │ ───────── │ ───────── │
│ ShapeFileProvider│ WmsProvider │ MemoryProvider │
│ GeoJsonProvider │ WfsProvider │ GeometryProvider │
│ MbTilesProvider │ ArcGisProvider │ ProjectingProvider │
└─────────────────────────────────────────────────────────────────┘
6.2 内置数据提供者
6.2.1 MemoryProvider - 内存数据提供者
// 创建要素集合
var features = new List<IFeature>
{
CreatePointFeature(116.4, 39.9, "北京"),
CreatePointFeature(121.5, 31.2, "上海"),
CreatePointFeature(113.3, 23.1, "广州")
};
// 创建 MemoryProvider
var memoryProvider = new MemoryProvider(features)
{
CRS = "EPSG:3857"
};
// 用于 Layer
var layer = new Layer
{
Name = "Cities",
DataSource = memoryProvider,
Style = CreatePointStyle()
};
// 辅助方法
private PointFeature CreatePointFeature(double lon, double lat, string name)
{
var point = SphericalMercator.FromLonLat(lon, lat).ToMPoint();
var feature = new PointFeature(point);
feature["name"] = name;
return feature;
}
6.2.2 ProjectingProvider - 投影转换提供者
当数据源的坐标系与地图坐标系不同时,使用 ProjectingProvider 进行转换:
// 假设有一个 WGS84 坐标系的数据提供者
var wgs84Provider = new MyWgs84Provider();
wgs84Provider.CRS = "EPSG:4326";
// 包装为投影转换提供者
var projectingProvider = new ProjectingProvider(wgs84Provider)
{
CRS = "EPSG:3857" // 目标坐标系
};
// 使用投影提供者创建图层
var layer = new Layer
{
Name = "ProjectedData",
DataSource = projectingProvider,
Style = CreateStyle()
};
// 设置地图坐标系
map.CRS = "EPSG:3857";
6.2.3 GeometryProvider - 几何提供者
// 使用 NTS 几何创建提供者
var geometryFactory = new GeometryFactory();
var geometries = new[]
{
geometryFactory.CreatePoint(new Coordinate(116.4, 39.9)),
geometryFactory.CreatePoint(new Coordinate(121.5, 31.2)),
geometryFactory.CreateLineString(new[]
{
new Coordinate(116.4, 39.9),
new Coordinate(121.5, 31.2)
})
};
var geometryProvider = new GeometryProvider(geometries);
var layer = new Layer
{
DataSource = geometryProvider,
Style = new VectorStyle
{
Fill = new Brush(Color.Red),
Line = new Pen(Color.Blue, 2)
}
};
6.3 文件数据源
6.3.1 GeoJSON 数据源
使用 Mapsui.Extensions 包读取 GeoJSON:
using Mapsui.Extensions.GeoJson;
// 从文件读取 GeoJSON
var geoJsonPath = "data/cities.geojson";
var features = GeoJsonReader.ReadFeatures(File.ReadAllText(geoJsonPath));
var layer = new MemoryLayer
{
Name = "GeoJsonData",
Features = features,
Style = CreateStyle()
};
// 从 URL 读取 GeoJSON
public async Task<MemoryLayer> LoadGeoJsonFromUrlAsync(string url)
{
using var client = new HttpClient();
var json = await client.GetStringAsync(url);
var features = GeoJsonReader.ReadFeatures(json);
return new MemoryLayer
{
Name = "RemoteGeoJson",
Features = features
};
}
6.3.2 Shapefile 数据源
使用 Mapsui.Nts 包读取 Shapefile:
using Mapsui.Providers.Shapefile;
// 读取 Shapefile
var shapefilePath = "data/countries.shp";
var shapeFileProvider = new ShapeFile(shapefilePath)
{
CRS = "EPSG:4326"
};
// 如果地图使用 Web Mercator,需要投影转换
var projectingProvider = new ProjectingProvider(shapeFileProvider)
{
CRS = "EPSG:3857"
};
var layer = new Layer
{
Name = "Countries",
DataSource = projectingProvider,
Style = new VectorStyle
{
Fill = new Brush(new Color(200, 200, 200)),
Outline = new Pen(Color.Gray, 1)
}
};
6.4 网络服务数据源
6.4.1 WMS 服务
using Mapsui.Providers.Wms;
// 创建 WMS 提供者
var wmsUrl = "https://example.com/wms";
var wmsProvider = new WmsProvider(new Uri(wmsUrl))
{
LayerNames = new[] { "roads", "buildings" },
CRS = "EPSG:3857"
};
// 创建 WMS 图层
var wmsLayer = new ImageLayer
{
Name = "WMS Layer",
DataSource = wmsProvider
};
// 配置 WMS 参数
public WmsProvider CreateWmsProvider(string url, string[] layers)
{
var provider = new WmsProvider(new Uri(url));
provider.LayerNames = layers;
provider.CRS = "EPSG:3857";
provider.Version = "1.3.0"; // WMS 版本
provider.ImageFormat = "image/png"; // 图像格式
return provider;
}
6.4.2 WMTS 服务
using BruTile.Wmts;
// 创建 WMTS 瓦片源
var wmtsUrl = "https://example.com/wmts/1.0.0/WMTSCapabilities.xml";
var wmtsTileSource = new WmtsTileSource(
new Uri(wmtsUrl),
"layerIdentifier",
new MemoryCache<byte[]>()
);
// 创建 WMTS 图层
var wmtsLayer = new TileLayer(wmtsTileSource)
{
Name = "WMTS Layer"
};
6.4.3 ArcGIS 服务
使用 Mapsui.ArcGIS 包:
using Mapsui.ArcGIS;
// ArcGIS 动态地图服务
var arcGisUrl = "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer";
var arcGisProvider = new ArcGISDynamicProvider(new Uri(arcGisUrl));
var arcGisLayer = new ImageLayer
{
Name = "ArcGIS Imagery",
DataSource = arcGisProvider
};
// ArcGIS 瓦片服务
var arcGisTileUrl = "https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer";
var arcGisTileLayer = new TileLayer(
CreateArcGisTileSource(arcGisTileUrl)
);
6.5 自定义数据提供者
6.5.1 实现 IProvider
public class SqlServerProvider : IProvider
{
private readonly string _connectionString;
private readonly string _tableName;
private readonly string _geometryColumn;
private MRect? _extent;
public string? CRS { get; set; } = "EPSG:4326";
public MRect? Extent
{
get => _extent ??= CalculateExtent();
}
public SqlServerProvider(string connectionString, string tableName,
string geometryColumn = "geom")
{
_connectionString = connectionString;
_tableName = tableName;
_geometryColumn = geometryColumn;
}
public IEnumerable<IFeature> GetFeatures(FetchInfo fetchInfo)
{
var features = new List<IFeature>();
var extent = fetchInfo.Extent;
using var connection = new SqlConnection(_connectionString);
connection.Open();
var sql = $@"
SELECT *, {_geometryColumn}.STAsBinary() as WKB
FROM {_tableName}
WHERE {_geometryColumn}.STIntersects(
geometry::STGeomFromText(
'POLYGON(({extent.MinX} {extent.MinY},
{extent.MaxX} {extent.MinY},
{extent.MaxX} {extent.MaxY},
{extent.MinX} {extent.MaxY},
{extent.MinX} {extent.MinY}))',
4326)
) = 1";
using var cmd = new SqlCommand(sql, connection);
using var reader = cmd.ExecuteReader();
var wkbReader = new WKBReader();
while (reader.Read())
{
var wkb = (byte[])reader["WKB"];
var geometry = wkbReader.Read(wkb);
var feature = new GeometryFeature(geometry);
// 复制所有属性
for (int i = 0; i < reader.FieldCount; i++)
{
var columnName = reader.GetName(i);
if (columnName != "WKB" && columnName != _geometryColumn)
{
feature[columnName] = reader[i];
}
}
features.Add(feature);
}
return features;
}
private MRect CalculateExtent()
{
using var connection = new SqlConnection(_connectionString);
connection.Open();
var sql = $@"
SELECT
{_geometryColumn}.STEnvelope().STPointN(1).STX as MinX,
{_geometryColumn}.STEnvelope().STPointN(1).STY as MinY,
{_geometryColumn}.STEnvelope().STPointN(3).STX as MaxX,
{_geometryColumn}.STEnvelope().STPointN(3).STY as MaxY
FROM {_tableName}";
using var cmd = new SqlCommand(sql, connection);
using var reader = cmd.ExecuteReader();
double minX = double.MaxValue, minY = double.MaxValue;
double maxX = double.MinValue, maxY = double.MinValue;
while (reader.Read())
{
minX = Math.Min(minX, (double)reader["MinX"]);
minY = Math.Min(minY, (double)reader["MinY"]);
maxX = Math.Max(maxX, (double)reader["MaxX"]);
maxY = Math.Max(maxY, (double)reader["MaxY"]);
}
return new MRect(minX, minY, maxX, maxY);
}
}
6.5.2 带缓存的提供者
public class CachingProvider : IProvider
{
private readonly IProvider _innerProvider;
private readonly IMemoryCache _cache;
private readonly TimeSpan _cacheExpiration;
public string? CRS
{
get => _innerProvider.CRS;
set => _innerProvider.CRS = value;
}
public MRect? Extent => _innerProvider.Extent;
public CachingProvider(IProvider innerProvider,
IMemoryCache cache,
TimeSpan? cacheExpiration = null)
{
_innerProvider = innerProvider;
_cache = cache;
_cacheExpiration = cacheExpiration ?? TimeSpan.FromMinutes(5);
}
public IEnumerable<IFeature> GetFeatures(FetchInfo fetchInfo)
{
var cacheKey = CreateCacheKey(fetchInfo);
return _cache.GetOrCreate(cacheKey, entry =>
{
entry.AbsoluteExpirationRelativeToNow = _cacheExpiration;
return _innerProvider.GetFeatures(fetchInfo).ToList();
})!;
}
private string CreateCacheKey(FetchInfo fetchInfo)
{
var extent = fetchInfo.Extent;
return $"{_innerProvider.GetType().Name}_{extent.MinX}_{extent.MinY}_{extent.MaxX}_{extent.MaxY}_{fetchInfo.Resolution}";
}
}
6.6 瓦片数据源
6.6.1 常用瓦片源
// OpenStreetMap
var osmTileSource = new HttpTileSource(
new GlobalSphericalMercator(),
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
name: "OpenStreetMap"
);
// Stamen 瓦片
var stamenTerrain = new HttpTileSource(
new GlobalSphericalMercator(),
"http://tile.stamen.com/terrain/{z}/{x}/{y}.png",
name: "Stamen Terrain"
);
// 天地图
var tiandituVec = new HttpTileSource(
new GlobalSphericalMercator(),
"http://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=YOUR_TOKEN",
name: "天地图矢量"
);
6.6.2 MBTiles 离线瓦片
using BruTile.MbTiles;
// 创建 MBTiles 瓦片源
var mbtilesPath = "maps/offline.mbtiles";
var mbTilesTileSource = new MbTilesTileSource(
new SQLiteConnectionString(mbtilesPath, false)
);
// 创建离线瓦片图层
var offlineLayer = new TileLayer(mbTilesTileSource)
{
Name = "Offline Map"
};
// 检查 MBTiles 文件信息
public void PrintMbTilesInfo(string path)
{
using var connection = new SQLiteConnection(
new SQLiteConnectionString(path, false)
);
connection.Open();
var metadata = connection.Table<MbTilesMetadata>().ToList();
foreach (var item in metadata)
{
Console.WriteLine($"{item.Name}: {item.Value}");
}
}
6.7 数据源与坐标系
6.7.1 坐标系统匹配
public class CoordinateSystemHelper
{
/// <summary>
/// 确保数据提供者与地图坐标系统匹配
/// </summary>
public static IProvider EnsureMatchingCrs(IProvider provider, string mapCrs)
{
if (provider.CRS == mapCrs)
{
return provider;
}
// 如果坐标系不匹配,使用 ProjectingProvider 包装
return new ProjectingProvider(provider)
{
CRS = mapCrs
};
}
/// <summary>
/// 创建带自动投影的图层
/// </summary>
public static Layer CreateLayerWithProjection(
IProvider provider,
string providerCrs,
string mapCrs,
IStyle style)
{
provider.CRS = providerCrs;
var effectiveProvider = EnsureMatchingCrs(provider, mapCrs);
return new Layer
{
DataSource = effectiveProvider,
Style = style
};
}
}
6.7.2 常用坐标系
public static class CommonCrs
{
// WGS84 经纬度
public const string Wgs84 = "EPSG:4326";
// Web Mercator (Google, OSM 使用)
public const string WebMercator = "EPSG:3857";
// 中国2000坐标系
public const string Cgcs2000 = "EPSG:4490";
// 北京54坐标系
public const string Beijing54 = "EPSG:4214";
// 西安80坐标系
public const string Xian80 = "EPSG:4610";
}
6.8 数据加载策略
6.8.1 同步加载
// 简单的同步加载
var layer = new MemoryLayer
{
Features = LoadFeatures() // 同步加载
};
private IEnumerable<IFeature> LoadFeatures()
{
// 从数据库或文件加载
return features;
}
6.8.2 异步加载
public class AsyncDataLoader
{
private readonly Map _map;
private readonly MemoryLayer _dataLayer;
public AsyncDataLoader(Map map)
{
_map = map;
_dataLayer = new MemoryLayer { Name = "AsyncData" };
_map.Layers.Add(_dataLayer);
}
public async Task LoadDataAsync(string url)
{
try
{
// 显示加载指示
ShowLoading();
// 异步获取数据
var features = await Task.Run(() => FetchFeaturesFromUrl(url));
// 在 UI 线程更新
_dataLayer.Features = features;
_dataLayer.DataHasChanged();
}
catch (Exception ex)
{
ShowError(ex.Message);
}
finally
{
HideLoading();
}
}
private IEnumerable<IFeature> FetchFeaturesFromUrl(string url)
{
using var client = new HttpClient();
var json = client.GetStringAsync(url).Result;
return GeoJsonReader.ReadFeatures(json);
}
private void ShowLoading() { /* 实现加载指示 */ }
private void HideLoading() { /* 隐藏加载指示 */ }
private void ShowError(string message) { /* 显示错误信息 */ }
}
6.8.3 分页加载
public class PagedDataProvider : IProvider
{
private readonly Func<int, int, IEnumerable<IFeature>> _fetchPage;
private readonly int _pageSize;
public string? CRS { get; set; }
public MRect? Extent { get; private set; }
public PagedDataProvider(
Func<int, int, IEnumerable<IFeature>> fetchPage,
int pageSize = 100)
{
_fetchPage = fetchPage;
_pageSize = pageSize;
}
public IEnumerable<IFeature> GetFeatures(FetchInfo fetchInfo)
{
var allFeatures = new List<IFeature>();
int page = 0;
while (true)
{
var pageFeatures = _fetchPage(page, _pageSize).ToList();
if (pageFeatures.Count == 0)
break;
// 过滤在视口范围内的要素
var filteredFeatures = pageFeatures
.Where(f => IsInExtent(f, fetchInfo.Extent));
allFeatures.AddRange(filteredFeatures);
page++;
if (pageFeatures.Count < _pageSize)
break;
}
return allFeatures;
}
private bool IsInExtent(IFeature feature, MRect extent)
{
// 检查要素是否在范围内
return feature.Extent?.Intersects(extent) == true;
}
}
6.9 本章小结
本章详细介绍了 Mapsui 的数据提供者与数据源:
- IProvider 接口:数据提供者的核心抽象
- 内置数据提供者:MemoryProvider、ProjectingProvider、GeometryProvider
- 文件数据源:GeoJSON、Shapefile 文件读取
- 网络服务数据源:WMS、WMTS、ArcGIS 服务
- 自定义数据提供者:实现 IProvider 接口
- 瓦片数据源:在线瓦片和 MBTiles 离线瓦片
- 坐标系统处理:使用 ProjectingProvider 进行投影转换
- 数据加载策略:同步、异步和分页加载
在下一章中,我们将深入学习 Mapsui 的样式系统。
6.10 思考与练习
- 实现一个从 PostgreSQL/PostGIS 读取数据的自定义 Provider。
- 创建一个支持增量加载的数据提供者。
- 实现 WMS GetFeatureInfo 功能。
- 比较不同数据源的性能差异。
- 设计一个支持多数据源切换的图层。

浙公网安备 33010602011771号