车载终端项目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、多边形区域:由一系列点所围成的闭合区域

  结合上面的数据结构,可以很容易在平面几何中得到相应的比较算法,然而,我们采集到的是大地坐标,即经纬度,不可以直接运用公式求解,所以要先将大地坐标系转换到平面坐标系,可采用高斯投影公式来转换。下面贴上项目使用的工具类,里面都是一些工具函数:

View Code
  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 }

 

  

posted @ 2012-07-14 21:44  我爱班花  阅读(976)  评论(0编辑  收藏  举报