C# 判断经纬度是否在区域内,是否偏离线路
最近在做 经纬度点 是否在区域内、是否偏离既定线路功能,有些地图也提供了相应的功能,但是要掉接口
速度就会慢,由于数据量比较大,在网上找了一些代码,整理测试一下,还是比较准确,在这分享给大家。
主要原理:
判断点是否在区域内(光线投射算法):
求解从该点向右发出的水平线射线与多边形各边的交点,当交点数为奇数,则在内部。
不过要注意几种特殊情况:1、点在边或者顶点上;2、点在边的延长线上;3、点出发的水平射线与多边形相交在顶点上
判是否偏离线路:
求点到直线的垂足,获取最小值判断是否超过预设的距离。
1.公式
设直线方程为ax+by+c=0,点坐标为(m,n)
则垂足为((b*b*m-a*b*n-a*c)/(a*a+b*b),(a*a*n-a*b*m-b*c)/(a*a+b*b))
2.计算点到线段的最近点
如果该线段平行于X轴(Y轴),则过点point作该线段所在直线的垂线,垂足很容
易求得,然后计算出垂足,如果垂足在线段上则返回垂足,否则返回离垂足近的端
点;
如果该线段不平行于X轴也不平行于Y轴,则斜率存在且不为0。设线段的两端点为
pt1和pt2,斜率为:
k = ( pt2.y - pt1. y ) / (pt2.x - pt1.x );
该直线方程为:
y = k* ( x - pt1.x) + pt1.y
其垂线的斜率为 - 1 / k,
垂线方程为:
y = (-1/k) * (x - point.x) + point.y
联立两直线方程解得:
x = ( k^2 * pt1.x + k * (point.y - pt1.y ) + point.x ) / ( k^2 + 1)
y = k * ( x - pt1.x) + pt1.y;
原文链接:https://blog.csdn.net/ffgcc/article/details/80033038
直接上代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Commons { /// <summary> /// 地图计算帮助类 逍遥子 /// </summary> public class MapHelper { /// <summary> /// 地球半径,单位 m /// </summary> private static double EARTH_RADIUS = 6378137.0; /// <summary> /// 判断点是否在多边形内或多边形上 /// </summary> /// <param name="point">坐标点</param> /// <param name="Points">多边形边界点集合</param> /// <returns></returns> public static bool IsPtInPoly(MapPoint point, MapPoint[] Points) { double ALon= point.x, ALat= point.y; int iSum, iCount, iIndex; double dLon1 = 0, dLon2 = 0, dLat1 = 0, dLat2 = 0, dLon; if (Points.Length < 3) { return false; } iSum = 0; iCount = Points.Length; for (iIndex = 0; iIndex < iCount; iIndex++) { if (ALon == Points[iIndex].x && ALat == Points[iIndex].y) //A点在多边形上 return true; if (iIndex == iCount - 1) { dLon1 = Points[iIndex].x; dLat1 = Points[iIndex].y; dLon2 = Points[0].x; dLat2 = Points[0].y; } else { dLon1 = Points[iIndex].x; dLat1 = Points[iIndex].y; dLon2 = Points[iIndex + 1].x; dLat2 = Points[iIndex + 1].y; } //以下语句判断A点是否在边的两端点的纬度之间,在则可能有交点 if (((ALat > dLat1) && (ALat < dLat2)) || ((ALat > dLat2) && (ALat < dLat1))) { if (Math.Abs(dLat1 - dLat2) > 0) { //获取A点向左射线与边的交点的x坐标: dLon = dLon1 - ((dLon1 - dLon2) * (dLat1 - ALat)) / (dLat1 - dLat2); //如果交点在A点左侧,则射线与边的全部交点数加一: if (dLon < ALon) { iSum++; } //如果相等,则说明A点在边上 if (dLon == ALon) return true; } } } if ((iSum % 2) != 0) { return true; } return false; } /// <summary> /// 判断是否偏离航线在允许范围内 /// </summary> /// <param name="point">实时点用于判断此点是否偏离航线</param> /// <param name="points">航线组成的点坐标</param> /// <param name="allowRange">允许偏离航线的距离 单位:m</param> /// <returns>true -- 未偏离航线 ; false -- 偏离航线</returns> public static bool PointToPintLine(MapPoint point, List<MapPoint> points, double allowRange) { double minDistance = -1; for (int i = 0; i < points.Count - 1; i++) { if (points[i].x== points[i+1].x&& points[i].y== points[i+1].y) { continue; } /** * 获取线段的x取值范围和Y的取值范围 */ double[] rangeX = new double[2]; double[] rangeY = new double[2]; if (points[i].x > points[i + 1].x) { rangeX[0] = points[i + 1].x; rangeX[1] = points[i].x; } else { rangeX[0] = points[i].x; rangeX[1] = points[i + 1].x; } if (points[i].y > points[i + 1].y) { rangeY[0] = points[i + 1].y; rangeY[1] = points[i].y; } else { rangeY[0] = points[i].y; rangeY[1] = points[i + 1].y; } /** * 根据两点求出直线方程AX+BY+C=0中,A B C 的值 */ double a = points[i + 1].y - points[i].y; double b = points[i].x - points[i + 1].x; double c = points[i + 1].x * points[i].y - points[i].x * points[i + 1].y; /** * 求点到直线的垂足以及距离 */ //得到垂足点 MapPoint foot = getFootOfPerpendicular(point.x, point.y, a, b, c); //得到距离 double distance = getDistance(point.x, point.y, foot.x, foot.y); /** * 判断垂足是否在线段上 */ if (foot.x >= rangeX[0] && foot.x <= rangeX[1] && foot.y >= rangeY[0] && foot.y <= rangeY[1]) { /** * 1.如果在线段上则记录值 * 2.跟minDistance比较,如果小于目前值则进行替换(若是初始值(-1)也进行替换) */ if ((minDistance == -1) || (minDistance != -1 && distance < minDistance)) { minDistance = distance; } } else { //计算点距离 double startPointDistance = getDistance(point.x, point.y, points[i].x, points[i].y); double endPointDistance = getDistance(point.x, point.y, points[i+1].x, points[i + 1].y); distance = (startPointDistance <= endPointDistance ? startPointDistance : endPointDistance); if (minDistance==-1||minDistance > distance) { minDistance = distance; } } } /** * 1.看是否minDistance是否是初始值, * 2.如果是初始值则再次计算点到首末两点的距离,若均大于allowRange则认为偏离航线 * 3.如果不是初始值则判断最小值是否小于allowRange */ if (minDistance == -1) { /** * 计算点到首末两点的距离 */ MapPoint startPoint = points[0]; MapPoint endPoint = points[points.Count - 1]; double startPointDistance = getDistance(point.x, point.y, startPoint.x, startPoint.y); double endPointDistance = getDistance(point.x, point.y, endPoint.x, endPoint.y); double distance = (startPointDistance <= endPointDistance ? startPointDistance : endPointDistance); minDistance = distance; } if (minDistance <= allowRange) { return true; } else { return false; } } /** * Description 求点到直线的垂足 * * @param x1 * 点横坐标 * @param y1 * 点纵坐标 * @param A * 直线方程一般式系数A * @param B * 直线方程一般式系数B * @param C * 直线方程一般式系数C * @return 垂足点 */ private static MapPoint getFootOfPerpendicular(double x1, double y1, double A, double B, double C) { if (A * A + B * B < 1e-13) return null; if (Math.Abs(A * x1 + B * y1 + C) < 1e-13) { return new MapPoint(x1, y1); } else { double newX = (B * B * x1 - A * B * y1 - A * C) / (A * A + B * B); double newY = (-A * B * x1 + A * A * y1 - B * C) / (A * A + B * B); return new MapPoint(newX, newY); } } /** * 根据经纬度,计算两点间的距离 * * @param longitude1 第一个点的经度 * @param latitude1 第一个点的纬度 * @param longitude2 第二个点的经度 * @param latitude2 第二个点的纬度 * @return 返回距离 单位 米 */ public static double getDistance(double longitude1, double latitude1, double longitude2, double latitude2) { // 纬度 double lat1 = ToRadians(latitude1); double lat2 = ToRadians(latitude2); // 经度 double lng1 = ToRadians(longitude1); double lng2 = ToRadians(longitude2); // 纬度之差 double a = lat1 - lat2; // 经度之差 double b = lng1 - lng2; // 计算两点距离的公式 double s = 2 * Math.Asin(Math.Sqrt(Math.Pow(Math.Sin(a / 2), 2) + Math.Cos(lat1) * Math.Cos(lat2) * Math.Pow(Math.Sin(b / 2), 2))); // 弧长乘地球半径, 返回单位: 千米 s = s * EARTH_RADIUS; return s; } /// <summary> /// 角度转弧度 /// </summary> /// <param name="degrees"></param> /// <returns></returns> public static double ToRadians(double degrees) { double radians = (Math.PI / 180) * degrees; return (radians); } } /// <summary> /// 地图点 /// </summary> public class MapPoint { /// <summary> /// 经度 /// </summary> public double x { get; set; } /// <summary> /// 纬度 /// </summary> public double y { get; set; } /// <summary> /// 地图点 /// </summary> /// <param name="alon">经度</param> /// <param name="alat">纬度</param> public MapPoint(double alon, double alat) { this.x = alon; this.y = alat; } public MapPoint(object alon, object alat) { this.x = Convert.ToDouble(alon); this.y = Convert.ToDouble(alat); } } }
测试:
[TestMethod()] public void IsPtInPolyTest() { //判断是否在多边形内 //MapPoint[] ps = new MapPoint[] { new MapPoint(120.2043, 30.2795), new MapPoint(120.2030, 30.2511), new MapPoint(120.1810, 30.2543), new MapPoint(120.1798, 30.2781), new MapPoint(120.1926, 30.2752) }; //MapPoint n1 = new MapPoint(120.1936, 30.2846); //MapPoint n2 = new MapPoint(120.1823, 30.2863); //MapPoint n3 = new MapPoint(120.2189, 30.2712); //MapPoint y1 = new MapPoint(120.1902, 30.2712); //MapPoint y2 = new MapPoint(120.1866, 30.2672); //MapPoint y4 = new MapPoint(120.1869, 30.2718); //bool aa = MapHelper.IsPtInPoly(120.2043, 30.2795, ps); bool aa = false; MapPoint[] ps1 = new MapPoint[] { new MapPoint(30.59119542666749, 104.050569540718), new MapPoint(30.588443179070143, 104.05052662537724), new MapPoint(30.588664839488466, 104.0601933058818), new MapPoint(30.5905027542628, 104.0577471314589), new MapPoint(30.59129701820391, 104.05441046371537) }; MapPoint n22 = new MapPoint(30.589948612774652, 104.05258656173338); MapPoint n221 = new MapPoint(30.59234063418448, 104.05193210278689); //在区域内 aa = MapHelper.IsPtInPoly(new MapPoint(30.589948612774652, 104.05258656173338), ps1); //不在区域内 aa = MapHelper.IsPtInPoly(new MapPoint(30.59234063418448, 104.05193210278689), ps1); }
[TestMethod()] public void pointToPintLineTest() { //判断是否偏离线路 MapPoint[] ps1 = new MapPoint[] { new MapPoint(30.585548284620927, 104.05539820326156), new MapPoint(30.58535432554657, 104.05175039929757), new MapPoint(30.585151129909544, 104.04671857559433), new MapPoint(30.58310991374495, 104.04661128724243), new MapPoint(30.581114837410443, 104.046643473748), //new MapPoint(30.58206815224024, 104.04642201916893), //new MapPoint(30.579241379387206, 104.04642205588303), //new MapPoint(30.576698720819568, 104.04636078755512), }; //在线上 bool a= MapHelper.PointToPintLine(new MapPoint(30.58398736487427, 104.04660055840725), ps1.ToList(), 10); //不在 a = MapHelper.PointToPintLine(new MapPoint(30.58228683361441, 104.05176744705074), ps1.ToList(), 10); }