04-空间关系操作符
第四章:空间关系操作符
4.1 空间关系概述
空间关系是 GIS 分析的基础,用于描述两个几何对象在空间上的相对位置关系。geometry-api-net 提供了 9 种符合 OGC 标准的空间关系测试操作符。
4.1.1 空间关系类型
| 关系 | 操作符 | 说明 |
|---|---|---|
| 包含 | ContainsOperator | A 完全包含 B |
| 相交 | IntersectsOperator | A 和 B 有共同部分 |
| 距离 | DistanceOperator | A 和 B 之间的最短距离 |
| 相等 | EqualsOperator | A 和 B 空间上相同 |
| 分离 | DisjointOperator | A 和 B 没有共同部分 |
| 在内部 | WithinOperator | A 完全在 B 内部 |
| 穿过 | CrossesOperator | A 穿过 B |
| 接触 | TouchesOperator | A 和 B 仅在边界相接 |
| 重叠 | OverlapsOperator | A 和 B 部分重叠 |
4.1.2 操作符设计模式
所有空间关系操作符都实现了 IBinaryGeometryOperator<bool> 接口(除了 DistanceOperator 返回 double):
public interface IBinaryGeometryOperator<TResult>
{
TResult Execute(Geometry geometry1, Geometry geometry2,
SpatialReference? spatialRef = null);
}
操作符采用单例模式,通过 Instance 属性获取实例:
// 使用操作符
bool result = ContainsOperator.Instance.Execute(geometry1, geometry2);
// 或使用 GeometryEngine
bool result = GeometryEngine.Contains(geometry1, geometry2);
4.2 Contains(包含)
4.2.1 定义
如果几何对象 A 包含几何对象 B,意味着 B 的所有点都在 A 的内部或边界上,且 B 的内部与 A 的内部相交。
数学表达:
- B 在 A 内部:B ⊆ A
- Contains(A, B) = true 等价于 Within(B, A) = true
4.2.2 API
public class ContainsOperator : IBinaryGeometryOperator<bool>
{
public static ContainsOperator Instance { get; }
public bool Execute(Geometry geometry1, Geometry geometry2,
SpatialReference? spatialRef = null);
}
4.2.3 使用示例
using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;
using Esri.Geometry.Core.Operators;
// 包络矩形包含点
var envelope = new Envelope(0, 0, 100, 100);
var pointInside = new Point(50, 50);
var pointOutside = new Point(150, 50);
bool contains1 = ContainsOperator.Instance.Execute(envelope, pointInside); // true
bool contains2 = ContainsOperator.Instance.Execute(envelope, pointOutside); // false
// 使用 GeometryEngine
bool contains3 = GeometryEngine.Contains(envelope, pointInside); // true
// 多边形包含点(使用光线投射算法)
var polygon = new Polygon();
polygon.AddRing(new List<Point>
{
new Point(0, 0),
new Point(100, 0),
new Point(100, 100),
new Point(0, 100),
new Point(0, 0)
});
bool inPolygon = GeometryEngine.Contains(polygon, new Point(50, 50)); // true
bool inPolygon2 = GeometryEngine.Contains(polygon, new Point(150, 50)); // false
4.2.4 实现原理:光线投射算法
对于点在多边形内的测试,ContainsOperator 使用光线投射算法(Ray Casting Algorithm):
private static bool IsPointInPolygon(Polygon polygon, Point point)
{
if (polygon.IsEmpty || polygon.RingCount == 0)
return false;
var inside = false;
var x = point.X;
var y = point.Y;
// 测试外环
var ring = polygon.GetRing(0);
if (ring.Count < 3) return false;
// 光线投射:从点向右发射一条射线,计算与多边形边界的交点数
// 奇数次交点 = 在内部,偶数次交点 = 在外部
for (int i = 0, j = ring.Count - 1; i < ring.Count; j = i++)
{
double xi = ring[i].X, yi = ring[i].Y;
double xj = ring[j].X, yj = ring[j].Y;
if (((yi > y) != (yj > y)) &&
(x < (xj - xi) * (y - yi) / (yj - yi) + xi))
{
inside = !inside;
}
}
// 检查孔洞
for (var ringIndex = 1; ringIndex < polygon.RingCount; ringIndex++)
{
var holeRing = polygon.GetRing(ringIndex);
// ... 类似的光线投射检测
// 如果点在孔洞内,则不在多边形内
}
return inside;
}
算法复杂度:O(n),其中 n 是多边形顶点数。
4.2.5 应用场景
// 场景:判断用户是否在服务区域内
var serviceArea = new Polygon();
serviceArea.AddRing(new List<Point>
{
new Point(116.2, 39.7),
new Point(116.6, 39.7),
new Point(116.6, 40.1),
new Point(116.2, 40.1),
new Point(116.2, 39.7)
});
var userLocation = new Point(116.4, 39.9);
if (GeometryEngine.Contains(serviceArea, userLocation))
{
Console.WriteLine("用户在服务区域内");
}
else
{
Console.WriteLine("用户不在服务区域内");
}
4.3 Intersects(相交)
4.3.1 定义
如果两个几何对象有任何共同的点,则它们相交。这是最常用的空间关系测试。
数学表达:A ∩ B ≠ ∅
4.3.2 API
public class IntersectsOperator : IBinaryGeometryOperator<bool>
{
public static IntersectsOperator Instance { get; }
public bool Execute(Geometry geometry1, Geometry geometry2,
SpatialReference? spatialRef = null);
}
4.3.3 使用示例
using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;
// 两个重叠的包络矩形
var env1 = new Envelope(0, 0, 100, 100);
var env2 = new Envelope(50, 50, 150, 150);
var env3 = new Envelope(200, 200, 300, 300);
bool intersects1 = GeometryEngine.Intersects(env1, env2); // true
bool intersects2 = GeometryEngine.Intersects(env1, env3); // false
// 点与包络矩形
var point = new Point(50, 50);
bool intersects3 = GeometryEngine.Intersects(env1, point); // true
// 多边形相交
var poly1 = new Polygon();
poly1.AddRing(new List<Point>
{
new Point(0, 0),
new Point(50, 0),
new Point(50, 50),
new Point(0, 50),
new Point(0, 0)
});
var poly2 = new Polygon();
poly2.AddRing(new List<Point>
{
new Point(25, 25),
new Point(75, 25),
new Point(75, 75),
new Point(25, 75),
new Point(25, 25)
});
bool polyIntersects = GeometryEngine.Intersects(poly1, poly2); // true
4.3.4 实现原理
IntersectsOperator 根据不同的几何类型组合采用不同的算法:
public bool Execute(Geometry geometry1, Geometry geometry2,
SpatialReference? spatialRef = null)
{
// 空几何不相交
if (geometry1.IsEmpty || geometry2.IsEmpty)
return false;
// 包络矩形相交测试(快速过滤)
if (geometry1 is Envelope env1 && geometry2 is Envelope env2)
return env1.Intersects(env2);
// 点与包络矩形
if (geometry1 is Envelope env && geometry2 is Point pt)
return env.Contains(pt);
// 点与多边形
if (geometry1 is Polygon poly && geometry2 is Point p)
return ContainsOperator.Instance.Execute(poly, p);
// 默认使用包络矩形相交测试
var envelope1 = geometry1.GetEnvelope();
var envelope2 = geometry2.GetEnvelope();
return envelope1.Intersects(envelope2);
}
4.3.5 应用场景
// 场景:查找与查询范围相交的所有对象
var queryExtent = new Envelope(116.3, 39.8, 116.5, 40.0);
var buildings = new List<Polygon> { /* 建筑物多边形列表 */ };
var intersectingBuildings = buildings
.Where(b => GeometryEngine.Intersects(queryExtent, b))
.ToList();
Console.WriteLine($"找到 {intersectingBuildings.Count} 个相交的建筑物");
4.4 Distance(距离)
4.4.1 定义
计算两个几何对象之间的最短欧几里得距离。如果两个几何对象相交,距离为 0。
4.4.2 API
public class DistanceOperator : IBinaryGeometryOperator<double>
{
public static DistanceOperator Instance { get; }
public double Execute(Geometry geometry1, Geometry geometry2,
SpatialReference? spatialRef = null);
}
4.4.3 使用示例
using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;
// 两点之间的距离
var point1 = new Point(0, 0);
var point2 = new Point(3, 4);
double distance = GeometryEngine.Distance(point1, point2);
Console.WriteLine($"距离:{distance}"); // 5(勾股定理:3² + 4² = 5²)
// 点到包络矩形的距离
var envelope = new Envelope(10, 10, 20, 20);
var testPoint = new Point(0, 0);
double distToEnv = GeometryEngine.Distance(testPoint, envelope);
Console.WriteLine($"点到矩形的距离:{distToEnv:F4}"); // ~14.14
// 点在矩形内部,距离为 0
var pointInside = new Point(15, 15);
double distInside = GeometryEngine.Distance(pointInside, envelope);
Console.WriteLine($"内部点距离:{distInside}"); // 0
4.4.4 实现原理
public double Execute(Geometry geometry1, Geometry geometry2,
SpatialReference? spatialRef = null)
{
// 点与点的距离
if (geometry1 is Point p1 && geometry2 is Point p2)
return p1.Distance(p2);
// 点与包络矩形的距离
if (geometry1 is Point point && geometry2 is Envelope envelope)
{
// 如果点在包络内,距离为 0
if (envelope.Contains(point))
return 0;
// 计算点到最近边的距离
double dx = Math.Max(0, Math.Max(envelope.XMin - point.X, point.X - envelope.XMax));
double dy = Math.Max(0, Math.Max(envelope.YMin - point.Y, point.Y - envelope.YMax));
return Math.Sqrt(dx * dx + dy * dy);
}
// 包络矩形之间的距离
if (geometry1 is Envelope env1 && geometry2 is Envelope env2)
{
if (env1.Intersects(env2))
return 0;
double dx = Math.Max(0, Math.Max(env1.XMin - env2.XMax, env2.XMin - env1.XMax));
double dy = Math.Max(0, Math.Max(env1.YMin - env2.YMax, env2.YMin - env1.YMax));
return Math.Sqrt(dx * dx + dy * dy);
}
// 对于其他几何类型,使用包络近似
return Execute(geometry1.GetEnvelope(), geometry2.GetEnvelope());
}
4.4.5 应用场景
// 场景:查找最近的商店
var userLocation = new Point(116.4, 39.9);
var stores = new List<(string name, Point location)>
{
("商店A", new Point(116.41, 39.91)),
("商店B", new Point(116.38, 39.88)),
("商店C", new Point(116.45, 39.95))
};
var nearestStore = stores
.OrderBy(s => GeometryEngine.Distance(userLocation, s.location))
.First();
double nearestDistance = GeometryEngine.Distance(userLocation, nearestStore.location);
Console.WriteLine($"最近的商店:{nearestStore.name},距离:{nearestDistance:F4} 度");
4.5 Equals(相等)
4.5.1 定义
判断两个几何对象在空间上是否完全相同(考虑容差)。
4.5.2 API
public class EqualsOperator : IBinaryGeometryOperator<bool>
{
public static EqualsOperator Instance { get; }
public bool Execute(Geometry geometry1, Geometry geometry2,
SpatialReference? spatialRef = null);
}
4.5.3 使用示例
using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;
// 相同坐标的点
var point1 = new Point(10.0, 20.0);
var point2 = new Point(10.0, 20.0);
bool equals1 = GeometryEngine.Equals(point1, point2); // true
// 容差范围内的点
var point3 = new Point(10.0000001, 20.0000001);
bool equals2 = GeometryEngine.Equals(point1, point3); // true(在默认容差内)
// 不同的点
var point4 = new Point(11.0, 21.0);
bool equals3 = GeometryEngine.Equals(point1, point4); // false
// 相同的包络矩形
var env1 = new Envelope(0, 0, 100, 100);
var env2 = new Envelope(0, 0, 100, 100);
bool equals4 = GeometryEngine.Equals(env1, env2); // true
4.5.4 实现原理
public bool Execute(Geometry geometry1, Geometry geometry2,
SpatialReference? spatialRef = null)
{
if (geometry1 == null || geometry2 == null)
return false;
if (geometry1.Type != geometry2.Type)
return false;
if (geometry1.IsEmpty && geometry2.IsEmpty)
return true;
if (geometry1.IsEmpty != geometry2.IsEmpty)
return false;
// 点的相等性
if (geometry1 is Point p1 && geometry2 is Point p2)
return p1.Equals(p2, GeometryConstants.DefaultTolerance);
// 包络矩形的相等性
if (geometry1 is Envelope env1 && geometry2 is Envelope env2)
{
return Math.Abs(env1.XMin - env2.XMin) <= GeometryConstants.DefaultTolerance &&
Math.Abs(env1.YMin - env2.YMin) <= GeometryConstants.DefaultTolerance &&
Math.Abs(env1.XMax - env2.XMax) <= GeometryConstants.DefaultTolerance &&
Math.Abs(env1.YMax - env2.YMax) <= GeometryConstants.DefaultTolerance;
}
// 其他类型使用包络比较
var envelope1 = geometry1.GetEnvelope();
var envelope2 = geometry2.GetEnvelope();
return Execute(envelope1, envelope2);
}
4.6 Disjoint(分离)
4.6.1 定义
如果两个几何对象没有任何共同的点,则它们分离。这是 Intersects 的逆操作。
数学表达:A ∩ B = ∅
4.6.2 API
public class DisjointOperator : IBinaryGeometryOperator<bool>
{
public static DisjointOperator Instance { get; }
public bool Execute(Geometry geometry1, Geometry geometry2,
SpatialReference? spatialRef = null);
}
4.6.3 使用示例
using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;
var env1 = new Envelope(0, 0, 10, 10);
var env2 = new Envelope(20, 20, 30, 30); // 完全分离
var env3 = new Envelope(5, 5, 15, 15); // 重叠
bool disjoint1 = GeometryEngine.Disjoint(env1, env2); // true
bool disjoint2 = GeometryEngine.Disjoint(env1, env3); // false
// 等价于 !Intersects
bool notIntersects = !GeometryEngine.Intersects(env1, env2); // true
4.6.4 实现原理
public bool Execute(Geometry geometry1, Geometry geometry2,
SpatialReference? spatialRef = null)
{
// Disjoint 是 Intersects 的逆
return !IntersectsOperator.Instance.Execute(geometry1, geometry2, spatialRef);
}
4.7 Within(在内部)
4.7.1 定义
如果几何对象 A 完全在几何对象 B 的内部,则 A 在 B 内。这是 Contains 的逆操作。
数学表达:Within(A, B) = Contains(B, A)
4.7.2 API
public class WithinOperator : IBinaryGeometryOperator<bool>
{
public static WithinOperator Instance { get; }
public bool Execute(Geometry geometry1, Geometry geometry2,
SpatialReference? spatialRef = null);
}
4.7.3 使用示例
using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;
var smallEnv = new Envelope(20, 20, 30, 30);
var largeEnv = new Envelope(0, 0, 100, 100);
bool within1 = GeometryEngine.Within(smallEnv, largeEnv); // true
bool within2 = GeometryEngine.Within(largeEnv, smallEnv); // false
// 点在多边形内
var point = new Point(50, 50);
var polygon = new Polygon();
polygon.AddRing(new List<Point>
{
new Point(0, 0),
new Point(100, 0),
new Point(100, 100),
new Point(0, 100),
new Point(0, 0)
});
bool pointWithin = GeometryEngine.Within(point, polygon); // true
4.7.4 实现原理
public bool Execute(Geometry geometry1, Geometry geometry2,
SpatialReference? spatialRef = null)
{
// Within 是 Contains 的逆:A within B = B contains A
return ContainsOperator.Instance.Execute(geometry2, geometry1, spatialRef);
}
4.8 Crosses(穿过)
4.8.1 定义
如果两个几何对象的内部相交,但既不完全相同,也没有一个包含另一个,则它们穿过。通常用于线与线或线与面的关系。
4.8.2 API
public class CrossesOperator : IBinaryGeometryOperator<bool>
{
public static CrossesOperator Instance { get; }
public bool Execute(Geometry geometry1, Geometry geometry2,
SpatialReference? spatialRef = null);
}
4.8.3 使用示例
using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;
// 两条交叉的线
var line1 = new Polyline();
line1.AddPath(new List<Point>
{
new Point(0, 50),
new Point(100, 50) // 水平线
});
var line2 = new Polyline();
line2.AddPath(new List<Point>
{
new Point(50, 0),
new Point(50, 100) // 垂直线
});
bool crosses = GeometryEngine.Crosses(line1, line2); // true
// 线穿过多边形
var polyline = new Polyline();
polyline.AddPath(new List<Point>
{
new Point(-10, 50),
new Point(110, 50) // 穿过多边形
});
var polygon = new Polygon();
polygon.AddRing(new List<Point>
{
new Point(0, 0),
new Point(100, 0),
new Point(100, 100),
new Point(0, 100),
new Point(0, 0)
});
bool lineCrossesPoly = GeometryEngine.Crosses(polyline, polygon); // true
4.9 Touches(接触)
4.9.1 定义
如果两个几何对象仅在边界上有共同点,而内部不相交,则它们接触。
4.9.2 API
public class TouchesOperator : IBinaryGeometryOperator<bool>
{
public static TouchesOperator Instance { get; }
public bool Execute(Geometry geometry1, Geometry geometry2,
SpatialReference? spatialRef = null);
}
4.9.3 使用示例
using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;
// 两个相邻的多边形
var poly1 = new Polygon();
poly1.AddRing(new List<Point>
{
new Point(0, 0),
new Point(50, 0),
new Point(50, 100),
new Point(0, 100),
new Point(0, 0)
});
var poly2 = new Polygon();
poly2.AddRing(new List<Point>
{
new Point(50, 0), // 共享边界
new Point(100, 0),
new Point(100, 100),
new Point(50, 100), // 共享边界
new Point(50, 0)
});
bool touches = GeometryEngine.Touches(poly1, poly2); // true(共享边界)
// 点在多边形边界上
var boundaryPoint = new Point(0, 50); // 在 poly1 的左边界上
bool pointTouches = GeometryEngine.Touches(boundaryPoint, poly1); // true
4.10 Overlaps(重叠)
4.10.1 定义
如果两个相同维度的几何对象相交,但既不完全相同,也没有一个完全包含另一个,则它们重叠。
4.10.2 API
public class OverlapsOperator : IBinaryGeometryOperator<bool>
{
public static OverlapsOperator Instance { get; }
public bool Execute(Geometry geometry1, Geometry geometry2,
SpatialReference? spatialRef = null);
}
4.10.3 使用示例
using Esri.Geometry.Core;
using Esri.Geometry.Core.Geometries;
// 两个部分重叠的多边形
var poly1 = new Polygon();
poly1.AddRing(new List<Point>
{
new Point(0, 0),
new Point(60, 0),
new Point(60, 60),
new Point(0, 60),
new Point(0, 0)
});
var poly2 = new Polygon();
poly2.AddRing(new List<Point>
{
new Point(40, 40),
new Point(100, 40),
new Point(100, 100),
new Point(40, 100),
new Point(40, 40)
});
bool overlaps = GeometryEngine.Overlaps(poly1, poly2); // true
// 两个包络矩形重叠
var env1 = new Envelope(0, 0, 50, 50);
var env2 = new Envelope(25, 25, 75, 75);
bool envOverlaps = GeometryEngine.Overlaps(env1, env2); // true
4.11 空间关系矩阵
4.11.1 关系总结
下表显示了各种空间关系之间的逻辑关系:
| 条件 | Disjoint | Intersects | Contains | Within | Touches | Overlaps | Crosses | Equals |
|---|---|---|---|---|---|---|---|---|
| A ∩ B = ∅ | ✓ | |||||||
| A ∩ B ≠ ∅ | ✓ | |||||||
| B ⊆ A | ✓ | ✓ | ||||||
| A ⊆ B | ✓ | ✓ | ||||||
| 仅边界相交 | ✓ | ✓ | ||||||
| 部分内部重叠 | ✓ | ✓ | ||||||
| 线性穿过 | ✓ | ✓ | ||||||
| A = B | ✓ | ✓ | ✓ | ✓ |
4.11.2 互斥关系
// Disjoint 和 Intersects 互斥
Disjoint(A, B) = !Intersects(A, B)
// Contains 和 Within 是逆关系
Contains(A, B) = Within(B, A)
4.12 最佳实践
4.12.1 性能优化
// 1. 先用包络矩形快速过滤
var queryEnvelope = new Envelope(100, 100, 200, 200);
var candidates = allGeometries
.Where(g => g.GetEnvelope().Intersects(queryEnvelope));
// 2. 再进行精确测试
var results = candidates
.Where(g => GeometryEngine.Intersects(g, queryPolygon))
.ToList();
4.12.2 常见错误
// ❌ 错误:直接精确测试大量几何对象
var results = allGeometries
.Where(g => GeometryEngine.Contains(polygon, g)) // 性能差
.ToList();
// ✅ 正确:先包络过滤
var polygonEnvelope = polygon.GetEnvelope();
var results = allGeometries
.Where(g => polygonEnvelope.Intersects(g.GetEnvelope()))
.Where(g => GeometryEngine.Contains(polygon, g))
.ToList();
4.12.3 选择正确的关系测试
| 需求 | 推荐操作符 |
|---|---|
| 检查点是否在区域内 | Contains |
| 检查两个区域是否有交集 | Intersects |
| 查找最近的对象 | Distance |
| 检查两个对象是否相同 | Equals |
| 检查线是否穿过区域 | Crosses |
| 检查区域是否相邻 | Touches |
4.13 小结
本章详细介绍了 geometry-api-net 提供的 9 种空间关系操作符:
- Contains:测试包含关系,使用光线投射算法
- Intersects:测试是否有共同部分
- Distance:计算最短距离
- Equals:测试空间相等性
- Disjoint:测试分离关系(Intersects 的逆)
- Within:测试在内部关系(Contains 的逆)
- Crosses:测试穿过关系
- Touches:测试接触关系
- Overlaps:测试重叠关系
这些操作符是进行空间分析的基础,在下一章中我们将学习几何运算操作符,它们可以生成新的几何对象。

浙公网安备 33010602011771号