You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

302 lines
7.1 KiB

  1. Array.prototype.clean = function(deleteValue) {
  2. for (var i = 0; i < this.length; i++) {
  3. if (this[i] == deleteValue) {
  4. this.splice(i, 1);
  5. i--;
  6. }
  7. }
  8. return this;
  9. };
  10. /*
  11. Array.findRanges
  12. Turns this:
  13. ["a","a","a","b","b","c","c","c","c","c","a","a","c"]
  14. into this:
  15. {
  16. "a":[
  17. {
  18. "from":0,
  19. "to":2
  20. },
  21. {
  22. "from":10,
  23. "to":11
  24. }
  25. ],
  26. "b":[
  27. {
  28. "from":3,
  29. "to":4
  30. }
  31. ],
  32. "c":[
  33. {
  34. "from":5,
  35. "to":9
  36. },
  37. {
  38. "from":12,
  39. "to":12
  40. }
  41. ]
  42. }
  43. */
  44. Array.prototype.findRanges = function() {
  45. var buckets = {};
  46. for(var i = 0; i < this.length; i++) {
  47. if(!(this[i] in buckets)) {
  48. buckets[this[i]] = [{
  49. from: i,
  50. to: i
  51. }]
  52. } else {
  53. var last = buckets[this[i]][ buckets[this[i]].length-1 ];
  54. if(i == last.to + 1) {
  55. last.to = i;
  56. } else {
  57. buckets[this[i]].push({
  58. from: i,
  59. to: i
  60. })
  61. }
  62. }
  63. }
  64. return buckets;
  65. };
  66. var map = L.map('map', { zoomControl: false }).setView([45.516, -122.660], 14, null, null, 24);
  67. var layer = L.esri.basemapLayer("Topographic");
  68. layer.maxZoom = 24;
  69. layer.maxNativeZoom = 24;
  70. layer.addTo(map);
  71. new L.Control.Zoom({ position: 'topleft' }).addTo(map);
  72. var geojsonLineOptions = {
  73. color: "#0033ff",
  74. weight: 4,
  75. opacity: 0.5
  76. };
  77. var visible_layers = [];
  78. var visible_data = [];
  79. var highlightedMarker;
  80. var animatedMarker;
  81. var timers = [];
  82. var batteryChart;
  83. /*
  84. Chart.defaults.global.animation = false;
  85. Chart.defaults.global.responsive = true;
  86. */
  87. function resetAnimation() {
  88. if(animatedMarker) {
  89. map.removeLayer(animatedMarker);
  90. }
  91. if(timers.length > 0) {
  92. for(var i in timers) {
  93. clearTimeout(timers[i]);
  94. }
  95. }
  96. }
  97. jQuery(function($){
  98. $('.calendar a').click(function(evt){
  99. var append = evt.altKey;
  100. if(!append) {
  101. $('.calendar a').removeClass('selected');
  102. if(visible_layers.length) {
  103. for(var i in visible_layers) {
  104. map.removeLayer(visible_layers[i]);
  105. }
  106. }
  107. visible_layers = [];
  108. visible_data = [];
  109. }
  110. $(this).addClass('selected');
  111. resetAnimation();
  112. var db_name = $("#database").data("name");
  113. var db_token = $("#database").data("token");
  114. $.get("/api/query?format=linestring&date="+$(this).data('date')+"&tz=America/Los_Angeles&token="+db_token, function(data){
  115. if(data.coordinates && data.coordinates.length > 0) {
  116. visible_data.push(data);
  117. visible_layers.push(L.geoJson(data, {
  118. style: geojsonLineOptions
  119. }).addTo(map));
  120. // If the new layer is completely outside the current view, zoom the map to fit all layers
  121. var layer = visible_layers[visible_layers.length - 1];
  122. var is_outside = false;
  123. if(!map.getBounds().intersects(layer.getBounds())) {
  124. is_outside = true;
  125. }
  126. if(is_outside) {
  127. var full_bounds;
  128. for(var i in visible_layers) {
  129. layer = visible_layers[i];
  130. if(full_bounds) {
  131. full_bounds.extend(layer.getBounds());
  132. } else {
  133. full_bounds = layer.getBounds();
  134. }
  135. }
  136. map.fitBounds(full_bounds);
  137. }
  138. var batteryStateBands = [];
  139. var buckets = data.properties.map(function(d){return d.battery_state}).findRanges();
  140. for(var i in buckets) {
  141. for(var j=0; j<buckets[i].length; j++) {
  142. switch(i) {
  143. case "charging":
  144. buckets[i][j].color = "rgba(193,236,171,0.4)";
  145. break;
  146. case "full":
  147. buckets[i][j].color = "rgba(171,204,236,0.4)";
  148. break;
  149. case "unplugged":
  150. buckets[i][j].color = "rgba(236,178,171,0.4)";
  151. break;
  152. default:
  153. buckets[i][j].color = "#ffffff";
  154. break;
  155. }
  156. batteryStateBands.push(buckets[i][j]);
  157. }
  158. }
  159. $('#battery-chart').highcharts({
  160. title: {
  161. text: '',
  162. style: {
  163. display: 'none'
  164. }
  165. },
  166. subtitle: {
  167. text: '',
  168. style: {
  169. display: 'none'
  170. }
  171. },
  172. chart: {
  173. height: 80
  174. },
  175. legend: {
  176. enabled: false
  177. },
  178. xAxis: {
  179. categories: data.properties.map(function(d){
  180. if(isNaN(d.timestamp)) {
  181. return d.timestamp.substr(11,5);
  182. } else {
  183. var date = new Date(d.timestamp * 1000);
  184. return date.getHours() + ":" + ("0" + date.getMinutes()).substr(-2);
  185. }
  186. }),
  187. plotBands: batteryStateBands,
  188. labels: {
  189. style: {
  190. fontSize: '8px'
  191. }
  192. }
  193. },
  194. yAxis: {
  195. title: {
  196. text: ''
  197. },
  198. plotLines: [{
  199. value: 0,
  200. width: 1,
  201. color: '#808080'
  202. }],
  203. max: 100,
  204. min: 0
  205. },
  206. series: [{
  207. name: 'Battery',
  208. data: data.properties.map(function(d,i){
  209. return {
  210. x: i,
  211. y: ('battery_level' in d ? d.battery_level * 100 : -1),
  212. state: d.battery_state
  213. }
  214. }),
  215. tooltip: {
  216. animation: true,
  217. pointFormat: '{point.state}<br><b>{point.y}</b>',
  218. valueSuffix: '%'
  219. },
  220. turboThreshold: 0
  221. }]
  222. });
  223. $('#battery-chart').mousemove(function(event){
  224. var chart = $('#battery-chart').highcharts();
  225. var percent = (event.offsetX - chart.plotLeft) / chart.plotWidth;
  226. if(percent >= 0 && percent <= 1) {
  227. var coord = pointFromGeoJSON(visible_data[0].coordinates[Math.round(percent * visible_data[0].coordinates.length)]);
  228. if(!highlightedMarker) {
  229. highlightedMarker = L.marker(coord).addTo(map);
  230. } else {
  231. highlightedMarker.setLatLng(coord);
  232. }
  233. }
  234. });
  235. }
  236. });
  237. return false;
  238. });
  239. function pointFromGeoJSON(geo) {
  240. return L.latLng(geo[1], geo[0])
  241. }
  242. $('#btn-play').click(function(){
  243. console.log(visible_data[0].coordinates[0]);
  244. var point = pointFromGeoJSON(visible_data[0].coordinates[0]);
  245. resetAnimation();
  246. animatedMarker = L.marker(point);
  247. animatedMarker.addTo(map);
  248. timers = [];
  249. var interval = 3;
  250. for(var i in visible_data[0].coordinates) {
  251. (function(i){
  252. timers.push(setTimeout(function(){
  253. point = pointFromGeoJSON(visible_data[0].coordinates[i]);
  254. animatedMarker.setLatLng(point);
  255. }, interval*i));
  256. })(i);
  257. }
  258. });
  259. $(".calendar a[data-date='"+((new Date()).toISOString().slice(0,10))+"']").focus().click();
  260. ////////////////////
  261. //batteryChart = new Chart(document.getElementById("battery-chart").getContext("2d"));
  262. });