main.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. const selColor = "#FF5C26";
  2. let d = [{
  3. label:"Altitude,m",
  4. data:[],
  5. yaxis: 2
  6. },
  7. {
  8. label:"Speed,km/h",
  9. data:[]
  10. }];
  11. let options = {
  12. xaxis: {
  13. mode: "time",
  14. tickLength: 5,
  15. zoomRange: [60 * 1000, 1000 * 3600 * 24 * 365]
  16. },
  17. /*selection: {
  18. mode: "x",
  19. color: selColor
  20. },*/
  21. yaxes: [{
  22. min: 0,
  23. zoomRange: false,
  24. panRange: false
  25. }, {
  26. position: "right",
  27. alignTicksWithAxis: 1,
  28. min: 0,
  29. zoomRange: false,
  30. panRange: false
  31. }],
  32. zoom: {
  33. interactive: true
  34. },
  35. pan: {
  36. interactive: true,
  37. cursor: "move",
  38. frameRate: 60
  39. }
  40. };
  41. let optO = {
  42. legend : {
  43. show: false
  44. },
  45. series: {
  46. lines: {
  47. show: true,
  48. lineWidth: 1
  49. },
  50. shadowSize: 0
  51. },
  52. xaxis: {
  53. //ticks: [],
  54. mode: "time"
  55. },
  56. yaxis: {
  57. ticks: [],
  58. autoscaleMargin: 0.1
  59. },
  60. selection: {
  61. mode: "x",
  62. color: selColor,
  63. minSize: 0
  64. }
  65. };
  66. let gps = new GPS();
  67. let plot, overview;
  68. window.onload = function() {
  69. Cesium.Ion.defaultAccessToken = '<token>';
  70. var extent = {west: -0.2540382220862719, south: 0.6872565916005104, east: 0.6129855406042352, north: 0.9377043806513488};
  71. Cesium.Camera.DEFAULT_VIEW_RECTANGLE = extent;
  72. Cesium.Camera.DEFAULT_VIEW_FACTOR = 0;
  73. bingMapsProvider = new Cesium.BingMapsImageryProvider({
  74. url: 'https://dev.virtualearth.net',
  75. key: '<key>',
  76. mapStyle: Cesium.BingMapsStyle.AERIAL_WITH_LABELS
  77. });
  78. viewer = new Cesium.Viewer('map', {
  79. fullscreenElement: "map",
  80. terrainProvider : Cesium.createWorldTerrain({
  81. requestVertexNormals: true,
  82. requestWaterMask: true
  83. }),
  84. //shadows: true
  85. });
  86. viewer.scene.globe.enableLighting = true;
  87. viewer.resolutionScale = 1.2;
  88. viewer.scene.screenSpaceCameraController.enableTilt = !('ontouchstart' in window);
  89. viewer.baseLayerPicker.viewModel.selectedImagery = new Cesium.ProviderViewModel({
  90. name: 'Bing Maps Aerial with Labels',
  91. iconUrl: Cesium.buildModuleUrl('Widgets/Images/ImageryProviders/bingAerialLabels.png'),
  92. tooltip: 'Bing Maps aerial imagery with labels',
  93. creationFunction: function () {
  94. return bingMapsProvider
  95. }
  96. });
  97. var paths = [];
  98. fetch("markers")
  99. .then(function(response) {
  100. return response.json();
  101. })
  102. .then(function(jsonResponse) {
  103. gps.parse(jsonResponse);
  104. for(const t of gps.tracks) {
  105. const i = t.info;
  106. var desc = "Start: <b>" + timeFormat(i.startTime, "datetime") +
  107. "</b><br/>Finish: <b>" + timeFormat(i.endTime, "datetime") + "</b><br/>" +
  108. "Track time (full): "+ timeFormat(i.totalTime) + "<br>" +
  109. "Track time (mov.): "+ timeFormat(i.movTime) + "<br>" +
  110. "Alt: " + i.totalAscend.toFixed(1) + "m &uarr;" + i.ascend.toFixed(1) + "m &darr;" + i.descend.toFixed(1) + "m"+
  111. "<br/>Distance: "+(i.distance/1000).toFixed(1)+"km<br/>" +
  112. "top speed: "+i.topSpeed.toFixed(1)+
  113. "km/h<br/>"+
  114. "average speed: "+i.avgSpeed.toFixed(1)+"km/h<br/>";
  115. const col = Cesium.Color.fromCssColorString(i.color)
  116. var path = viewer.entities.add({
  117. label : "Track "+i.id,
  118. id: "track_"+i.id,
  119. description: desc,
  120. polyline : {
  121. positions : t.data.map(p => Cesium.Cartesian3.fromDegrees(p.lng, p.lat, p.alt)),
  122. width : 5,
  123. material : new Cesium.PolylineOutlineMaterialProperty({
  124. color : col,
  125. }),
  126. //clampToGround: true,
  127. depthFailMaterial: new Cesium.PolylineOutlineMaterialProperty({
  128. color : Cesium.Color.fromAlpha(col, 0.6),
  129. }),
  130. shadows: Cesium.ShadowMode.ENABLED
  131. }
  132. });
  133. paths.push(path);
  134. }
  135. var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  136. handler.setInputAction(function (movement) {
  137. var pick = viewer.scene.pick(movement.position);
  138. if (Cesium.defined(pick) && (pick.id._id.match(/track_([0-9]+)/))) {
  139. var id = parseInt(RegExp.$1);
  140. var path = paths[id];
  141. show(id);
  142. }
  143. }, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
  144. viewer.flyTo(paths[paths.length - 1], { offset: new Cesium.HeadingPitchRange(0, -90, 0)});
  145. let regionIndex = 0;
  146. for(let r=0; r<gps.regions.length; r++) {
  147. setTimeout(addInfo, (gps.regions.length-1-regionIndex) * 300, bingMapsProvider, viewer, regionIndex, gps.regions[r]);
  148. regionIndex++;
  149. }
  150. const i = gps.getInfo();
  151. var html =
  152. "Time (full): "+ timeFormat(i.totalTime) + "<br>" +
  153. "Time (mov.): "+ timeFormat(i.movTime) + "<br>" +
  154. "Alt: &uarr;" + (i.ascend/1000).toFixed(1) + "km &darr;" + (i.descend/1000).toFixed(1) + "km<br>"+
  155. "Distance: <b>"+(i.distance/1000).toFixed(1)+"km</b><br/>" +
  156. "Top speed: <b>"+i.topSpeed.toFixed(1) + "km/h</b><br/>"+
  157. "Avg. speed: "+i.avgSpeed.toFixed(1)+"km/h<br/>"+
  158. "Data points: "+i.points+"<br/>"+
  159. "<a href='javascript:show(" + (gps.tracks.length-1) + ")'>Graph</a>";
  160. $(".status").append(html);
  161. });
  162. plot = $.plot("#placeholder", d, options);
  163. overview = $.plot("#overview", d, optO);
  164. // now connect the two
  165. $("#placeholder").bind("plotpan", updateOverview);
  166. $("#placeholder").bind("plotzoom", updateOverview);
  167. function updateOverview(event, ranges) {
  168. var axes = plot.getAxes();
  169. for(var axis in axes) {
  170. axes[axis].from = axes[axis].min;
  171. axes[axis].to = axes[axis].max;
  172. }
  173. overview.setSelection(axes, true);
  174. }
  175. $("#overview").bind("plotselected", function (event, ranges) {
  176. $.each(plot.getXAxes(), function(_, axis) {
  177. var opts = axis.options;
  178. opts.min = ranges.xaxis.from;
  179. opts.max = ranges.xaxis.to;
  180. });
  181. plot.setupGrid();
  182. plot.draw();
  183. });
  184. $("#whole").click(function () {
  185. setZoom(false,false);
  186. });
  187. $("#right").click(function () {
  188. var min = plot.getXAxes()[0].options.min;
  189. var max = plot.getXAxes()[0].options.max;
  190. if(min != null && max != null)
  191. setZoom((min+max)/2, max * 1.5 - min/2);
  192. });
  193. $("#left").click(function () {
  194. var min = plot.getXAxes()[0].options.min;
  195. var max = plot.getXAxes()[0].options.max;
  196. if(min != null && max != null)
  197. setZoom(min * 1.5 - max/2, (min+max)/2);
  198. });
  199. }
  200. let firstShow = true;
  201. function show(id) {
  202. if(firstShow) {
  203. firstShow = false;
  204. d[0].data = smoothOut(gps.data.map(v => v.alt), 0.85);
  205. d[1].data = smoothOut(gps.data.map(v => v.speed), 0.85);
  206. for(let i=0; i<gps.data.length; i++) {
  207. const cur = gps.data[i];
  208. if((cur.timeDiff > 60 && cur.speed < 0.5) || cur.speed === null) {
  209. d[0].data[i] = null;
  210. d[1].data[i] = null;
  211. }
  212. d[0].data[i] = [cur.timestamp * 1000, d[0].data[i]];
  213. d[1].data[i] = [cur.timestamp * 1000, d[1].data[i]];
  214. }
  215. let opts = plot.getXAxes()[0].options;
  216. opts.panRange = [
  217. gps.data[0].timestamp * 1000,
  218. gps.data[gps.data.length-1].timestamp * 1000
  219. ];
  220. plot.setData(d);
  221. plot.setupGrid(); //only necessary if your new data will change the axes or grid
  222. plot.draw();
  223. overview.setData(d);
  224. overview.setupGrid(); //only necessary if your new data will change the axes or grid
  225. overview.draw();
  226. for(let t of gps.tracks) {
  227. $("#tracks").append("<li><input type='button' onclick='setZoom("+t.info.startTime*1000+","+t.info.endTime*1000+")' value='" + timeFormat(t.info.startTime, "date") + " " + (t.info.distance/1000).toFixed(1) + "km'</li>");
  228. }
  229. }
  230. $("#shadow").css("visibility", "visible");
  231. $("#frame").css("visibility", "visible");
  232. console.log(id);
  233. let start = gps.tracks[id].info.startTime*1000;
  234. let end = gps.tracks[id].info.endTime*1000;
  235. setZoom(start,end);
  236. $("#track").click(function () {
  237. setZoom(start,end);
  238. });
  239. };
  240. function hide() {
  241. $(".popup").css("visibility", "hidden");
  242. };
  243. function setZoom(Xmin, Xmax) {
  244. var opts = plot.getXAxes()[0].options;
  245. if(Xmax==false) {
  246. Xmax = +new Date() - 60 * new Date().getTimezoneOffset() * 1000;
  247. }
  248. if(Xmin==false) {
  249. Xmin = d[0].data[0][0];
  250. overview.clearSelection();
  251. } else {
  252. overview.setSelection({ xaxis: { from: Xmin, to: Xmax}});
  253. }
  254. opts.min=Xmin;
  255. opts.max=Xmax;
  256. plot.setupGrid();
  257. plot.draw();
  258. plot.clearSelection();
  259. return false;
  260. }
  261. function addInfo(bingMapsProvider, viewer, id, data) {
  262. var url = `https://dev.virtualearth.net/REST/v1/LocationRecog/${data.lat.toFixed(6) + "," + data.lng.toFixed(6)}?radius=2&top=1&c=de-DE&includeEntityTypes=address&key=${bingMapsProvider._key}&output=json`;
  263. fetch(url).then(res=>res.json()).then(function(result) {
  264. if (result.statusDescription === 'OK') {
  265. const address = result?.resourceSets[0]?.resources[0]?.addressOfLocation[0];
  266. if (address) {
  267. let txt = address.locality + " " + address.neighborhood;
  268. if (!address.locality && !address.neighborhood) {
  269. txt = address.adminDivision;
  270. }
  271. if (address.countryIso2 != "DE") {
  272. txt += " " + address.countryIso2;
  273. }
  274. $('.sidebar').append("<div class='info' id='info_" + id + "'>" +
  275. timeFormat(data.timestamp, "short") + "<br>" +
  276. txt + "<br><b>" +
  277. (data.distance/1000).toFixed(1) + " km</b></div>");
  278. $('#info_'+id).click(function() {
  279. viewer.camera.flyTo({
  280. destination: Cesium.Cartesian3.fromDegrees(data.lng, data.lat, data.distance),
  281. offset: new Cesium.HeadingPitchRange(0, -90, 0)
  282. });
  283. });
  284. } else {
  285. console.log('No results found');
  286. }
  287. } else {
  288. console.log('Geocoder failed due to: ' + status);
  289. }
  290. });
  291. }
  292. function bindInfoWindow(path, map, infoWindow, html) {
  293. google.maps.event.addListener(path, 'click', function(e) {
  294. infoWindow.setContent(html);
  295. console.log(e);
  296. infoWindow.setPosition(e.latLng);
  297. infoWindow.open(map);
  298. });
  299. google.maps.event.addListener(path, 'mouseover', function() {
  300. path.setOptions({
  301. zIndex: 3,
  302. strokeWeight: 6
  303. });
  304. });
  305. google.maps.event.addListener(path, 'mouseout', function() {
  306. setTimeout(function() {
  307. path.setOptions({
  308. zIndex: 2,
  309. strokeWeight: 4
  310. });
  311. }, 1000);
  312. });
  313. }