using System; namespace Plane.Geography { /// /// 提供地理相关计算的方法。 /// public static class GeographyUtils { /// /// 米数转换为纬度跨度时需要乘的常数。 /// public const double METERS_TO_LAT_SPAN = 1 / (GLOBE_CIRCUMFERENCE / 360); /// /// 角度转换为弧度时需要乘的常数。 /// private const double DEG_TO_RAD = Math.PI / 180; /// /// 地球的半径。 /// private const double EARTH_RADIUS = 6371000; /// /// 当前星球的周长。 /// private const double GLOBE_CIRCUMFERENCE = GLOBE_RADIUS * 2 * Math.PI; /// /// 当前星球的半径。 /// private const double GLOBE_RADIUS = EARTH_RADIUS; /// /// 计算从 (lat1, lng1) 到 (lat2, lng2) 的方向,单位为弧度。 /// /// 第一个点的纬度。 /// 第一个点的经度。 /// 第二个点的纬度。 /// 第二个点的经度。 /// 点 1 到点 2 的方向。 public static double CalcDirection2D(double lat1, double lng1, double lat2, double lng2) { return Math.Atan2((lng2 - lng1) / CalcMetersToLngSpan(lat1), (lat2 - lat1) / METERS_TO_LAT_SPAN).NormalizeDirectionRad(); } /// /// 计算空间中两点间的距离,单位为米。 /// /// 点 1。 /// 点 2。 /// 空间中两点间的距离,单位为米。 public static double CalcDistance(ILocation l1, ILocation l2) { return CalcDistance(l1.Latitude, l1.Longitude, l1.Altitude, l2.Latitude, l2.Longitude, l2.Altitude); } /// /// 计算空间中两点间的距离,单位为米。 /// /// 纬度 1。 /// 经度 1。 /// 纬度 2。 /// 经度 2。 /// 空间中两点间的距离,单位为米。 public static double CalcDistance_simple(double lat1, double lng1, double lat2, double lng2) { double dx = lng2 - lng1; double dy = lat2 - lat1; double b = (lat1 + lat2) * 0.5; double Lx = (0.05 * b * b * b - 19.16 * b * b + 47.13 * b + 110966) * dx; double Ly = (17 * b + 110352) * dy; return Math.Sqrt(Lx * Lx + Ly * Ly); } /// /// 计算空间中两点间的距离,单位为米。 /// /// 纬度 1。 /// 经度 1。 /// 高度 1。 /// 纬度 2。 /// 经度 2。 /// 高度 2。 /// 空间中两点间的距离,单位为米。 public static double CalcDistance_simple(double lat1, double lng1, double alt1, double lat2, double lng2,double alt2) { double dx = lng2 - lng1; double dy = lat2 - lat1; double b = (lat1 + lat2) * 0.5; double Lx = (0.05 * b * b * b - 19.16 * b * b + 47.13 * b + 110966) * dx; double Ly = (17 * b + 110352) * dy; double d= Math.Sqrt(Lx * Lx + Ly * Ly); return Math.Sqrt(Math.Pow((alt2 - alt1), 2) + Math.Pow(d, 2)); } /// /// 计算空间中两点之间的距离,单位为米。 /// /// 纬度 1。 /// 经度 1。 /// 高度 1。 /// 纬度 2。 /// 经度 2。 /// 高度 2。 /// 空间中两点之间的距离,单位为米。 public static double CalcDistance(double lat1, double lng1, double alt1, double lat2, double lng2, double alt2) { // generally used geo measurement function // var R = 6378.137; // Radius of earth in KM var dLat = (lat2 - lat1) * Math.PI / 180; var dLon = (lng2 - lng1) * Math.PI / 180; var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) + Math.Cos(lat1 * Math.PI / 180) * Math.Cos(lat2 * Math.PI / 180) * Math.Sin(dLon / 2) * Math.Sin(dLon / 2); var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a)); var d = GLOBE_RADIUS * c; return Math.Sqrt(Math.Pow((alt2 - alt1), 2) + Math.Pow(d, 2)); } /// /// 计算二维平面上两个位置之间的距离,单位为米。 /// /// 位置 1。 /// 位置 2。 /// 空间中两点间的距离。 public static double CalcDistance2D(ILocation2D l1, ILocation2D l2) => CalcDistance2D(l1.Latitude, l1.Longitude, l2.Latitude, l2.Longitude); /// /// 计算二维平面上两个位置之间的距离,单位为米。 /// /// 纬度 1。 /// 经度 1。 /// 纬度 2。 /// 经度 2。 /// 计算二维平面上两个位置之间的距离,单位为米。 public static double CalcDistance2D(double lat1, double lng1, double lat2, double lng2) => CalcDistance(lat1, lng1, 0, lat2, lng2, 0); /// /// 计算在水平面上从指定点往指定方向移动指定距离后所在的点。 /// /// 出发点的纬度。 /// 出发点的经度。 /// 移动方向,单位为角度。 /// 移动距离。 /// 从指定点往指定方向移动指定距离后所在的点。 public static Tuple CalcLatLngSomeMetersAway2D(double lat1, double lng1, float directionDegrees, float distance) { var direction = directionDegrees.DegToRad(); return Tuple.Create( lat1 + distance * Math.Cos(direction) * METERS_TO_LAT_SPAN, lng1 + distance * Math.Sin(direction) * CalcMetersToLngSpan(lat1) ); } /// /// 计算在水平面上从指定点往指定方向移动指定距离后所在的点。 /// /// 出发点。 /// 移动方向,单位为角度。 /// 移动距离。 /// 从指定点往指定方向移动指定距离后所在的点。 public static ILocation2D CalcLatLngSomeMetersAway2D(ILocation2D loc1, float directionDegrees, float distance) { var direction = directionDegrees.DegToRad(); return new PLLocation( loc1.Latitude + distance * Math.Cos(direction) * METERS_TO_LAT_SPAN, loc1.Longitude + distance * Math.Sin(direction) * CalcMetersToLngSpan(loc1.Latitude), 0 ); } /// /// 计算在指定纬度上,米数转换为经度跨度时需要乘的值。 /// /// 纬度。 /// 在指定纬度上,米数转换为经度跨度时需要乘的值。 public static double CalcMetersToLngSpan(double latitude) { return METERS_TO_LAT_SPAN / Math.Cos(latitude * DEG_TO_RAD); } /// /// 检测同一水平面上(忽略高度)的两条线段是否相交。 /// /// 线段 A 端点 1。 /// 线段 A 端点 2。 /// 线段 B 端点 1。 /// 线段 B 端点 2。 /// 若相交,返回 true;否则返回 false。 public static bool CheckCrossing2D(ILocation2D l1, ILocation2D l2, ILocation2D l3, ILocation2D l4) => CheckCrossing2D(l1.Latitude, l1.Longitude, l2.Latitude, l2.Longitude, l3.Latitude, l3.Longitude, l4.Latitude, l4.Longitude); /// /// 检测同一水平面上的两条线段是否相交。 /// /// 线段 A 端点 1 的纬度。 /// 线段 A 端点 1 的经度。 /// 线段 A 端点 2 的纬度。 /// 线段 A 端点 2 的经度。 /// 线段 B 端点 1 的纬度。 /// 线段 B 端点 1 的经度。 /// 线段 B 端点 2 的纬度。 /// 线段 B 端点 2 的经度。 /// 若相交,返回 true;否则返回 false。 public static bool CheckCrossing2D(double latA1, double lngA1, double latA2, double lngA2, double latB1, double lngB1, double latB2, double lngB2) { // 线段 A(x1, y1) - B(x2, y2), 所在直线 L1 方程为 F(x, y) = 0; // 线段 C(x3, y3) - D(x4, y4), 所在直线 L2 方程为 G(x, y) = 0; // 思路: // (即问题的充要条件) // (点 A 与点 B 在直线 L2 两侧) AND (点 C 与点 D 在直线 L1 两侧); // 方法: // 如果点 P(Xp, Yp) 不在直线 a * x + b * y + c = 0 上, 则 a* Xp + b * Yp + c <> 0; // 如果用两个点的坐标代入同一直线方程 a * x + b * y + c 计算出的值异号, 则两点在直线两侧; // 解法: // (G(x1, y1) * G(x2, y2) < 0) AND (F(x3, y3) * F(x4, y4) < 0) // 斜截式 y = kx + b // 即 kx - y + b = 0 var k1 = (latA2 - latA1) / (lngA2 - lngA1); var b1 = latA1 - k1 * lngA1; var k2 = (latB2 - latB1) / (lngB2 - lngB1); var b2 = latB1 - k2 * lngB1; return k2 * lngA1 - latA1 + b2 > 0 != k2 * lngA2 - latA2 + b2 > 0 && k1 * lngB1 - latB1 + b1 > 0 != k1 * lngB2 - latB2 + b1 > 0; } /// /// 把角度转换为弧度。 /// /// 角度值。 /// 弧度值。 public static double DegToRad(this float value) { return value * DEG_TO_RAD; } /// /// 把角度转换为弧度。 /// /// 角度值。 /// 弧度值。 public static double DegToRad(this double value) { return value * DEG_TO_RAD; } /// /// 把角度转换为弧度。 /// /// 角度值。 /// 弧度值。 public static double DegToRad(this short value) { return value * DEG_TO_RAD; } /// /// 把指示方向的角度数限制在 [0, 360)。 /// /// 指示方向的角度数。 /// 等价的在标准范围内的角度。 public static float NormalizeDirection(this float value) { while (value < 0) { value += 360; } while (value >= 360) { value -= 360; } return value; } /// /// 把指示方向的角度值限制在 [0, 360)。 /// /// 指示方向的角度值。 /// 等价的在标准范围内的角度。 public static double NormalizeDirection(this double value) { while (value < 0) { value += 360; } while (value >= 360) { value -= 360; } return value; } /// /// 把指示方向的弧度值限制在 [0, 2PI)。 /// /// 指示方向的弧度值。 /// 等价的在标准范围内的弧度。 public static double NormalizeDirectionRad(this double value) { while (value < 0) { value += Math.PI * 2; } while (value >= Math.PI * 2) { value -= Math.PI * 2; } return value; } /// /// 把弧度转换为角度。 /// /// 弧度值。 /// 角度值。 public static double RadToDeg(this float value) { return value / DEG_TO_RAD; } /// /// 把弧度转换为角度。 /// /// 弧度值。 /// 角度值。 public static double RadToDeg(this double value) { return value / DEG_TO_RAD; } } }