From 7f241eb85380c01810a79fd98d7e69dd60203f75 Mon Sep 17 00:00:00 2001 From: Aaron Parecki Date: Mon, 5 Oct 2015 23:09:56 +0000 Subject: [PATCH] fancy graph now includes speed and visit/event information --- compass/app/Http/Controllers/Api.php | 42 +++- compass/public/assets/battery.js | 120 ++++++++++ compass/public/assets/events.js | 82 +++++++ compass/public/assets/extensions.js | 74 ++++++ compass/public/assets/map.css | 6 +- compass/public/assets/map.js | 222 +++--------------- compass/public/assets/speed.js | 36 +++ compass/resources/views/layouts/map.blade.php | 4 + compass/resources/views/map.blade.php | 2 +- 9 files changed, 384 insertions(+), 204 deletions(-) create mode 100644 compass/public/assets/battery.js create mode 100644 compass/public/assets/events.js create mode 100644 compass/public/assets/extensions.js create mode 100644 compass/public/assets/speed.js diff --git a/compass/app/Http/Controllers/Api.php b/compass/app/Http/Controllers/Api.php index b2b488a..a03e2a4 100644 --- a/compass/app/Http/Controllers/Api.php +++ b/compass/app/Http/Controllers/Api.php @@ -35,12 +35,13 @@ class Api extends BaseController $qz = new Quartz\DB(env('STORAGE_DIR').$db->name, 'r'); + if($request->input('tz')) { + $tz = $request->input('tz'); + } else { + $tz = 'America/Los_Angeles'; + } + if($date=$request->input('date')) { - if($request->input('tz')) { - $tz = $request->input('tz'); - } else { - $tz = 'America/Los_Angeles'; - } $start = DateTime::createFromFormat('Y-m-d H:i:s', $date.' 00:00:00', new DateTimeZone($tz)); $end = DateTime::createFromFormat('Y-m-d H:i:s', $date.' 23:59:59', new DateTimeZone($tz)); } else { @@ -51,23 +52,44 @@ class Api extends BaseController $locations = []; $properties = []; + $events = []; foreach($results as $id=>$record) { - $record->date->format('U.u'); - $locations[] = $record->data; - $properties[] = $record->data->properties; + if(property_exists($record->data->properties, 'action')) { + $rec = $record->data; + $date = $record->date; + $rec->properties->unixtime = (int)$date->format('U'); + $events[] = $rec; + } else { + #$record->date->format('U.u'); + $locations[] = $record->data; + $props = $record->data->properties; + $date = $record->date; + $date->setTimeZone(new DateTimeZone($tz)); + $props->timestamp = $date->format('c'); + $props->unixtime = (int)$date->format('U'); + $properties[] = $props; + } } if($request->input('format') == 'linestring') { - $response = array( + $linestring = array( 'type' => 'LineString', 'coordinates' => array(), 'properties' => $properties ); foreach($locations as $loc) { - $response['coordinates'][] = $loc->geometry->coordinates; + if(property_exists($loc, 'geometry')) + $linestring['coordinates'][] = $loc->geometry->coordinates; + else + $linestring['coordinates'][] = null; } + + $response = array( + 'linestring' => $linestring, + 'events' => $events + ); } else { $response = [ diff --git a/compass/public/assets/battery.js b/compass/public/assets/battery.js new file mode 100644 index 0000000..4700c2c --- /dev/null +++ b/compass/public/assets/battery.js @@ -0,0 +1,120 @@ +function pointFromGeoJSON(geo) { + if(geo) { + return L.latLng(geo[1], geo[0]) + } +} + +function showBatteryGraph(response) { + + var data = response.linestring; + + var batteryStateBands = []; + var buckets = data.properties.map(function(d){return d.battery_state}).findRanges(); + + for(var i in buckets) { + for(var j=0; j'+this.y+'%'; + } + }, + turboThreshold: 0, + lineWidth: 1, + color: '#a8cff4', + marker: { + enabled: true, + radius: 2, + fillColor: '#7cb5ec' + } + }].concat(speedSeries(response)).concat(collectEventSeries(response)), + }); +} diff --git a/compass/public/assets/events.js b/compass/public/assets/events.js new file mode 100644 index 0000000..eacc732 --- /dev/null +++ b/compass/public/assets/events.js @@ -0,0 +1,82 @@ +function collectEventSeries(data) { + + var events = data.events; + + var series = { + "visit": { + name: "Visit", + type: 'scatter', + color: '#8f799e', + y: 10, + data: [] + }, + "paused_location_updates": { + name: "Paused Location Updates", + color: '#a0876e', + y: 20, + data: [] + }, + "resumed_location_updates": { + name: "Resumed Location Updates", + color: '#819e73', + y: 30, + data: [] + }, + "did_finish_deferred_updates": { + name: "Finished Deferred Updates", + color: '#9ea06e', + y: 40, + data: [] + }, + "did_enter_background": { + name: "Entered Background", + color: '#799b9e', + y: 50, + data: [] + }, + "will_resign_active": { + name: "Will Resign Active", + color: '#737f9e', + y: 60, + data: [] + }, + "will_terminate": { + name: "Will Terminate", + color: '#9e7773', + y: 70, + data: [] + } + }; + + for(var i=0; i 0) { + series[i].type = 'scatter'; + series[i].yAxis = 0; + series[i].tooltip = { + pointFormatter: function() { + moveMarkerToPosition(this); + var h = this.x.getHours(); + var m = this.x.getMinutes(); + var s = this.x.getSeconds(); + if(m < 10) m = '0'+m; + if(s < 10) s = '0'+s; + return h+':'+m+':'+s; + } + }; + response.push(series[i]); + } + } + + return response; +} diff --git a/compass/public/assets/extensions.js b/compass/public/assets/extensions.js new file mode 100644 index 0000000..2cc2d0d --- /dev/null +++ b/compass/public/assets/extensions.js @@ -0,0 +1,74 @@ +Array.prototype.clean = function(deleteValue) { + for (var i = 0; i < this.length; i++) { + if (this[i] == deleteValue) { + this.splice(i, 1); + i--; + } + } + return this; +}; + +/* + Array.findRanges + + Turns this: + + ["a","a","a","b","b","c","c","c","c","c","a","a","c"] + + into this: + + { + "a":[ + { + "from":0, + "to":2 + }, + { + "from":10, + "to":11 + } + ], + "b":[ + { + "from":3, + "to":4 + } + ], + "c":[ + { + "from":5, + "to":9 + }, + { + "from":12, + "to":12 + } + ] + } + +*/ + +Array.prototype.findRanges = function() { + var buckets = {}; + for(var i = 0; i < this.length; i++) { + if(!(this[i] in buckets)) { + buckets[this[i]] = [{ + from: i, + to: i + }] + } else { + var last = buckets[this[i]][ buckets[this[i]].length-1 ]; + if(i == last.to + 1) { + last.to = i; + } else { + buckets[this[i]].push({ + from: i, + to: i + }) + } + } + } + return buckets; +}; + +Object.values = function(obj){ return Object.keys(obj).map(function(key){return obj[key]}) }; diff --git a/compass/public/assets/map.css b/compass/public/assets/map.css index 7e82244..398df61 100644 --- a/compass/public/assets/map.css +++ b/compass/public/assets/map.css @@ -3,13 +3,13 @@ html, body { } #map { - height: calc(100% - 120px); + height: calc(100% - 160px); } #calendar { z-index: 100; width: 200px; - height: calc(100% - 160px); + height: calc(100% - 180px); position: absolute; top: 10px; right: 10px; @@ -50,7 +50,7 @@ table.calendar { background: #6699ff; } -#battery-chart { +#battery-chart, #events-chart { width: 100%; } diff --git a/compass/public/assets/map.js b/compass/public/assets/map.js index 8e0a7b6..a330141 100644 --- a/compass/public/assets/map.js +++ b/compass/public/assets/map.js @@ -1,76 +1,3 @@ -Array.prototype.clean = function(deleteValue) { - for (var i = 0; i < this.length; i++) { - if (this[i] == deleteValue) { - this.splice(i, 1); - i--; - } - } - return this; -}; - -/* - Array.findRanges - - Turns this: - - ["a","a","a","b","b","c","c","c","c","c","a","a","c"] - - into this: - - { - "a":[ - { - "from":0, - "to":2 - }, - { - "from":10, - "to":11 - } - ], - "b":[ - { - "from":3, - "to":4 - } - ], - "c":[ - { - "from":5, - "to":9 - }, - { - "from":12, - "to":12 - } - ] - } - -*/ - -Array.prototype.findRanges = function() { - var buckets = {}; - for(var i = 0; i < this.length; i++) { - if(!(this[i] in buckets)) { - buckets[this[i]] = [{ - from: i, - to: i - }] - } else { - var last = buckets[this[i]][ buckets[this[i]].length-1 ]; - if(i == last.to + 1) { - last.to = i; - } else { - buckets[this[i]].push({ - from: i, - to: i - }) - } - } - } - return buckets; -}; - var map = L.map('map', { zoomControl: false }).setView([45.516, -122.660], 14, null, null, 24); var layer = L.esri.basemapLayer("Topographic"); @@ -92,14 +19,6 @@ var highlightedMarker; var animatedMarker; var timers = []; -var batteryChart; - -/* -Chart.defaults.global.animation = false; -Chart.defaults.global.responsive = true; -*/ - - function resetAnimation() { if(animatedMarker) { map.removeLayer(animatedMarker); @@ -133,8 +52,20 @@ jQuery(function($){ var db_name = $("#database").data("name"); var db_token = $("#database").data("token"); - $.get("/api/query?format=linestring&date="+$(this).data('date')+"&tz=America/Los_Angeles&token="+db_token, function(data){ + $.get("/api/query?format=linestring&date="+$(this).data('date')+"&tz=America/Los_Angeles&token="+db_token, function(response){ + var data = response.linestring; + if(data.coordinates && data.coordinates.length > 0) { + // For any null coordinates, fill it in with the previous location + var lastCoord = null; + for(var i in data.coordinates) { + if(data.coordinates[i] == null) { + data.coordinates[i] = lastCoord; + } else { + lastCoord = data.coordinates[i]; + } + } + visible_data.push(data); visible_layers.push(L.geoJson(data, { style: geojsonLineOptions @@ -159,117 +90,14 @@ jQuery(function($){ } map.fitBounds(full_bounds); } - - var batteryStateBands = []; - var buckets = data.properties.map(function(d){return d.battery_state}).findRanges(); - for(var i in buckets) { - for(var j=0; j{point.y}', - valueSuffix: '%' - }, - turboThreshold: 0 - }] - }); - $('#battery-chart').mousemove(function(event){ - var chart = $('#battery-chart').highcharts(); - var percent = (event.offsetX - chart.plotLeft) / chart.plotWidth; - if(percent >= 0 && percent <= 1) { - var coord = pointFromGeoJSON(visible_data[0].coordinates[Math.round(percent * visible_data[0].coordinates.length)]); - if(!highlightedMarker) { - highlightedMarker = L.marker(coord).addTo(map); - } else { - highlightedMarker.setLatLng(coord); - } - } - }); - + showBatteryGraph(response); } }); return false; }); - - function pointFromGeoJSON(geo) { - return L.latLng(geo[1], geo[0]) - } - + $('#btn-play').click(function(){ console.log(visible_data[0].coordinates[0]); var point = pointFromGeoJSON(visible_data[0].coordinates[0]); @@ -295,8 +123,22 @@ jQuery(function($){ $(".calendar a[data-date='"+((new Date()).toISOString().slice(0,10))+"']").focus().click(); - //////////////////// +}); + +function moveMarkerToPosition(point) { + if(point.location) { + var coord = pointFromGeoJSON(point.location); + if(coord) { + if(!highlightedMarker) { + highlightedMarker = L.marker(coord).addTo(map); + } else { + highlightedMarker.setLatLng(coord); + } + } + } +} - //batteryChart = new Chart(document.getElementById("battery-chart").getContext("2d")); +function pointFromGeoJSON(geo) { + return L.latLng(geo[1], geo[0]) +} -}); diff --git a/compass/public/assets/speed.js b/compass/public/assets/speed.js new file mode 100644 index 0000000..1d80b50 --- /dev/null +++ b/compass/public/assets/speed.js @@ -0,0 +1,36 @@ +function speedSeries(response) { + + var data = response.linestring; + + var series = { + name: "Speed", + yAxis: 1, + tooltip: { + animation: true, + pointFormatter: function(){ + moveMarkerToPosition(this); + return ''+this.y+'mph'; + } + }, + lineWidth: 1, + color: '#a8a8a8', + marker: { + enabled: true, + radius: 1, + symbol: 'circle', + fillColor: '#a8a8a8' + }, + turboThreshold: 0, + data: [] + }; + + series.data = data.properties.map(function(d,i){ + return { + x: new Date(d.unixtime*1000), + y: ('speed' in d && d.speed >= 0 ? Math.round(d.speed * 2.23694) : null), + location: data.coordinates[i] + } + }); + + return [series]; +} diff --git a/compass/resources/views/layouts/map.blade.php b/compass/resources/views/layouts/map.blade.php index 27970cc..24624f0 100644 --- a/compass/resources/views/layouts/map.blade.php +++ b/compass/resources/views/layouts/map.blade.php @@ -18,6 +18,10 @@ + + + + \ No newline at end of file diff --git a/compass/resources/views/map.blade.php b/compass/resources/views/map.blade.php index e602df3..49b1c1d 100644 --- a/compass/resources/views/map.blade.php +++ b/compass/resources/views/map.blade.php @@ -32,7 +32,7 @@
-
+