项目地址: https://github.com/znlgis/geometry-api-net
官方文档: https://github.com/znlgis/geometry-api-net/wiki
NuGet:
OpenGIS.Esri.Geometry.Core(最新 1.0.9,截至 2026-10)许可证: LGPL-2.1
注意: 原 Esri 仓库(github.com/Esri/geometry-api-net)已停止维护(404);此分支由 znlgis 社区维护,包名与命名空间已改为
OpenGIS.Esri.Geometry。
概述
这是一个 .NET Standard 2.0 的空间几何计算库,提供 6 种几何类型(Point、MultiPoint、Line、Polyline、Polygon、Envelope)、35 个算子类(Operators 命名空间 27 个几何算子 + IO 命名空间 8 个读写算子,含 8 个 DE-9IM 谓词:Contains/Crosses/Disjoint/Equals/Intersects/Overlaps/Touches/Within)和 4 种序列化格式(WKT/EWKT、WKB/EWKB、GeoJSON、Esri JSON)。
- 核心包:
OpenGIS.Esri.Geometry.Core(GeometryEngine提供 39 个静态便捷方法) - JSON 扩展包:
OpenGIS.Esri.Geometry.Json(System.Text.Json 转换器) - 许可证:LGPL-2.1-only
- 能力边界:不提供
MakeValid,Simplify只做抽稀、不修复自相交环;需要几何修复请在上游(GEOS/PostGIS/JTS)完成后再导入。
快速开始
bashdotnet add package OpenGIS.Esri.Geometry.Core dotnet add package OpenGIS.Esri.Geometry.Json # 可选,System.Text.Json 支持
核心 API 与命名空间
| 命名空间 | 用途 |
|---|---|
OpenGIS.Esri.Geometry.Core | 核心入口,GeometryEngine 便捷 API |
OpenGIS.Esri.Geometry.Core.Geometries | 几何类型(Point, Polygon 等) |
OpenGIS.Esri.Geometry.Core.Operators | 空间运算算子 |
OpenGIS.Esri.Geometry.Core.SpatialReference | 空间参考 / 坐标系统 |
OpenGIS.Esri.Geometry.Core.IO | 序列化(WKT, WKB, GeoJSON, EsriJson) |
OpenGIS.Esri.Geometry.Json.Converters | System.Text.Json 转换器 |
几何类型体系与两种 API 风格的完整说明见 reference/geometry-types.md
序列化格式
WKT(Well-Known Text)
csharpusing OpenGIS.Esri.Geometry.Core.IO; // 导出 string wkt = WktExportOperator.ExportToWkt(geometry); // "POINT (10.5 20.7)" // "POINT Z (10.5 20.7 30.1)" // "LINESTRING (0 0, 10 0, 10 10)" // "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))" // "MULTIPOINT ((1 2), (3 4))" // 导入 Geometry geometry = WktImportOperator.ImportFromWkt("POINT (10.5 20.7)");
WKB(Well-Known Binary)
csharp// 导出(默认小端序) byte[] wkb = WkbExportOperator.ExportToWkb(geometry); byte[] wkbBigEndian = WkbExportOperator.ExportToWkb(geometry, bigEndian: true); // 导入(自动检测字节序) Geometry geometry = WkbImportOperator.ImportFromWkb(wkbBytes);
GeoJSON
csharp// 导出 string geoJson = GeoJsonExportOperator.ExportToGeoJson(geometry); // {"type":"Point","coordinates":[10.5,20.7]} // 导入 Geometry geometry = GeoJsonImportOperator.ImportFromGeoJson(geoJsonString);
Esri JSON
csharp// 导出 string esriJson = EsriJsonExportOperator.Instance.Execute(geometry); // Point: {"x":10.5,"y":20.7} // MultiPoint: {"points":[[1,2],[3,4]]} // Polyline: {"paths":[[[0,0],[10,0],[10,10]]]} // Polygon: {"rings":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]} // Envelope: {"xmin":0,"ymin":0,"xmax":10,"ymax":10} // 导入 Geometry geometry = EsriJsonImportOperator.ImportFromEsriJson(esriJsonString);
System.Text.Json 集成
csharpusing OpenGIS.Esri.Geometry.Json.Converters; var options = new JsonSerializerOptions(); options.Converters.Add(new PointJsonConverter()); string json = JsonSerializer.Serialize(point, options); Point point = JsonSerializer.Deserialize<Point>(json, options);
空间参考
csharpusing OpenGIS.Esri.Geometry.Core.SpatialReference; // 预定义空间参考 var wgs84 = SpatialReference.Wgs84(); // EPSG:4326 (经纬度) var webMercator = SpatialReference.WebMercator(); // EPSG:3857 (Web 墨卡托) // 自定义 WKID var sr = new SpatialReference(4490); // CGCS2000 // 属性 sr.Wkid // int?, WKID 编号 sr.LatestWkid // int?, 最新 WKID sr.Wkt // string?, WKT 表示 // 组合几何与空间参考 var mapGeom = new MapGeometry(geometry, SpatialReference.Wgs84()); mapGeom.Geometry // Geometry 对象 mapGeom.SpatialReference // SpatialReference 对象
Proximity2DResult(邻近搜索结果)
csharpProximity2DResult result = GeometryEngine.GetNearestCoordinate(geometry, point); result.IsEmpty // bool, 结果是否为空 result.Coordinate // Point, 最近坐标点 result.VertexIndex // int, 最近顶点索引 result.Distance // double, 距离 result.IsRightSide // bool, 是否在多路径几何的右侧
典型工作流
端到端:WKT 读入 → 有效性校验 → 叠加分析 → 输出 GeoJSON 与 Esri JSON。
csharpusing OpenGIS.Esri.Geometry.Core; using OpenGIS.Esri.Geometry.Core.Geometries; using OpenGIS.Esri.Geometry.Core.IO; // 1. 从 WKT 读入输入几何(环由库自动闭合) Geometry parcel = GeometryEngine.GeometryFromWkt( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))"); Geometry zone = GeometryEngine.GeometryFromWkt( "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))"); // 2. 校验:先确认几何本身可用,不要拿 0 容差去"修复" if (!parcel.IsValid() || !zone.IsValid()) throw new InvalidOperationException("输入几何无效,请先修正数据"); // 3. 叠加:求交集(5×5 的方形重叠区) Geometry overlapArea = GeometryEngine.Intersection(parcel, zone); bool intersects = GeometryEngine.Intersects(parcel, zone); // true double area = GeometryEngine.Area(overlapArea); // 约 25(内部容差带来 1e-6 级误差) // 4. 输出两种格式 string geoJson = GeometryEngine.GeometryToGeoJson(overlapArea); string esriJson = EsriJsonExportOperator.Instance.Execute(overlapArea); // 5. 回读校验:序列化再反序列化,几何应一致 Geometry roundTrip = GeometryEngine.GeometryFromGeoJson(geoJson); bool same = GeometryEngine.Equals(overlapArea, roundTrip);
输出与验收:
overlapArea是(5,5)-(10,10)的矩形,GeometryEngine.Area()实测约24.999998(默认容差1e-10下的浮点结果,不要断言严格等于 25)。geoJson形如{"type":"Polygon","coordinates":[[[10,5],...]]};esriJson形如{"rings":[[[10,5],...]]}。same必须为true;若为false,先检查环是否闭合、坐标顺序是否一致。GeometryEngine.Simplify(geometry, tolerance)的tolerance必须为正,传0.0会抛ArgumentException: Tolerance must be positive;且Simplify只做抽稀,不保证把自相交环修成有效面,需修复无效数据请在上游处理。
注意:集合运算返回
Geometry基类,需要面积/环等具体成员时按实际类型(Polygon/Polyline)转型后再用。
常见开发场景
场景 1:判断点是否在多边形内
csharpvar polygon = new Polygon(); polygon.AddRing(new[] { new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10), new Point(0, 0) }); var point = new Point(5, 5); bool inside = GeometryEngine.Contains(polygon, point); // true
场景 2:计算两个经纬度点之间的大地距离(米)
csharpvar beijing = new Point(116.4074, 39.9042); var shanghai = new Point(121.4737, 31.2304); double meters = GeometryEngine.GeodesicDistance(beijing, shanghai);
场景 3:创建缓冲区
csharpvar point = new Point(10, 20); Geometry buffer = GeometryEngine.Buffer(point, 5.0); // 半径为 5 的缓冲区 var envelope = buffer.GetEnvelope(); // XMin=5, YMin=15, XMax=15, YMax=25
场景 4:计算两个区域的交集
csharpvar env1 = new Envelope(0, 0, 10, 10); var env2 = new Envelope(5, 5, 15, 15); Geometry result = GeometryEngine.Intersection(env1, env2); // 交集区域: (5,5) 到 (10,10)
场景 5:合并多个几何体
csharpvar p1 = new Point(0, 0); var p2 = new Point(10, 10); Geometry merged = GeometryEngine.Union(p1, p2); // 结果为 MultiPoint,包含两个点
场景 6:GeoJSON 往返序列化
csharpvar polygon = new Polygon(); polygon.AddRing(new[] { new Point(0, 0), new Point(1, 0), new Point(1, 1), new Point(0, 1), new Point(0, 0) }); string geoJson = GeometryEngine.GeometryToGeoJson(polygon); Geometry restored = GeometryEngine.GeometryFromGeoJson(geoJson);
场景 7:从 WKT 导入几何体并做空间分析
csharpvar geom1 = GeometryEngine.GeometryFromWkt("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))"); var geom2 = GeometryEngine.GeometryFromWkt("POINT (5 5)"); bool contains = GeometryEngine.Contains(geom1, geom2); // true double distance = GeometryEngine.Distance(geom1, geom2); // 0
场景 8:获取几何体的凸包
csharpvar multiPoint = new MultiPoint(new[] { new Point(0, 0), new Point(10, 0), new Point(5, 10), new Point(3, 3), new Point(7, 2) }); Geometry hull = GeometryEngine.ConvexHull(multiPoint); // 结果为包围所有点的最小凸多边形
场景 9:计算多边形的质心
csharpvar polygon = new Polygon(); polygon.AddRing(new[] { new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10), new Point(0, 0) }); Point centroid = GeometryEngine.Centroid(polygon); // centroid.X = 5, centroid.Y = 5
场景 10:线的简化与加密
csharpvar polyline = new Polyline(); polyline.AddPath(new[] { new Point(0, 0), new Point(5, 0.1), new Point(10, 0), new Point(10, 5), new Point(10, 10) }); // 简化:移除偏差小于 tolerance 的顶点 Geometry simplified = GeometryEngine.Simplify(polyline, 0.5); // 加密:确保每段不超过 maxSegmentLength Geometry densified = GeometryEngine.Densify(polyline, 2.0); // 泛化:保持形状前提下减少顶点 Geometry generalized = GeometryEngine.Generalize(polyline, 1.0);
构建与测试
bash# 构建 dotnet build # 运行测试(xUnit) dotnet test # 运行示例 dotnet run --project samples/OpenGIS.Esri.Geometry.Samples
常见问题
| 问题 | 解决 |
|---|---|
| NuGet 安装后找不到命名空间 | 同时安装 OpenGIS.Esri.Geometry.Core 与(可选)OpenGIS.Esri.Geometry.Json;命名空间为 OpenGIS.Esri.Geometry.Core.* |
| Polygon 面积 / 包含判断异常 | 环必须闭合(首尾点相同);注意 IsValid() 对自相交环较宽松,可能返回 true |
Simplify 抛 ArgumentException: Tolerance must be positive | 容差必须为正,不能传 0.0;Simplify 只抽稀,不修复自相交 |
| 浮点坐标比较误差 | 内部容差 GeometryConstants.DefaultTolerance(1e-10),为内部常量、不可外部配置;面积等断言用近似比较 |
| 集合运算返回值类型不符预期 | 返回 Geometry 基类,需按实际类型做类型转换后使用 |
无法 new 算子实例 | 所有 Operator 类通过 Instance 属性获取 Lazy 单例 |
修改 GetRing() / GetPath() 结果报错 | 返回 IReadOnlyList<Point> 只读视图,需要复制后修改 |
GeodesicDistance 结果异常 | 基于 WGS84(Vincenty 公式),输入必须是经纬度坐标;GeodesicArea 使用球面超量公式 |
| GeoJSON 导出类型与预期不符 | 单路径 Polyline 导出为 LineString,多路径导出为 MultiLineString;Envelope 导出为 Polygon |
AI 使用建议
- 本技能的组织:主文件给高频用法,完整参数表与高级示例在
reference/geometry-types.md,需要时再展开;示例工程与生成的数据写到工作目录,不要改动技能目录本身。 - 执行环境:先
dotnet --version(库目标netstandard2.0,可被 net6/net8/net9/net10 引用);Windows 上用 PowerShell 执行dotnet命令,dotnet new console -o demo && cd demo建最小工程。 - 依赖坐标:
dotnet add package OpenGIS.Esri.Geometry.Core --version 1.0.9(NuGet 最新 1.0.9,2026-09-21),需要System.Text.Json转换器时再装OpenGIS.Esri.Geometry.Json;还原失败报NU1101说明源或版本不可用,报告“依赖不可下载”,不要自己编版本号。 - 验证产物:最小控制台程序断言三件事——
GeometryEngine.GeometryFromWkt→GeometryToWkt往返、GeometryEngine.Contains(polygon, point)返回 true、GeometryEngine.Buffer(point, 5.0).GetEnvelope()为 (XMin=5,YMin=15,XMax=15,YMax=25),再dotnet run看实际输出;dotnet build成功不代表几何结果正确。 - 构建与测试:
dotnet build编译、dotnet test跑 xUnit、dotnet run --project samples/OpenGIS.Esri.Geometry.Samples跑示例(需在仓库 checkout 内,且该路径存在);输出在bin/、obj/,不要提交。 - 编译期陷阱:
Polygon.AddRing必须闭合(首尾点相同)否则面积/包含判断异常;集合运算返回Geometry基类,要按Point/Polygon转型后再用;容差GeometryConstants.DefaultTolerance(1e-10)是内部常量,无法外部调整。 - 大地测量前提:
GeodesicDistance/GeodesicArea基于 WGS84(Vincenty),输入必须是经纬度;对投影坐标(如 EPSG:3857 米制)调用会得到无意义结果。 - 安全与权限:
dotnet restore需访问 NuGet 源,离线时先确认本地缓存;修改全局NuGet.config、安装 SDK 或写入系统目录前先征得用户同意,命令输出里不要回显私有源凭据。
相关技能
- nettopologysuite — .NET JTS 移植(几何计算核心):../nettopologysuite/SKILL.md
- opengis-utils-for-net — .NET GIS 统一工具包:../opengis-utils-for-net/SKILL.md
- geometry-api-java — Java 版本 Esri Geometry API:../geometry-api-java/SKILL.md
- jts — JTS Topology Suite:../jts/SKILL.md


