C#如何检测两个地理位置点间的路径是否穿过指定地理围栏
C# 地理围栏线段相交检测实现方案
你需要实现的是线段(起点-终点连线)与多边形(地理围栏)的空间关系判断,命中场景分为两类:线段和围栏任意边相交、线段至少一个端点/整条线段在围栏内部,两类场景都满足你说的「相交或从内部穿过」的要求。
前置准备:定义基础数据结构
首先统一坐标数据结构,注意所有参与计算的坐标必须使用同一坐标系(WGS84/GCJ02/BD09等,禁止混合不同坐标系坐标):
// 经纬度点结构 public struct GeoPoint { public double Longitude { get; } // 经度对应X轴 public double Latitude { get; } // 纬度对应Y轴 public GeoPoint(double lon, double lat) { Longitude = lon; Latitude = lat; } } // 地理围栏直接使用 List<GeoPoint> 类型存储,点按顺时针/逆时针顺序排列即可,无需手动闭合
核心工具方法实现
1. 射线投射法判断点是否在多边形内部
就是你提到的Ray Casting算法的落地实现,额外处理了点刚好在围栏边上的边界场景:
/// <summary> /// 判断点是否在多边形内部/边上 /// </summary> /// <param name="point">待检测点</param> /// <param name="polygon">地理围栏多边形点集合</param> /// <returns>true: 在内部/边上;false: 在外部</returns> public bool IsPointInPolygon(GeoPoint point, List<GeoPoint> polygon) { bool inside = false; int n = polygon.Count; for (int i = 0, j = n - 1; i < n; j = i++) { GeoPoint p1 = polygon[i]; GeoPoint p2 = polygon[j]; // 优先判断点是否刚好在当前边上 if (IsPointOnSegment(point, p1, p2)) return true; // 射线相交判断:统计水平射线与多边形边的交点数,奇数为内部 if ((p1.Latitude > point.Latitude) != (p2.Latitude > point.Latitude)) { double lonIntersect = (point.Latitude - p1.Latitude) * (p2.Longitude - p1.Longitude) / (p2.Latitude - p1.Latitude) + p1.Longitude; if (point.Longitude <= lonIntersect) inside = !inside; } } return inside; }
2. 线段相交判断工具
包含点是否在线段上、两条线段是否相交的两个基础方法:
/// <summary> /// 判断点c是否在线段ab上 /// </summary> private bool IsPointOnSegment(GeoPoint c, GeoPoint a, GeoPoint b) { // 坐标范围快速过滤 if (c.Longitude < Math.Min(a.Longitude, b.Longitude) || c.Longitude > Math.Max(a.Longitude, b.Longitude)) return false; if (c.Latitude < Math.Min(a.Latitude, b.Latitude) || c.Latitude > Math.Max(a.Latitude, b.Latitude)) return false; // 叉乘为0说明三点共线,1e-8为浮点误差容限 double cross = (b.Longitude - a.Longitude) * (c.Latitude - a.Latitude) - (b.Latitude - a.Latitude) * (c.Longitude - a.Longitude); return Math.Abs(cross) < 1e-8; } /// <summary> /// 判断线段ab和线段cd是否相交 /// </summary> public bool IsSegmentIntersect(GeoPoint a, GeoPoint b, GeoPoint c, GeoPoint d) { // 快速排斥:外接矩形不重叠直接返回不相交 if (Math.Max(a.Longitude, b.Longitude) < Math.Min(c.Longitude, d.Longitude) || Math.Max(c.Longitude, d.Longitude) < Math.Min(a.Longitude, b.Longitude) || Math.Max(a.Latitude, b.Latitude) < Math.Min(c.Latitude, d.Latitude) || Math.Max(c.Latitude, d.Latitude) < Math.Min(a.Latitude, b.Latitude)) return false; // 跨立实验判断线段相交 double cross1 = (b.Longitude - a.Longitude) * (c.Latitude - a.Latitude) - (b.Latitude - a.Latitude) * (c.Longitude - a.Longitude); double cross2 = (b.Longitude - a.Longitude) * (d.Latitude - a.Latitude) - (b.Latitude - a.Latitude) * (d.Longitude - a.Longitude); double cross3 = (d.Longitude - c.Longitude) * (a.Latitude - c.Latitude) - (d.Latitude - c.Latitude) * (a.Longitude - c.Longitude); double cross4 = (d.Longitude - c.Longitude) * (b.Latitude - c.Latitude) - (d.Latitude - c.Latitude) * (b.Longitude - c.Longitude); if (cross1 * cross2 < 0 && cross3 * cross4 < 0) return true; // 处理端点落在另一条线段的边界场景 if (IsPointOnSegment(a, c, d) || IsPointOnSegment(b, c, d) || IsPointOnSegment(c, a, b) || IsPointOnSegment(d, a, b)) return true; return false; }
最终业务逻辑封装
整合上述工具方法,直接调用即可得到你需要的检测结果:
/// <summary> /// 判断起点到终点的路径是否穿过地理围栏 /// </summary> /// <param name="start">路径起点</param> /// <param name="end">路径终点</param> /// <param name="geofence">地理围栏多边形</param> /// <returns>true: 路径和围栏相交/穿过内部;false: 路径完全在围栏外</returns> public bool IsPathCrossGeofence(GeoPoint start, GeoPoint end, List<GeoPoint> geofence) { // 场景1:起点或终点在围栏内部,直接命中 if (IsPointInPolygon(start, geofence) || IsPointInPolygon(end, geofence)) return true; // 场景2:路径线段和围栏任意一条边相交 int n = geofence.Count; for (int i = 0; i < n; i++) { GeoPoint fencePoint1 = geofence[i]; GeoPoint fencePoint2 = geofence[(i + 1) % n]; // 自动闭合围栏 if (IsSegmentIntersect(start, end, fencePoint1, fencePoint2)) return true; } return false; }
注意事项
- 所有参与计算的经纬度必须使用同一坐标系,不要混合WGS84、GCJ02、BD09等不同坐标系的坐标,否则会产生几百米到几公里的计算误差
- 代码中的
1e-8是浮点计算的容差值,你可以根据业务要求的坐标精度自行调整 - 如果你的地理围栏是圆形围栏,不需要使用上述多边形判断方案,直接计算线段到圆心的距离是否小于半径,再结合端点到圆心的距离判断即可
内容的提问来源于stack exchange,提问作者Beachmat
相关产品推荐
相关产品推荐

