关于判断某个是否在某个区域内或者在某条航线的周围_示例
最近的项目中有一个给定经纬度坐标判断此坐标是否在某个区域内的需求,找了一小时,又花了一个半小时理解和总结,才汇聚出这篇文章
编程小白第一篇,由于数学不太会,也有很多不懂得地方,希望勿喷,有什么不对的地方欢迎指出
namespace 经纬度判断
{
using System;
using System.Collections.Generic;
using System.Linq;
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
test1 t = new test1();
t.IsPtInPolyTest();
}
}
#region 经纬度判断
/* 此帮助类依据
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;
*/
#region 判断是否在航线上
public class test
{
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);
}
}
#endregion
#region 判断是否在地图内
public class test1
{
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;
bool aa1 = false;
bool bb = false;
bool bb1 = 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 y = new MapPoint(30.589948612774652, 104.05258656173338);
MapPoint n = new MapPoint(30.59234063418448, 104.05193210278689);
//在区域内
aa = MapHelper.IsPtInPoly(new MapPoint(30.589948612774652, 104.05258656173338), ps1);
//不在区域内
aa1 = MapHelper.IsPtInPoly(new MapPoint(30.59234063418448, 104.05193210278689), ps1);
//在区域内
bb = MapHelper.IsPtInPoly(y, ps1);
//不在区域内
bb1 = MapHelper.IsPtInPoly(n, ps1);
Console.WriteLine(aa);
Console.WriteLine(aa1);
Console.WriteLine(bb);
Console.WriteLine(bb1);
}
}
#endregion
#region 判断某一点是否在航线上或者地图内
/// <summary>
/// 地图计算帮助类 逍遥子
/// https://www.cnblogs.com/piaoxuewuming/p/16317390.html
/// </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;//获取当前点位的经x纬y度
int iSum, iCount, iIndex;
double dLon1 = 0, dLon2 = 0, dLat1 = 0, dLat2 = 0, dLon;//声明变量存储两两连线的开始点位经纬度结束点位经纬度
if (Points.Length < 3)//如果当前多边形集合点位坐标小于3个就直接返回false,至少三个点位才能够组成多边形
{
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)))
{
//返回双精度浮点数的绝对值,返回结果:A double-precision floating-point number, x, such that 0 <= x <=double.MaxValue.浮点数一种双精度浮点数,x,使0 <= x <=双倍。
if (Math.Abs(dLat1 - dLat2) > 0)//判断两个纬度之间是否重合
{
//获取A点向左射线与边的交点的x坐标:
dLon = dLon1 - ((dLon1 - dLon2) * (dLat1 - ALat)) / (dLat1 - dLat2);
//如果交点在A点左侧,则射线与边的全部交点数加一:比较烧脑,大概就是当点位位于图形之中的时候会有至少1个A点向左射线与边的交点的x坐标在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);
}
}
#endregion
#endregion
}

浙公网安备 33010602011771号