第10章 - 投影与坐标系
第10章:投影与坐标系
10.1 坐标系基础
10.1.1 地理坐标系与投影坐标系
┌─────────────────────────────────────────────────────────────────┐
│ 坐标系统类型 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 地理坐标系 (Geographic CRS) 投影坐标系 (Projected CRS) │
│ ──────────────────────── ──────────────────────── │
│ • 使用经度和纬度 • 使用 X 和 Y 坐标 │
│ • 单位:度 • 单位:米 │
│ • 例如:WGS84 (EPSG:4326) • 例如:Web Mercator │
│ (EPSG:3857) │
│ │
│ 90° ┌─────────────┐ │
│ │ │ Y │ │
│ ────┼──── 经度 │ ↑ │ │
│ │ (X: -180 到 180) │ │ │ │
│ -90° │ └──→ X │ │
│ 纬度 └─────────────┘ │
│ (Y: -90 到 90) 平面坐标 │
│ │
└─────────────────────────────────────────────────────────────────┘
10.1.2 常用坐标系
| EPSG 代码 | 名称 | 描述 |
|---|---|---|
| EPSG:4326 | WGS84 | GPS 使用的地理坐标系 |
| EPSG:3857 | Web Mercator | Google/OSM 使用的投影坐标系 |
| EPSG:4490 | CGCS2000 | 中国国家大地坐标系 |
| EPSG:4214 | Beijing 1954 | 北京54坐标系 |
| EPSG:4610 | Xian 1980 | 西安80坐标系 |
10.1.3 Mapsui 中的坐标系
// Mapsui 默认使用 Web Mercator (EPSG:3857)
var map = new Map
{
CRS = "EPSG:3857" // 默认值
};
// 数据提供者的坐标系
var provider = new MyProvider
{
CRS = "EPSG:4326" // 数据源是 WGS84
};
10.2 SphericalMercator 投影
10.2.1 经纬度与 Web Mercator 转换
using Mapsui.Projections;
// 经纬度 → Web Mercator
double lon = 116.4; // 北京经度
double lat = 39.9; // 北京纬度
var mercator = SphericalMercator.FromLonLat(lon, lat);
// mercator.X ≈ 12958173
// mercator.Y ≈ 4852832
// Web Mercator → 经纬度
var lonLat = SphericalMercator.ToLonLat(mercator.X, mercator.Y);
// lonLat.X ≈ 116.4
// lonLat.Y ≈ 39.9
10.2.2 坐标转换辅助类
public static class CoordinateConverter
{
/// <summary>
/// 经纬度转 Web Mercator
/// </summary>
public static MPoint LonLatToMercator(double lon, double lat)
{
var (x, y) = SphericalMercator.FromLonLat(lon, lat);
return new MPoint(x, y);
}
/// <summary>
/// Web Mercator 转经纬度
/// </summary>
public static (double Lon, double Lat) MercatorToLonLat(double x, double y)
{
var (lon, lat) = SphericalMercator.ToLonLat(x, y);
return (lon, lat);
}
/// <summary>
/// MPoint 转经纬度
/// </summary>
public static (double Lon, double Lat) MercatorToLonLat(MPoint point)
{
return MercatorToLonLat(point.X, point.Y);
}
/// <summary>
/// 范围转换:经纬度 → Web Mercator
/// </summary>
public static MRect LonLatExtentToMercator(double minLon, double minLat,
double maxLon, double maxLat)
{
var min = SphericalMercator.FromLonLat(minLon, minLat);
var max = SphericalMercator.FromLonLat(maxLon, maxLat);
return new MRect(min.X, min.Y, max.X, max.Y);
}
}
10.2.3 创建不同坐标的要素
// 使用经纬度坐标创建要素(自动转换为 Web Mercator)
public static PointFeature CreateFeatureFromLonLat(double lon, double lat,
string name)
{
var point = CoordinateConverter.LonLatToMercator(lon, lat);
var feature = new PointFeature(point);
feature["name"] = name;
feature["longitude"] = lon;
feature["latitude"] = lat;
return feature;
}
// 使用示例
var beijing = CreateFeatureFromLonLat(116.4, 39.9, "北京");
var shanghai = CreateFeatureFromLonLat(121.5, 31.2, "上海");
10.3 ProjectingProvider
10.3.1 使用 ProjectingProvider
当数据源坐标系与地图坐标系不同时,使用 ProjectingProvider 进行自动转换:
// 假设数据源使用 WGS84 (EPSG:4326)
var wgs84Provider = new MyWgs84Provider();
wgs84Provider.CRS = "EPSG:4326";
// 地图使用 Web Mercator (EPSG:3857)
var map = new Map { CRS = "EPSG:3857" };
// 使用 ProjectingProvider 包装
var projectingProvider = new ProjectingProvider(wgs84Provider)
{
CRS = "EPSG:3857" // 目标坐标系
};
var layer = new Layer
{
Name = "ProjectedLayer",
DataSource = projectingProvider
};
map.Layers.Add(layer);
10.3.2 自定义投影转换
Mapsui 默认只支持 WGS84 和 Web Mercator 之间的转换。对于其他坐标系,需要使用 ProjNet 库:
using ProjNet.CoordinateSystems;
using ProjNet.CoordinateSystems.Transformations;
public class CustomProjection : IProjection
{
private readonly ICoordinateTransformation _transform;
public CustomProjection(string fromCrs, string toCrs)
{
var csFactory = new CoordinateSystemFactory();
var ctFactory = new CoordinateTransformationFactory();
var fromCs = GetCoordinateSystem(fromCrs);
var toCs = GetCoordinateSystem(toCrs);
_transform = ctFactory.CreateFromCoordinateSystems(fromCs, toCs);
}
public MPoint Project(string fromCrs, string toCrs, MPoint point)
{
var result = _transform.MathTransform.Transform(
new double[] { point.X, point.Y }
);
return new MPoint(result[0], result[1]);
}
private ICoordinateSystem GetCoordinateSystem(string crs)
{
// 根据 CRS 标识符返回坐标系统定义
// 可以使用 WKT 字符串或 EPSG 代码
return crs switch
{
"EPSG:4326" => GeographicCoordinateSystem.WGS84,
"EPSG:3857" => ProjectedCoordinateSystem.WebMercator,
// 添加更多坐标系...
_ => throw new NotSupportedException($"Unsupported CRS: {crs}")
};
}
}
10.4 缩放级别与分辨率
10.4.1 分辨率概念
分辨率(Resolution)表示每个像素代表的地图单位(米):
// 常见缩放级别对应的分辨率(Web Mercator)
public static class ZoomLevelResolutions
{
// 地球周长约 40075016.68 米
private const double EarthCircumference = 40075016.68;
// 标准瓦片大小
private const int TileSize = 256;
/// <summary>
/// 获取指定缩放级别的分辨率
/// </summary>
public static double GetResolution(int zoomLevel)
{
return EarthCircumference / (TileSize * Math.Pow(2, zoomLevel));
}
/// <summary>
/// 根据分辨率获取缩放级别
/// </summary>
public static int GetZoomLevel(double resolution)
{
return (int)Math.Round(
Math.Log(EarthCircumference / (TileSize * resolution)) / Math.Log(2)
);
}
// 预计算的分辨率表
public static readonly double[] Resolutions = new double[]
{
156543.03392800014, // 0 - 世界
78271.51696400007, // 1
39135.75848200003, // 2
19567.87924100002, // 3
9783.93962050001, // 4
4891.96981025, // 5
2445.98490513, // 6
1222.99245256, // 7
611.496226281, // 8
305.748113141, // 9
152.87405657, // 10
76.4370282852, // 11
38.2185141426, // 12
19.1092570713, // 13
9.55462853565, // 14
4.77731426782, // 15
2.38865713391, // 16
1.19432856696, // 17
0.597164283478, // 18 - 街道
0.298582141739 // 19 - 建筑
};
}
10.4.2 控制缩放级别
// 设置缩放限制
map.Navigator.ZoomLimits = new MMinMax(
ZoomLevelResolutions.GetResolution(18), // 最大缩放(最小分辨率)
ZoomLevelResolutions.GetResolution(3) // 最小缩放(最大分辨率)
);
// 缩放到指定级别
public void ZoomToLevel(Map map, int zoomLevel)
{
var resolution = ZoomLevelResolutions.GetResolution(zoomLevel);
map.Navigator.ZoomTo(resolution);
}
// 获取当前缩放级别
public int GetCurrentZoomLevel(Map map)
{
var resolution = map.Navigator.Viewport.Resolution;
return ZoomLevelResolutions.GetZoomLevel(resolution);
}
10.5 范围与视口
10.5.1 MRect - 矩形范围
// 创建矩形范围
var extent = new MRect(minX, minY, maxX, maxY);
// 从经纬度创建范围
public static MRect CreateExtentFromLonLat(double minLon, double minLat,
double maxLon, double maxLat)
{
var min = SphericalMercator.FromLonLat(minLon, minLat);
var max = SphericalMercator.FromLonLat(maxLon, maxLat);
return new MRect(min.X, min.Y, max.X, max.Y);
}
// 中国范围
var chinaExtent = CreateExtentFromLonLat(73.5, 18.2, 135.0, 53.5);
10.5.2 视口定位
// 定位到指定范围
map.Navigator.ZoomToBox(extent);
// 定位到指定点和缩放级别
var center = CoordinateConverter.LonLatToMercator(116.4, 39.9);
var resolution = ZoomLevelResolutions.GetResolution(12);
map.Navigator.CenterOnAndZoomTo(center, resolution);
// 带动画的定位
map.Navigator.FlyTo(extent, duration: 1000);
// 获取当前视口范围
var currentExtent = map.Navigator.Viewport.Extent;
10.5.3 视口坐标转换
public class ViewportHelper
{
private readonly Viewport _viewport;
public ViewportHelper(Viewport viewport)
{
_viewport = viewport;
}
/// <summary>
/// 屏幕坐标转地图坐标(Web Mercator)
/// </summary>
public MPoint ScreenToWorld(double screenX, double screenY)
{
return _viewport.ScreenToWorld(new MPoint(screenX, screenY));
}
/// <summary>
/// 屏幕坐标转经纬度
/// </summary>
public (double Lon, double Lat) ScreenToLonLat(double screenX, double screenY)
{
var world = ScreenToWorld(screenX, screenY);
return CoordinateConverter.MercatorToLonLat(world);
}
/// <summary>
/// 地图坐标转屏幕坐标
/// </summary>
public MPoint WorldToScreen(MPoint worldPoint)
{
return _viewport.WorldToScreen(worldPoint);
}
/// <summary>
/// 经纬度转屏幕坐标
/// </summary>
public MPoint LonLatToScreen(double lon, double lat)
{
var world = CoordinateConverter.LonLatToMercator(lon, lat);
return WorldToScreen(world);
}
}
10.6 地图比例尺
10.6.1 计算比例尺
public static class ScaleCalculator
{
/// <summary>
/// 计算当前地图比例尺
/// </summary>
/// <param name="resolution">分辨率(米/像素)</param>
/// <param name="dpi">屏幕 DPI,默认 96</param>
/// <returns>比例尺分母(1:X 中的 X)</returns>
public static double CalculateScale(double resolution, double dpi = 96)
{
// 1 英寸 = 0.0254 米
const double InchToMeter = 0.0254;
// 每英寸的像素数 = DPI
// 每米的像素数 = DPI / 0.0254
// 比例尺 = 分辨率 * 每米的像素数
return resolution * dpi / InchToMeter;
}
/// <summary>
/// 格式化比例尺显示
/// </summary>
public static string FormatScale(double scaleDenominator)
{
if (scaleDenominator >= 1000000)
return $"1:{scaleDenominator / 1000000:F0}M";
if (scaleDenominator >= 1000)
return $"1:{scaleDenominator / 1000:F0}K";
return $"1:{scaleDenominator:F0}";
}
}
10.6.2 缩放到指定比例尺
public void ZoomToScale(Map map, double scaleDenominator, double dpi = 96)
{
const double InchToMeter = 0.0254;
var resolution = scaleDenominator * InchToMeter / dpi;
map.Navigator.ZoomTo(resolution);
}
// 缩放到 1:10000
ZoomToScale(map, 10000);
10.7 距离计算
10.7.1 Haversine 公式
public static class DistanceCalculator
{
private const double EarthRadius = 6371000; // 地球半径(米)
/// <summary>
/// 使用 Haversine 公式计算两点间的球面距离
/// </summary>
public static double HaversineDistance(
double lat1, double lon1,
double lat2, double lon2)
{
var dLat = ToRadians(lat2 - lat1);
var dLon = ToRadians(lon2 - lon1);
var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
Math.Cos(ToRadians(lat1)) * Math.Cos(ToRadians(lat2)) *
Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
return EarthRadius * c;
}
/// <summary>
/// 计算 Web Mercator 坐标点间的距离
/// </summary>
public static double DistanceBetweenMercatorPoints(MPoint p1, MPoint p2)
{
var (lon1, lat1) = SphericalMercator.ToLonLat(p1.X, p1.Y);
var (lon2, lat2) = SphericalMercator.ToLonLat(p2.X, p2.Y);
return HaversineDistance(lat1, lon1, lat2, lon2);
}
private static double ToRadians(double degrees)
{
return degrees * Math.PI / 180;
}
}
10.7.2 面积计算
public static class AreaCalculator
{
/// <summary>
/// 计算多边形面积(使用球面计算)
/// </summary>
public static double CalculateSphericalArea(IEnumerable<MPoint> mercatorPoints)
{
// 转换为经纬度
var lonLatPoints = mercatorPoints
.Select(p => SphericalMercator.ToLonLat(p.X, p.Y))
.ToList();
return CalculateSphericalAreaFromLonLat(lonLatPoints);
}
/// <summary>
/// 使用球面多边形面积公式
/// </summary>
private static double CalculateSphericalAreaFromLonLat(
List<(double X, double Y)> points)
{
const double EarthRadius = 6371000;
double area = 0;
int n = points.Count;
for (int i = 0; i < n; i++)
{
int j = (i + 1) % n;
var lon1 = ToRadians(points[i].X);
var lat1 = ToRadians(points[i].Y);
var lon2 = ToRadians(points[j].X);
var lat2 = ToRadians(points[j].Y);
area += (lon2 - lon1) * (2 + Math.Sin(lat1) + Math.Sin(lat2));
}
return Math.Abs(area * EarthRadius * EarthRadius / 2);
}
private static double ToRadians(double degrees)
{
return degrees * Math.PI / 180;
}
}
10.8 本章小结
本章详细介绍了 Mapsui 中的投影与坐标系:
- 坐标系基础:地理坐标系与投影坐标系的区别
- SphericalMercator:经纬度与 Web Mercator 之间的转换
- ProjectingProvider:自动坐标系转换
- 缩放级别与分辨率:分辨率与缩放级别的关系
- 范围与视口:MRect 和视口坐标转换
- 地图比例尺:计算和显示比例尺
- 距离与面积计算:使用球面公式计算距离和面积
在下一章中,我们将学习瓦片图层与地图服务的使用。
10.9 思考与练习
- 实现一个支持 CGCS2000 到 Web Mercator 投影转换的 Provider。
- 创建一个坐标拾取工具,显示点击位置的经纬度和 Web Mercator 坐标。
- 实现一个动态比例尺显示控件。
- 创建一个多边形面积测量工具,显示球面面积。
- 实现根据当前视口范围计算显示范围内所有要素的功能。

浙公网安备 33010602011771号