GPS参数提取及轨迹重现
本项目分为两个版本:
1、基于百度地图API的轨迹快速重现,项目体验地址:GPS轨迹重现在线版
2、基于Leaflet的状态及轨迹重现,项目体验地址:GPS数据提取及轨迹重现
两者均采用在线地图,都需要Internet的支持
1、基于百度地图API的轨迹快速重现 1.1、功能简介
数据提取:提取出GPS文件中的坐标信息
坐标转换:原始坐标转化成百度坐标
轨迹描绘:根据提取出来的坐标信息将轨迹一次性描绘出来
支持地图的放大与缩小
1.2、效果展示
1.3、项目实现 1.3.1、准备
这里需要注意 的是,api是用JavaScript写的,所以需要懂一点前端(HTML+CSS+Javascript)的知识,不难。
首先,因为需要用到百度地图API,所以先要到官网申请一个ak,百度地图开放平台 ,具体操作可以参考百度地图API及使用 ,之后就可以在本地新建一个html文件,在官网上复制一个例程保存到改文件中并更改ak,然后就可以使用百度API了,写好代码后双击就可以在浏览器中看到运行效果。
1.3.2、实现
完成了上述准备之后就可以开始实现项目了,js代码可以写在html文件中的script标签中,也可以写在js文件中然后通过script标签引入,具体可参考源码。
1、加载地图,这里我用的是API3.0,百度地图JSAPI 3.0类参考 ,示例可以在这里找到 百度地图API SDK
1 2 3 4 5 var map = new BMap.Map("allmap" ); map.centerAndZoom(new BMap.Point(104.0820 , 30.63231 ), 17 ); map.enableScrollWheelZoom(true ); map.setMapType(BMAP_SATELLITE_MAP);
2、数据提取及轨迹描绘,数据提取是先读取文件并将内容保存到一个变量中,然后通过“$”符号进行分行处理,再根据‘,’进行分割处理并提取有效信息,详情见以下代码,关于GPS数据格式可参考GPS数据读取与处理 。
至于轨迹描绘,本来我是直接用百度地图API进行轨迹状态重现的,也就是逐点描绘,但后来因为一些问题(卡、慢)没有解决就改用了Leaflet。这里我采用的是百度地图API提供的加载海量点的方法来实现轨迹的快速描绘,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 var pointArr = [];var infoArr = [];function dealSelectFiles ( ) { var file = document .getElementById("selectFiles" ).files[0 ]; var reader = new FileReader(); reader.readAsText(file); reader.onload = function ( ) { var arr = this .result.split("$" ); var i = 1 ; flag_timer = 1 ; var high; while (arr[i]!=null && arr[i] != undefined && arr[i] != '' ){ if (arr[i].indexOf('GPGGA' ) != -1 ){ var info = arr[i].split("," ); high = info[9 ] +' m' ; } else if (arr[i].indexOf('GPRMC' ) != -1 && arr[i].indexOf('V' ) == -1 ){ var info_temp = { date :undefined , time :undefined , high :undefined , speed :undefined , direction :undefined }; var info = arr[i].split("," ); if (info[2 ] == 'A' ){ final_lat = info[3 ]; final_lon = info[5 ]; my_translate(info[3 ],info[5 ]); sped = parseFloat (info[7 ]) * 1.852 ; info_temp.high = high; info_temp.speed = sped; info_temp.time = (parseInt (info[1 ].slice(0 ,2 ))+8 )%24 +':' +info[1 ].slice(2 ,4 )+':' +info[1 ].slice(4 ,6 ); info_temp.date = info[9 ].slice(4 ,6 )+'年' +info[9 ].slice(2 ,4 )+'月' +info[9 ].slice(0 ,2 )+'日' ; info_temp.direction = info[8 ]+'°' ; infoArr.push(info_temp); } } i++; } alert('数据处理完成' ); var index = 0 ; var len = pointArr.length; var points = []; while (index<len){ var temp = new BMap.Point(pointArr[index].lon,pointArr[index].lat); points.push(temp); index++; } var options = { size : BMAP_POINT_SIZE_SMALLER, shape : BMAP_POINT_SHAPE_CIRCLE, color : '#0000FF' } var pointCollection = new BMap.PointCollection(points, options); map.addOverlay(pointCollection); map.addOverlay(new BMap.Marker(new BMap.Point(pointArr[0 ].lon,pointArr[0 ].lat),{icon : startIcon})); map.addOverlay(new BMap.Marker(new BMap.Point(pointArr[len-1 ].lon,pointArr[len-1 ].lat),{icon : finalIcon})); } } function my_translate (lat,lon ) { var d = lon.slice(0 ,3 ); var m = lon.slice(3 ,10 ); m = m/60 ; lon = parseFloat (d) + parseFloat (m); d = lat.slice(0 ,2 ); m = lat.slice(2 ,9 ); m = m/60 ; lat = parseFloat (d) + parseFloat (m); var t1 = wgs2gcj(lat, lon); var t2 = gcj2bd(t1[0 ],t1[1 ]); var point_temp = { lat : t2[0 ], lon : t2[1 ], }; pointArr.push(point_temp); }
3、坐标转换,因为百度坐标是在原始坐标上进行了加密处理,所以不能直接使用用原始坐标。百度提供的转换方法是在线转换,这种方法使用次数有限制并且转换较慢,所以我在Github上找到了一种离线的转换方式,经测试,很准确。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 var pi = 3.14159265358979324 ;var a = 6378245.0 ;var ee = 0.00669342162296594323 ;var x_pi = 3.14159265358979324 * 3000.0 / 180.0 ;function wgs2bd (lat, lon ) { _wgs2gcj = wgs2gcj(lat, lon); _gcj2bd = gcj2bd(_wgs2gcj[0 ], _wgs2gcj[1 ]); return _gcj2bd; } function gcj2bd (lat, lon ) { x = lon, y = lat; z = Math .sqrt(x * x + y * y) + 0.00002 * Math .sin(y * x_pi); theta = Math .atan2(y, x) + 0.000003 * Math .cos(x * x_pi); bd_lon = z * Math .cos(theta) + 0.0065 ; bd_lat = z * Math .sin(theta) + 0.006 ; return [ bd_lat, bd_lon ]; } function bd2gcj (lat, lon ) { x = lon - 0.0065 , y = lat - 0.006 ; z = Math .sqrt(x * x + y * y) - 0.00002 * Math .sin(y * x_pi); theta = Math .atan2(y, x) - 0.000003 * Math .cos(x * x_pi); gg_lon = z * Math .cos(theta); gg_lat = z * Math .sin(theta); return [ gg_lat, gg_lon ]; } function wgs2gcj (lat, lon ) { dLat = transformLat(lon - 105.0 , lat - 35.0 ); dLon = transformLon(lon - 105.0 , lat - 35.0 ); radLat = lat / 180.0 * pi; magic = Math .sin(radLat); magic = 1 - ee * magic * magic; sqrtMagic = Math .sqrt(magic); dLat = (dLat * 180.0 ) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi); dLon = (dLon * 180.0 ) / (a / sqrtMagic * Math .cos(radLat) * pi); mgLat = lat + dLat; mgLon = lon + dLon; return [ mgLat, mgLon ]; } function transformLat (lat, lon ) { ret = -100.0 + 2.0 * lat + 3.0 * lon + 0.2 * lon * lon + 0.1 * lat * lon + 0.2 * Math .sqrt(Math .abs(lat)); ret += (20.0 * Math .sin(6.0 * lat * pi) + 20.0 * Math .sin(2.0 * lat * pi)) * 2.0 / 3.0 ; ret += (20.0 * Math .sin(lon * pi) + 40.0 * Math .sin(lon / 3.0 * pi)) * 2.0 / 3.0 ; ret += (160.0 * Math .sin(lon / 12.0 * pi) + 320 * Math .sin(lon * pi / 30.0 )) * 2.0 / 3.0 ; return ret; } function transformLon (lat, lon ) { ret = 300.0 + lat + 2.0 * lon + 0.1 * lat * lat + 0.1 * lat * lon + 0.1 * Math .sqrt(Math .abs(lat)); ret += (20.0 * Math .sin(6.0 * lat * pi) + 20.0 * Math .sin(2.0 * lat * pi)) * 2.0 / 3.0 ; ret += (20.0 * Math .sin(lat * pi) + 40.0 * Math .sin(lat / 3.0 * pi)) * 2.0 / 3.0 ; ret += (150.0 * Math .sin(lat / 12.0 * pi) + 300.0 * Math .sin(lat / 30.0 * pi)) * 2.0 / 3.0 ; return ret; }
4、界面布局,这个就是通过编辑CSS代码来实现的,了解一下CSS就知道了,不难,不过值得一提的是我们调试CSS代码可以直接在浏览器的开发者工具里进行,调试好了再将代码复制到css文件中,这样方便一些。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 body , html ,#allmap {width : 100% ;height : 100% ;overflow : hidden;margin :0 ;font-family :"微软雅黑" ;}.info { z-index : 999 ; width : auto; padding : 10px ; margin-left : 10px ; position : fixed; top : 10px ; background-color : rgba (265 , 265 , 265 , 0.9 ); border-radius : 5px ; font-size : 14px ; color : #666 ; box-shadow : 0 2px 6px 0 rgba (27 , 142 , 236 , 0.5 ); } ul li { list-style : none; } .btn-wrap { z-index : 999 ; width : 226px ; position : fixed; bottom : 200px ; right : 10px ; padding : 10px ; border-radius : 5px ; background-color : rgba (265 , 265 , 265 , 0.9 ); box-shadow : 0 2px 6px 0 rgba (27 , 142 , 236 , 0.5 ); } .btn { width : 100px ; height : 30px ; float : left; background-color : #fff ; color : rgba (27 , 142 , 236 , 1 ); font-size : 14px ; border :1px solid rgba (27 , 142 , 236 , 1 ); border-radius : 5px ; margin : 0 5px 6px ; text-align : center; line-height : 30px ; } .btn :hover { background-color : rgba (27 , 142 , 236 , 0.8 ); color : #fff ; cursor : pointer; } .file_select { margin-top : 8px ; margin-bottom : 8px ; margin-left : 5px ; } .information { width : 200px ; height : 25px ; float : left; font-size : 14px ; margin-left : 5px ; } .txt { margin : 2px 10px 2px 10px ; } span ,textarea { vertical-align : middle; }
感兴趣的朋友可以直接通过百度地图API实现其它功能,源代码在文章最下方。
2、基于Leaflet的状态及轨迹重现 2.1、功能简介
数据提取:提取出GPS文件中的坐标、速度、高度等信息
坐标转换:原始坐标转化成其他坐标系
轨迹描绘:根据提取出来的信息将轨迹逐点描绘出来,并显示相应的状态信息
支持地图的放大与缩小
支持图层切换,MapBox提供的图层支持国外的高级别卫星地图
2.2、效果展示
2.3、项目实现
因为我之前使用百度地图API有一些问题没有解决,所以改用了Leaflet,有了之前使用百度地图API的经验,现在上手Leaflet就很容易,因为很多接口都差不多。
2.3.1、准备
首先,到Leaflet官方下载源码,Leaflet - a JavaScript library for interactive maps ,当然不下载直接在线引入也是可以的。然后根据官方说明操作就可以了,也可以参考别人写的教程LeafletJS - 教程_学习LeafletJS 。
2.3.2、实现
与百度地图API不同的是,Leaflet需要我们自己添加瓦片图层,这个选择就多了,例如高度地图、天地图…,可以在网上找。说到这里就不得不提一下我选择的MapBox图层,这个图层的清晰度比天地图等要高,而且包含全世界的高级别卫星地图,缺点就是服务器在国外,加载速度相对较慢。
1、初始化,一开始选择了三个切片图层,但因为高德的瓦片不是很全并且坐标系和另外两个有所不同,所以就没用了。天地图的切片图层可以到这里获取天地图API ;MapBox的图层可以到这里获取Mapbox Studio ,关于MapBox的操作可以参考Leaflet学习笔记【更新中】 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 var mapOptions = { center : [30.63231 , 104.0820 ], zoom : 16 } var map = new L.map('map' , mapOptions);var Gaode = new L.tileLayer('https://webst0{s}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}' , { maxZoom : 20 , maxNativeZoom : 20 , minZoom : 3 , attribution : "高德地图 AutoNavi.com" , subdomains : "1234" }); var Tiandi = L.tileLayer('http://t{s}.tianditu.gov.cn/img_w/wmts?tk=da144caca3dc9a894a921aa6c937ca71&SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}' , { subdomains : [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 ], }); var mapBox = L.tileLayer('https://api.mapbox.com/styles/v1/deven3433/ckvi4woqi0vy615qljjvthgn9/tiles/256/{z}/{x}/{y}@2x?access_token=pk.eyJ1IjoiZGV2ZW4zNDMzIiwiYSI6ImNrdmkzYjMxcTI2Y2Iydm55NmEwaWs2Z2wifQ.bMgbUERe8dqTan0PENXgBw' );var baseLayers = { "天地图" : Tiandi, "MapBox" : mapBox }; map.addLayer(Tiandi); L.control.layers(baseLayers, null ).addTo(map); var flag_timer = true ; document .onkeydown = function (event ) { var e = event || window .e; var keyCode = e.keyCode || e.which; switch (keyCode) { case 8 : flag_timer = false ; break ; case 32 : alert('暂停,点击确定继续' ); break ; } }
2、关键变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var r = 255 , g = 0 , b = 0 ; var pointColor = { radius : 3 , stroke : false , fillColor : 'rgb(' + r + ',' + g + ',' + b + ')' , fillOpacity : 1 , }; var flag = 0 ;var displaySpeed = 50 ;var MaxSpeed = 0 ;var last = 16 , current = 16 ;var pointArr = []; var infoArr = [];
3、按钮功能实现
1 2 3 4 5 6 7 8 9 function setSpeed ( ) { displaySpeed = document .getElementById('spedSet' ).value; } function ZoomIn ( ) { map.zoomIn(); } function ZoomOut ( ) { map.zoomOut(); }
4、自定义的一些接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 function stateDisplay (speed ) { if (speed < 10 ){ document .getElementById("state" ).src = "icon/foot.png" ; } else if (speed < 30 ){ document .getElementById("state" ).src = "icon/bike.png" ; } else if (speed < 120 ){ document .getElementById("state" ).src = "icon/car.png" ; } else if (speed < 200 ){ document .getElementById("state" ).src = "icon/train.png" ; } else if (speed < 500 ){ document .getElementById("state" ).src = "icon/high_speed.png" ; } else if (speed >= 500 ){ document .getElementById("state" ).src = "icon/plane.png" ; } } function my_translate (lat,lon ) //根据坐标信息描点 { var d = lon.slice(0 ,3 ); var m = lon.slice(3 ,10 ); m = m/60 ; lon = parseFloat (d) + parseFloat (m); d = lat.slice(0 ,2 ); m = lat.slice(2 ,9 ); m = m/60 ; lat = parseFloat (d) + parseFloat (m); var point_temp = { lat : lat, lon : lon, }; pointArr.push(point_temp); }
5、描点函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 var start = { icon : L.icon({ iconUrl : 'icon/start.png' , iconSize : [48 , 48 ], iconAnchor : [24 , 42 ], }) }; var final = { icon : L.icon({ iconUrl : 'icon/final.png' , iconSize : [48 , 48 ], iconAnchor : [24 , 42 ], }) }; var count = 0 ; function display (lat,lon,speed ) { stateDisplay(speed); g = parseInt (speed/MaxSpeed * 255 ); b = g; pointColor.fillColor = 'rgb(' +r+',' +g+',' +b+')' ; if (flag == 0 ){ new L.Marker([lat,lon],start).addTo(map); map.panTo([lat,lon]); flag = 1 ; } else if (flag == 1 ){ new L.circleMarker([lat,lon],pointColor).addTo(map); if (speed<150 ){ current = 16 ; }else if (speed<400 ){ current = 15 ; }else if (speed<600 ){ current = 14 ; }else if (speed>=600 ){ current = 13 ; } map.panTo([lat,lon],{ animate : false }); if (last != current){ count++; if (count > 10 ){ count = 0 ; last = current; map.setZoom(current); } } } else { new L.Marker([lat,lon],final).addTo(map); map.panTo([lat,lon]); flag = 0 ; } }
6、数据提取及轨迹重现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 function dealSelectFiles ( ) { var file = document .getElementById("selectFiles" ).files[0 ]; var reader = new FileReader(); reader.readAsText(file); reader.onload = function ( ) { var arr = this .result.split("$" ); var i = 1 ; var high,sped; flag_timer = true ; while (arr[i]!=null && arr[i] != undefined && arr[i] != '' ){ if (arr[i].indexOf('GPGGA' ) != -1 ){ var info = arr[i].split("," ); high = info[9 ]; } else if (arr[i].indexOf('GPRMC' ) != -1 && arr[i].indexOf('V' ) == -1 ){ var info_temp = { date :undefined , time :undefined , high :undefined , speed :undefined , direction :undefined }; var info = arr[i].split("," ); if (info[2 ] == 'A' ){ my_translate(info[3 ],info[5 ]); sped = parseFloat (info[7 ]) * 1.852 ; if (MaxSpeed < sped)MaxSpeed = sped; info_temp.high = high; info_temp.speed = sped; info_temp.time = (parseInt (info[1 ].slice(0 ,2 ))+8 )%24 +':' +info[1 ].slice(2 ,4 )+':' +info[1 ].slice(4 ,6 ); info_temp.date = info[9 ].slice(4 ,6 )+'年' +info[9 ].slice(2 ,4 )+'月' +info[9 ].slice(0 ,2 )+'日' ; info_temp.direction = info[8 ]+'°' ; infoArr.push(info_temp); } } i++; } alert('数据处理完成' ); document .getElementById("sped1" ).innerText = parseInt (MaxSpeed/4 ); document .getElementById("sped2" ).innerText = parseInt (MaxSpeed/2 ); document .getElementById("sped3" ).innerText = parseInt (MaxSpeed/4 * 3 ); document .getElementById("maxsped" ).innerText = parseInt (MaxSpeed); var index = 0 ; var len = pointArr.length; var timer = setInterval (function ( ) { display(pointArr[index].lat,pointArr[index].lon,infoArr[index].speed); document .getElementById("high" ).value = infoArr[index].high +' m' ; document .getElementById("time" ).value = infoArr[index].time; document .getElementById("speed" ).value = infoArr[index].speed.toFixed(4 )+' km/h' ;; document .getElementById("direction" ).value = infoArr[index].direction; document .getElementById("date" ).value = infoArr[index].date; document .getElementById("longitude" ).value = pointArr[index].lon.toFixed(4 )+'°E' ; document .getElementById("latitude" ).value = pointArr[index].lat.toFixed(4 )+'°N' ; if (!isNaN (infoArr[index].high))document .getElementById("highbar" ).value = infoArr[index].high; index++; var Progress = (index/len)*1000 ; document .getElementById("progressbar" ).value = Progress; if (index>= len){ flag = 2 ; display(pointArr[len-1 ].lat,pointArr[len-1 ].lon); pointArr = []; infoArr = []; clearInterval (timer); } if (flag_timer == false ){ flag_timer = true ; flag = 2 ; pointArr = []; infoArr = []; clearInterval (timer); } },displaySpeed); } }
3、项目源代码
最后把项目源代码给出来,需要的自取
Github:https://github.com/pingden/GPS_Trajectory_reproduction.git
Gitee:https://gitee.com/pingdeng/GPS.git
百度云:
链接:https://pan.baidu.com/s/1Qh3G2VKrSIvBAZPRevXsEA 提取码:0p9h
有问题可以在评论区留言~~~