车载终端项目GPS模块算法详述
项目系统使用WINCE6.0,应用层语言采用C#。
项目中GPS模块的工作过程为:读取串口中的GPS数据,解析封装到类中,然后进行数据库的保存操作,上传到后台,并进行位置和限制路径以及限制区域之间关系的判断。将判断结果进行报警处理。其中限制路径和区域的判断是模块中的重点。
对于其中算法,我分为两大部分:1是比较策略,2是调度策略。比较策略是指具体的比较算法的复杂度,比如,如何判断点是否在多边形区域内;而调度策略是指如何调度使每一次判断的比较总次数最少。比如:当前位置为(121,31),而所有的限制区域和限制路径所在范围能达到的最大经度都小于121,并且此时方向为正东,即远离所有的限制区域和路径,则最佳的调度调度策略会使比较的次数为0。当然,这种调度策略也会带来一定的复杂度,所以当限制路径和区域个数较少时,可以不予考虑。
下面将着重介绍计较策略,在介绍之前,先描述一个限制路径和限制区域的数据结构。
1、限制路径
限制路径是由一系列的拐点组成,拐点为经纬度坐标,相邻的两个拐点组成一条路段,路段有宽度属性。所以,路段矩形为相邻两拐点和路宽所围成的矩形。路段空间为 过相邻两拐点分别做垂直与两点连线的垂线所围成的无穷大区域。
点与路径之间的位置关系为:
1、点在所有的路段空间外,则称为:点在路径之外;
2、点在某个路段(如A)矩形上,称为:点在路径上;
3、点在某个路段(如A)空间内,且不在路段空间上,称为:点偏移路段(A);
点与路径之间的动作关系为:
1、点进入第一个路段空间,称为:点进入路径;
2、点退出最后一个路段空间,称为:点离开路径;
2、限制区域
限制区域由3种区域组成:圆形区域、矩形区域和多边形区域。(若不做说明,坐标均为经纬度)
a、圆形区域:由原点和半径R来定义
b、矩形区域:由左上点和右下点定义
c、多边形区域:由一系列点所围成的闭合区域
结合上面的数据结构,可以很容易在平面几何中得到相应的比较算法,然而,我们采集到的是大地坐标,即经纬度,不可以直接运用公式求解,所以要先将大地坐标系转换到平面坐标系,可采用高斯投影公式来转换。下面贴上项目使用的工具类,里面都是一些工具函数:
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 using System.Data; 5 using MotorTerminal.Data; 6 7 namespace MotorTerminal.GPS 8 { 9 class Utils 10 { 11 public const double EARTH_RADIUS = 6378.137; //地球半径 12 13 //根据角度求弧度 14 public static double rad(double d) 15 { 16 return d * Math.PI / 180.0; 17 } 18 19 //求点集中的最大x和y 20 public static CartesianPoint Max(IList<GPSLocation> list) 21 { 22 CartesianPoint result = new CartesianPoint(); 23 24 foreach (GPSLocation location in list) 25 { 26 result.x = result.x > location.Longitude ? result.x : location.Longitude; 27 result.y = result.y > location.Latitude ? result.y : location.Latitude; 28 } 29 30 return result; 31 } 32 33 //求点集中的最小x和y 34 public static CartesianPoint Min(IList<GPSLocation> list) 35 { 36 CartesianPoint result = new CartesianPoint(999, 999); 37 38 foreach (GPSLocation location in list) 39 { 40 result.x = result.x < location.Longitude ? result.x : location.Longitude; 41 result.y = result.y < location.Latitude ? result.y : location.Latitude; 42 } 43 44 return result; 45 } 46 47 //同经度上指定距离的纬度差,返回值单位为:度 48 public static double DistanceToDegreeByLon(int distance) 49 { 50 return (distance * 180) / (Math.PI * EARTH_RADIUS * 1000); 51 } 52 53 //同纬度上的指定距离的经度差,返回值单位为:度 54 public static double DistanceToDegreeByLat(int distance, double latitude) 55 { 56 double result = 2 * Math.Asin((Math.Sin(distance / (2 * EARTH_RADIUS * 1000))) / Math.Cos(rad(latitude))); 57 result = result * 180 / Math.PI; 58 return result; 59 } 60 61 //射线法判断点是否在多边形区域内 62 public static bool PointInFences(CartesianPoint pnt1, IList<CartesianPoint> fencePnts) 63 { 64 int j = 0, cnt = 0; 65 for (int i = 0; i < fencePnts.Count; i++) 66 { 67 j = (i == fencePnts.Count - 1) ? 0 : j + 1; 68 if ((fencePnts[i].y != fencePnts[j].y) && (((pnt1.y >= fencePnts[i].y) && (pnt1.y < fencePnts[j].y)) 69 || ((pnt1.y >= fencePnts[j].y) && (pnt1.y < fencePnts[i].y))) 70 && (pnt1.x < (fencePnts[j].x - fencePnts[i].x) * (pnt1.y - fencePnts[i].y) / (fencePnts[j].y - fencePnts[i].y) + fencePnts[i].x)) 71 { 72 cnt++; 73 } 74 } 75 return (cnt % 2 > 0) ? true : false; 76 } 77 78 /// <summary> 79 /// 获得地球上两点的距离,公式由弧长公式和余弦定理推导而出。 80 /// </summary> 81 /// <param name="lat1">起点的纬度</param> 82 /// <param name="lng1">起点的经度</param> 83 /// <param name="lat2">终点的纬度</param> 84 /// <param name="lng2">终点的经度</param> 85 /// <returns>返回距离,单位为:米,3位精度</returns> 86 public static double GetDistance(double lat1, double lng1, double lat2, double lng2) 87 { 88 double radLat1 = rad(lat1); 89 double radLat2 = rad(lat2); 90 double a = radLat1 - radLat2; 91 double b = rad(lng1) - rad(lng2); 92 double s = 2 * Math.Asin(Math.Sqrt(Math.Pow(Math.Sin(a / 2), 2) + 93 Math.Cos(radLat1) * Math.Cos(radLat2) * Math.Pow(Math.Sin(b / 2), 2))); 94 s = s * EARTH_RADIUS; 95 s = Math.Round(s * 1000, 3); 96 return s; 97 } 98 99 /// <summary> 100 /// 获得地球上两点的距离 101 /// </summary> 102 /// <param name="lat1">起点的纬度</param> 103 /// <param name="lng1">起点的经度</param> 104 /// <param name="lat2">终点的纬度</param> 105 /// <param name="lng2">终点的经度</param> 106 /// <returns>返回距离,单位为:米</returns> 107 public static int GetDistanceInt(double lat1, double lng1, double lat2, double lng2) 108 { 109 int result = -1; 110 double s = GetDistance(lat1, lng1, lat2, lng2); 111 try 112 { 113 result = Convert.ToInt32(s); 114 } 115 catch (System.Exception ex) 116 { 117 Console.WriteLine("错误函数为:GPS模块【GetDistanceInt】,异常为:{0}", ex.Message); 118 } 119 120 return result; 121 } 122 123 /// <summary> 124 /// 大地坐标系(经纬度坐标)转换到笛卡尔坐标系,使用高斯投影公式求解 125 /// </summary> 126 /// <param name="longitude">要转换点的经度</param> 127 /// <param name="latitude">要转换点的纬度</param> 128 /// <param name="type">投影基准面类型</param> 129 /// <returns>笛卡尔坐标点</returns> 130 public static CartesianPoint GeodeticToCartesian(double longitude, double latitude, CoordinateType type) 131 132 { 133 double L, B;//大地经度,纬度 134 double l, L0;//投影带中央线起算的经差,投影带中央子午线 135 double X, Y;//直角坐标 136 double f, e2, a, b, e12;////参考椭球体扁率,第一偏心率,长半轴,短半轴,第二偏心率 137 double t;//保存tan B 138 double m;//保存lcosB 139 double q2;//卯酉圈的分量 的平方 140 double M, N;//子午,卯酉圈曲半径 141 double S;//子午线弧长 142 double A0, B0, C0, D0, E0; 143 double n;//投影带号 144 double a_1_e2;//a*(1-e*e)的值 145 int zonewide = 3;//3度带比较准,可以用6度带 146 147 CoordinateType base_type = type; //投影基准面类型:北京54基准面为54,西安80基准面为80,WGS84基准面 148 // const double PI = 3.1415926535897932; 149 //----------初始化已知参数----------- 150 L = longitude;//经度 151 B = latitude;//纬度 152 if (CoordinateType.WGS84 == base_type) 153 { 154 a = 6378137; 155 b = 6356752.3142; 156 f = 1 / 298.257223563; 157 e2 = 0.0066943799013;//--第一偏心率 158 e12 = 0.00673949674227;//--第二偏心率 159 } 160 else if (CoordinateType.BJ54 == base_type) 161 { 162 a = 6378245; 163 b = 6356863.0187730473; 164 f = 1 / 298.3; 165 e2 = 0.006693421622966;//--第一偏心率 166 e12 = 0.006738525414683;//--第二偏心率 167 } 168 else if (CoordinateType.XIAN80 == base_type) 169 { 170 a = 6378140; 171 b = 6356755.2881575287; 172 f = 1 / 298.257; 173 e2 = 0.006694384999588;//--第一偏心率 174 e12 = 0.006739501819473;//--第二偏心率 175 } 176 else 177 { 178 a = 6378137; 179 b = 6356752.3142; 180 f = 1 / 298.257223563; 181 e2 = 0.0066943799013;//--第一偏心率 182 e12 = 0.00673949674227;//--第二偏心率 183 } 184 //---基本计算公式 a 长半轴,b 短半轴-------- 185 //f = (a-b)/a;//--椭球体扁率 186 //e2 = (a*a-b*b)/(a*a);//--第一偏心率 187 //e12 = (a*a-b*b)/(b*b);//--第二偏心率 188 a_1_e2 = a * (1 - e2); 189 //-----简化计算参数 190 if (zonewide == 6) 191 { 192 n = (int)(L / 6) + 1; 193 L0 = n * zonewide - 3; 194 } 195 else 196 { 197 n = (int)((L - 1.5) / 3) + 1; 198 L0 = n * 3; 199 } 200 201 //---转化为弧度,sin,cos 等运算都是以弧度为基础的 202 L0 = L0 * Math.PI / 180; 203 L = L * Math.PI / 180; 204 B = B * Math.PI / 180; 205 l = L - L0; 206 t = Math.Tan(B); 207 q2 = e12 * (Math.Cos(B) * Math.Cos(B)); 208 N = a / Math.Sqrt(1 - e2 * Math.Sin(B) * Math.Sin(B));////卯酉圈的曲率半径 209 m = l * Math.Cos(B); 210 //---子午线弧长 211 //--以下算法是对原始公式的多项式进行了合并处理,保存计算更准 212 double m0 = a_1_e2; 213 double m2 = 3.0 * e2 * m0 / 2.0; 214 double m4 = 5.0 * e2 * m2 / 4.0; 215 double m6 = 7.0 * e2 * m4 / 6.0; 216 double m8 = 9.0 * e2 * m6 / 8.0; 217 double a0 = m0 + m2 / 2.0 + 3.0 * m4 / 8.0 + 5.0 * m6 / 16.0 + 35.0 * m8 / 128; 218 double a2 = m2 / 2.0 + m4 / 2.0 + 15.0 * m6 / 32.0 + 7.0 * m8 / 16.0; 219 double a4 = m4 / 8.0 + 3.0 * m6 / 16.0 + 7.0 * m8 / 32.0; 220 double a6 = m6 / 32.0 + m8 / 16.0; 221 double a8 = m8 / 128.0; 222 A0 = a0; 223 B0 = a2; 224 C0 = a4; 225 D0 = a6; 226 E0 = a8; 227 S = (A0 * B - B0 * Math.Sin(2.0 * B) / 2.0 + C0 * Math.Sin(4.0 * B) / 4.0 - D0 * Math.Sin(6.0 * B) / 6.0 + E0 * Math.Sin(8.0 * B) / 8.0); 228 229 //----高斯投影公式------- 230 X = S + N * t * ((0.5 + ((5.0 - t * t + 9 * q2 + 4 * q2 * q2) / 24 + (61.0 - 58.0 * t * t + Math.Pow(t, 4)) * m * m / 720.0) * m * m) * m * m);//pow(t,4)为t的4次方 231 Y = N * ((1 + ((1 - t * t + q2) / 6.0 + (5.0 - 18.0 * t * t + Math.Pow(t, 4) + 14 * q2 - 58 * q2 * t * t) * m * m / 120.0) * m * m) * m); 232 //----根据中国国情坐标纵轴西移500公里当作起始轴 233 Y = 1000000 * n + 500000 + Y; 234 235 CartesianPoint point = new CartesianPoint(Y, X); 236 237 return point; 238 } 239 240 /// <summary> 241 /// 大地坐标系(经纬度坐标)转换到笛卡尔坐标系,投影基准面默认为:WGS84。使用高斯投影公式求解 242 /// </summary> 243 /// <param name="longitude">要转换点的经度</param> 244 /// <param name="latitude">要转换点的纬度</param> 245 /// <returns>笛卡尔坐标点</returns> 246 public static CartesianPoint GeodeticToCartesian(double longitude, double latitude) 247 { 248 return GeodeticToCartesian(longitude, latitude, CoordinateType.WGS84); 249 } 250 251 //将大地坐标点集(经纬度)转换成笛卡尔坐标点集 252 public static IList<CartesianPoint> GeodeticToCartesian(IList<GPSLocation> points) 253 { 254 IList<CartesianPoint> result = new List<CartesianPoint>(); 255 256 for (int i = 0; i < points.Count; i++) 257 { 258 CartesianPoint temp = new CartesianPoint(); 259 temp = GeodeticToCartesian(points[i].Longitude, points[i].Latitude); 260 261 result.Add(temp); 262 } 263 264 return result; 265 } 266 267 /// <summary> 268 /// 计算两点之间的距离,直接用距离公式 269 /// </summary> 270 /// <param name="p1">起点的笛卡尔坐标</param> 271 /// <param name="p2">终点的笛卡尔坐标</param> 272 /// <returns>距离</returns> 273 public static double GetPointDistance(CartesianPoint p1, CartesianPoint p2) 274 { 275 return Math.Sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)); 276 } 277 278 /// <summary> 279 /// 计算点到线段的距离,注:此函数计算距离的条件为点在过线段两端点的垂线所围成的范围内。 280 /// </summary> 281 /// <param name="pointStart">线段起点</param> 282 /// <param name="pointEnd">线段终点</param> 283 /// <param name="point">要计算到线段距离的点</param> 284 /// <returns>距离</returns> 285 public static double DistancePointToLine(CartesianPoint pointStart, CartesianPoint pointEnd, CartesianPoint point) 286 { 287 double a, b, c; 288 a = GetPointDistance(pointEnd, point); 289 if (a <= 0.00001) 290 return 0.0; 291 b = GetPointDistance(pointStart, point); 292 if (b <= 0.00001) 293 return 0.0; 294 c = GetPointDistance(pointStart, pointEnd); 295 if (c <= 0.00001) 296 return a; 297 298 if (a * a > b * b + c * c) //点未进入线段区域 299 return -1; 300 if (b * b > a * a + c * c) //点已出线段区域 301 return -2; 302 303 double l = (a + b + c) / 2; //周长的一半 304 double s = Math.Sqrt(l * (l - a) * (l - b) * (l - c)); //海伦公式求面积,也可以用矢量求 305 return 2 * s / c; 306 } 307 308 /// <summary> 309 /// 计算点到线段的距离,注:此函数计算距离的条件为点在过线段两端点的垂线所围成的范围内。 310 /// </summary> 311 /// <param name="pointStart">线段起点</param> 312 /// <param name="pointEnd">线段终点</param> 313 /// <param name="point">要计算到线段距离的点</param> 314 /// <returns>距离</returns> 315 public static int DistancePointToLineInt(CartesianPoint pointStart, CartesianPoint pointEnd, CartesianPoint point) 316 { 317 double result = DistancePointToLine(pointStart, pointEnd, point); 318 319 return Convert.ToInt32(result); 320 } 321 } 322 323 #region 内部使用的封装结构 324 325 /// <summary> 326 /// 笛卡尔点,即平面坐标系上的点 327 /// </summary> 328 struct CartesianPoint 329 { 330 public CartesianPoint(double x, double y) 331 { 332 this.x = x; 333 this.y = y; 334 } 335 public double x; 336 public double y; 337 } 338 339 /// <summary> 340 /// 圆形区域的封装结构,用来存储转换后的圆形笛卡尔坐标和外包矩形的阀值 341 /// </summary> 342 struct RoundCapsule 343 { 344 public IRoundedArea area; //圆形区域 345 public CartesianPoint centerPoint; //圆心(笛卡尔坐标) 346 public CartesianPoint max; //外包矩形的最大x和y,即右上点 347 public CartesianPoint min; //外包矩形的最小x和y,即左下点 348 } 349 350 /// <summary> 351 /// 矩形区域的封装结构,用来存储转换后的左上点和右下点的笛卡尔坐标,此2点即可作为阀值 352 /// </summary> 353 struct RectCapsule 354 { 355 public IRectangularArea area; //矩形区域 356 public CartesianPoint leftUpPoint; //左上点 357 public CartesianPoint rightDownPoint; //右下点 358 } 359 360 /// <summary> 361 /// 多边形区域的封装结构,用来存储转换后的各点的笛卡尔坐标和外包矩形的阀值 362 /// </summary> 363 struct PolygonCapsule 364 { 365 public IMultilateralArea area; //多边形区域 366 public CartesianPoint max; //外包矩形的最大x和y,即右上点 367 public CartesianPoint min; //外包矩形的最小x和y,即左下点 368 public IList<CartesianPoint> points; //多边形区域的笛卡尔坐标点集 369 } 370 371 /// <summary> 372 /// 限制路径中路段的包装结构 373 /// </summary> 374 struct RoutePartCapsule 375 { 376 public IRoutePart routePart; //路段 377 public CartesianPoint startPoint; //起始点 378 public CartesianPoint endPoint; //终点 379 } 380 381 /// <summary> 382 /// 限制路径的封装结构,用来 383 /// </summary> 384 struct RouteCapsule 385 { 386 public IRestrictedRoute route; //限制路径 387 public IList<RoutePartCapsule> routeParts; //路段集合 388 } 389 390 //坐标系类型 391 enum CoordinateType 392 { 393 WGS84, //北京54基准面为 394 BJ54, //WGS84基准面为 395 XIAN80 //西安80基准面为 396 } 397 398 #endregion 399 }