Browse Source

fancy graph now includes speed and visit/event information

pull/5/head
Aaron Parecki 8 years ago
parent
commit
7f241eb853
9 changed files with 384 additions and 204 deletions
  1. +32
    -10
      compass/app/Http/Controllers/Api.php
  2. +120
    -0
      compass/public/assets/battery.js
  3. +82
    -0
      compass/public/assets/events.js
  4. +74
    -0
      compass/public/assets/extensions.js
  5. +3
    -3
      compass/public/assets/map.css
  6. +32
    -190
      compass/public/assets/map.js
  7. +36
    -0
      compass/public/assets/speed.js
  8. +4
    -0
      compass/resources/views/layouts/map.blade.php
  9. +1
    -1
      compass/resources/views/map.blade.php

+ 32
- 10
compass/app/Http/Controllers/Api.php View File

@ -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 = [

+ 120
- 0
compass/public/assets/battery.js View File

@ -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<buckets[i].length; j++) {
switch(i) {
case "charging":
buckets[i][j].color = "rgba(193,236,171,0.4)";
break;
case "full":
buckets[i][j].color = "rgba(171,204,236,0.4)";
break;
case "unplugged":
buckets[i][j].color = "rgba(236,178,171,0.4)";
break;
default:
buckets[i][j].color = "#ffffff";
break;
}
buckets[i][j].from = new Date(data.properties[buckets[i][j].from].unixtime * 1000);
buckets[i][j].to = new Date(data.properties[buckets[i][j].to].unixtime * 1000);
batteryStateBands.push(buckets[i][j]);
}
}
$('#battery-chart').highcharts({
chart: {
height: 160,
zoomType: 'x',
panning: true,
panKey: 'shift'
},
title: {
text: '',
style: {
display: 'none'
}
},
subtitle: {
text: '',
style: {
display: 'none'
}
},
legend: {
enabled: false
},
xAxis: {
type: 'datetime',
plotBands: batteryStateBands,
labels: {
rotation: -35,
style: {
fontSize: '8px'
}
}
},
yAxis: [{
title: {
text: 'Battery %'
},
plotLines: [{
value: 0,
width: 1,
color: '#808080'
}],
max: 100,
min: 0
},{
title: {
text: 'Speed'
},
plotLines: [{
value: 0,
width: 1,
color: '#dddddd'
}],
min: 0,
opposite: true
}],
series: [
{
name: 'Battery',
yAxis: 0,
data: data.properties.map(function(d,i){
return {
x: new Date(d.unixtime*1000), // i,
y: ('battery_level' in d ? Math.round(d.battery_level * 100) : -1),
state: d.battery_state,
location: data.coordinates[i]
}
}),
tooltip: {
animation: true,
pointFormatter: function(){
moveMarkerToPosition(this);
return this.state+'<br><b>'+this.y+'%</b>';
}
},
turboThreshold: 0,
lineWidth: 1,
color: '#a8cff4',
marker: {
enabled: true,
radius: 2,
fillColor: '#7cb5ec'
}
}].concat(speedSeries(response)).concat(collectEventSeries(response)),
});
}

+ 82
- 0
compass/public/assets/events.js View File

@ -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<events.length; i++) {
series[events[i].properties.action].data.push({
x: new Date(events[i].properties.unixtime*1000),
y: series[events[i].properties.action].y,
location: events[i].geometry.coordinates
});
}
var response = [];
series = Object.values(series);
for(var i=0; i<series.length; i++) {
if(series[i].data.length > 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;
}

+ 74
- 0
compass/public/assets/extensions.js View File

@ -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]}) };

+ 3
- 3
compass/public/assets/map.css View File

@ -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%;
}

+ 32
- 190
compass/public/assets/map.js View File

@ -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<buckets[i].length; j++) {
switch(i) {
case "charging":
buckets[i][j].color = "rgba(193,236,171,0.4)";
break;
case "full":
buckets[i][j].color = "rgba(171,204,236,0.4)";
break;
case "unplugged":
buckets[i][j].color = "rgba(236,178,171,0.4)";
break;
default:
buckets[i][j].color = "#ffffff";
break;
}
batteryStateBands.push(buckets[i][j]);
}
}
$('#battery-chart').highcharts({
title: {
text: '',
style: {
display: 'none'
}
},
subtitle: {
text: '',
style: {
display: 'none'
}
},
chart: {
height: 80
},
legend: {
enabled: false
},
xAxis: {
categories: data.properties.map(function(d){
if(isNaN(d.timestamp)) {
return d.timestamp.substr(11,5);
} else {
var date = new Date(d.timestamp * 1000);
return date.getHours() + ":" + ("0" + date.getMinutes()).substr(-2);
}
}),
plotBands: batteryStateBands,
labels: {
style: {
fontSize: '8px'
}
}
},
yAxis: {
title: {
text: ''
},
plotLines: [{
value: 0,
width: 1,
color: '#808080'
}],
max: 100,
min: 0
},
series: [{
name: 'Battery',
data: data.properties.map(function(d,i){
return {
x: i,
y: ('battery_level' in d ? d.battery_level * 100 : -1),
state: d.battery_state
}
}),
tooltip: {
animation: true,
pointFormat: '{point.state}<br><b>{point.y}</b>',
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])
}
});

+ 36
- 0
compass/public/assets/speed.js View File

@ -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 '<b>'+this.y+'mph</b>';
}
},
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];
}

+ 4
- 0
compass/resources/views/layouts/map.blade.php View File

@ -18,6 +18,10 @@
<script src="/assets/leaflet-0.7.3/leaflet.js"></script>
<script src="/assets/leaflet-0.7.3/esri-leaflet.js"></script>
<script src="/assets/highcharts/js/highcharts.js"></script>
<script src="/assets/extensions.js"></script>
<script src="/assets/speed.js"></script>
<script src="/assets/events.js"></script>
<script src="/assets/battery.js"></script>
<script src="/assets/map.js"></script>
</body>
</html>

+ 1
- 1
compass/resources/views/map.blade.php View File

@ -32,7 +32,7 @@
<div id="map"></div>
<div id="graphs">
<div id="battery-chart" width="800" height="80"></div>
<div id="battery-chart" width="800" height="160"></div>
</div>
<div id="database" data-name="{{ $database->name }}" data-token="{{ $database->read_token }}"></div>

Loading…
Cancel
Save