Explorar el Código

fix speed field

subDesTagesMitExtraKaese hace 2 años
padre
commit
69e782c04f

+ 18 - 0
migrations/0007_alter_marker_speed.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.1.1 on 2022-09-21 13:34
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('gps_logger', '0006_marker_alt_alter_marker_location'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='marker',
+            name='speed',
+            field=models.FloatField(blank=True, null=True, verbose_name='Speed in km/h'),
+        ),
+    ]

+ 22 - 10
models.py

@@ -1,13 +1,15 @@
+import math
 from django.db import models
 from django.contrib.gis.db.models.fields import PointField
 from django.contrib.gis.db.models.functions import Distance
+from django.contrib.gis.measure import D
 
 class Marker(models.Model):
   timestamp = models.DateTimeField(unique=True)
   location = PointField(dim=2)
   alt = models.FloatField("Altitude")
   hdop = models.IntegerField(null=True, blank=True)
-  speed = models.IntegerField(null=True, blank=True)
+  speed = models.FloatField("Speed in km/h", null=True, blank=True)
 
 class Trip(models.Model):
   startTime = models.DateTimeField()
@@ -20,14 +22,24 @@ class CensoredLocation(models.Model):
   radius = models.IntegerField(default=800)
   name = models.CharField(max_length=255)
 
-  def delete_markers(self, markers = None) -> int:
-    # matches = Marker.objects.filter(location__distance_lt=(
-    #   self.location,
-    #   Distance(m=self.radius)
-    # ))
-
-    matches = Marker.objects.annotate(distance=Distance('location', self.location)
-       ).filter(distance__lte=self.radius)
+  def delete_markers(self) -> int:
+    matches = Marker.objects.filter(location__distance_lt=(
+      self.location,
+      distance_to_decimal_degrees(D(m=self.radius), self.location.y)
+    ))
     count = len(matches)
     matches.delete()
-    return count
+    return count
+
+
+def distance_to_decimal_degrees(distance, latitude):
+    """
+    Source of formulae information:
+        1. https://en.wikipedia.org/wiki/Decimal_degrees
+        2. http://www.movable-type.co.uk/scripts/latlong.html
+    :param distance: an instance of `from django.contrib.gis.measure.Distance`
+    :param latitude: y - coordinate of a point/location
+    """
+    lat_radians = latitude * (math.pi / 180)
+    # 1 longitudinal degree at the equator equal 111,319.5m equiv to 111.32km
+    return distance.m / (111_319.5 * math.cos(lat_radians))

+ 234 - 257
static/gps_logger/js/main.js

@@ -1,68 +1,68 @@
 const selColor = "#FF5C26";
 let d = [{
-	label:"Altitude,m",
-	data:[], 
-	yaxis: 2
+  label:"Altitude,m",
+  data:[], 
+  yaxis: 2
 },
 {
-	label:"Speed,km/h",
-	data:[]
+  label:"Speed,km/h",
+  data:[]
 }];
 
 let options = {
-	xaxis: {
-		mode: "time",
-		tickLength: 5,
-		zoomRange: [60 * 1000, 1000 * 3600 * 24 * 365]
-	},
-	/*selection: {
-		mode: "x",
-		color: selColor
-	},*/
-	yaxes: [{
-		min: 0,
-		zoomRange: false,
-		panRange: false
-	}, {
-		position: "right",
-		alignTicksWithAxis: 1,
-		min: 0,
-		zoomRange: false,
-		panRange: false
-	}],
-	zoom: {
-		interactive: true
-	},
-	pan: {
-		interactive: true,
-		cursor: "move",
-		frameRate: 60
-	}
+  xaxis: {
+    mode: "time",
+    tickLength: 5,
+    zoomRange: [60 * 1000, 1000 * 3600 * 24 * 365]
+  },
+  /*selection: {
+    mode: "x",
+    color: selColor
+  },*/
+  yaxes: [{
+    min: 0,
+    zoomRange: false,
+    panRange: false
+  }, {
+    position: "right",
+    alignTicksWithAxis: 1,
+    min: 0,
+    zoomRange: false,
+    panRange: false
+  }],
+  zoom: {
+    interactive: true
+  },
+  pan: {
+    interactive: true,
+    cursor: "move",
+    frameRate: 60
+  }
 };
 let optO = {
-	legend : {
-		show: false
-	},
-	series: {
-		lines: {
-			show: true,
-			lineWidth: 1
-		},
-		shadowSize: 0
-	},
-	xaxis: {
-		//ticks: [],
-		mode: "time"
-	},
-	yaxis: {
-		ticks: [],
-		autoscaleMargin: 0.1
-	},
-	selection: {
-		mode: "x",
-		color: selColor,
-		minSize: 0
-	}
+  legend : {
+    show: false
+  },
+  series: {
+    lines: {
+      show: true,
+      lineWidth: 1
+    },
+    shadowSize: 0
+  },
+  xaxis: {
+    //ticks: [],
+    mode: "time"
+  },
+  yaxis: {
+    ticks: [],
+    autoscaleMargin: 0.1
+  },
+  selection: {
+    mode: "x",
+    color: selColor,
+    minSize: 0
+  }
 };
 
 let gps = new GPS();
@@ -70,7 +70,7 @@ let plot, overview;
 
 window.onload = function() {
 	Cesium.Ion.defaultAccessToken = '<token>';
-	
+  
   var extent = {west: -0.2540382220862719, south: 0.6872565916005104, east: 0.6129855406042352, north: 0.9377043806513488};
 
   Cesium.Camera.DEFAULT_VIEW_RECTANGLE = extent;
@@ -83,15 +83,15 @@ window.onload = function() {
   });
 
   viewer = new Cesium.Viewer('map', {
-		fullscreenElement: "map",
-		terrainProvider : Cesium.createWorldTerrain({
-			requestVertexNormals: true,
-			requestWaterMask: true
-		}),
-		//shadows: true
-	});
-	viewer.scene.globe.enableLighting = true;
-	viewer.resolutionScale = 1.2;
+    fullscreenElement: "map",
+    terrainProvider : Cesium.createWorldTerrain({
+      requestVertexNormals: true,
+      requestWaterMask: true
+    }),
+    //shadows: true
+  });
+  viewer.scene.globe.enableLighting = true;
+  viewer.resolutionScale = 1.2;
   viewer.scene.screenSpaceCameraController.enableTilt = !('ontouchstart' in window);
   viewer.baseLayerPicker.viewModel.selectedImagery = new Cesium.ProviderViewModel({
     name: 'Bing Maps Aerial with Labels',
@@ -101,47 +101,47 @@ window.onload = function() {
       return bingMapsProvider
     }
   });
-	var paths = [];
+  var paths = [];
 
-	
-	fetch("markers")
-	.then(function(response) {
-		return response.json();
-	})
-	.then(function(jsonResponse) {
-		gps.parse(jsonResponse);
-		for(const t of gps.tracks) {
-			const i = t.info;
-			var desc = "Start: <b>" + timeFormat(i.startTime, "datetime") + 
-						"</b><br/>Finish: <b>" + timeFormat(i.endTime, "datetime") + "</b><br/>" +
-						"Track time (full): "+ timeFormat(i.totalTime) + "<br>" +
-						"Track time (mov.): "+ timeFormat(i.movTime) + "<br>" +
-						"Alt: " + i.totalAscend.toFixed(1) + "m &uarr;" + i.ascend.toFixed(1) + "m  &darr;" + i.descend.toFixed(1) + "m"+
-						"<br/>Distance: "+(i.distance/1000).toFixed(1)+"km<br/>" +
-						"top speed: "+i.topSpeed.toFixed(1)+
-						"km/h<br/>"+
-						"average speed: "+i.avgSpeed.toFixed(1)+"km/h<br/>";
+  
+  fetch("markers")
+  .then(function(response) {
+    return response.json();
+  })
+  .then(function(jsonResponse) {
+    gps.parse(jsonResponse);
+    for(const t of gps.tracks) {
+      const i = t.info;
+      var desc = "Start: <b>" + timeFormat(i.startTime, "datetime") + 
+            "</b><br/>Finish: <b>" + timeFormat(i.endTime, "datetime") + "</b><br/>" +
+            "Track time (full): "+ timeFormat(i.totalTime) + "<br>" +
+            "Track time (mov.): "+ timeFormat(i.movTime) + "<br>" +
+            "Alt: " + i.totalAscend.toFixed(1) + "m &uarr;" + i.ascend.toFixed(1) + "m  &darr;" + i.descend.toFixed(1) + "m"+
+            "<br/>Distance: "+(i.distance/1000).toFixed(1)+"km<br/>" +
+            "top speed: "+i.topSpeed.toFixed(1)+
+            "km/h<br/>"+
+            "average speed: "+i.avgSpeed.toFixed(1)+"km/h<br/>";
 
-			const col = Cesium.Color.fromCssColorString(i.color)
-			var path = viewer.entities.add({
-				label : "Track "+i.id,
+      const col = Cesium.Color.fromCssColorString(i.color)
+      var path = viewer.entities.add({
+        label : "Track "+i.id,
         id: "track_"+i.id,
         description: desc,
-				polyline : {
-					positions : t.data.map(p => Cesium.Cartesian3.fromDegrees(p.lng, p.lat, p.alt)),
-					width : 5,
-					material : new Cesium.PolylineOutlineMaterialProperty({
-						color : col,
-					}),
-					//clampToGround: true,
-					depthFailMaterial: new Cesium.PolylineOutlineMaterialProperty({
-						color : Cesium.Color.fromAlpha(col, 0.6),
-					}),
-					shadows: Cesium.ShadowMode.ENABLED
-				}
-			});
-			paths.push(path);
-		}
+        polyline : {
+          positions : t.data.map(p => Cesium.Cartesian3.fromDegrees(p.lng, p.lat, p.alt)),
+          width : 5,
+          material : new Cesium.PolylineOutlineMaterialProperty({
+            color : col,
+          }),
+          //clampToGround: true,
+          depthFailMaterial: new Cesium.PolylineOutlineMaterialProperty({
+            color : Cesium.Color.fromAlpha(col, 0.6),
+          }),
+          shadows: Cesium.ShadowMode.ENABLED
+        }
+      });
+      paths.push(path);
+    }
     var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
     handler.setInputAction(function (movement) {
       var pick = viewer.scene.pick(movement.position);
@@ -153,138 +153,138 @@ window.onload = function() {
     }, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
 
     viewer.flyTo(paths[paths.length - 1], { offset: new Cesium.HeadingPitchRange(0, -90, 0)});
-		let regionIndex = 0;
-		for(let r=0; r<gps.regions.length; r++) {
+    let regionIndex = 0;
+    for(let r=0; r<gps.regions.length; r++) {
       setTimeout(addInfo, (gps.regions.length-1-regionIndex) * 300, bingMapsProvider, viewer, regionIndex, gps.regions[r]);
-			regionIndex++;
-		}
-		const i = gps.getInfo();
-		var html =
-			"Time (full): "+ timeFormat(i.totalTime) + "<br>" +
-			"Time (mov.): "+ timeFormat(i.movTime) + "<br>" +
-			"Alt: &uarr;" + (i.ascend/1000).toFixed(1) + "km  &darr;" + (i.descend/1000).toFixed(1) + "km<br>"+
-			"Distance: <b>"+(i.distance/1000).toFixed(1)+"km</b><br/>" +
-			"Top speed: <b>"+i.topSpeed.toFixed(1) + "km/h</b><br/>"+
-			"Avg. speed: "+i.avgSpeed.toFixed(1)+"km/h<br/>"+
-			"Data points: "+i.points+"<br/>"+
-			"<a href='javascript:show(" + (gps.tracks.length-1) + ")'>Graph</a>";
-		
-		$(".status").append(html);
+      regionIndex++;
+    }
+    const i = gps.getInfo();
+    var html =
+      "Time (full): "+ timeFormat(i.totalTime) + "<br>" +
+      "Time (mov.): "+ timeFormat(i.movTime) + "<br>" +
+      "Alt: &uarr;" + (i.ascend/1000).toFixed(1) + "km  &darr;" + (i.descend/1000).toFixed(1) + "km<br>"+
+      "Distance: <b>"+(i.distance/1000).toFixed(1)+"km</b><br/>" +
+      "Top speed: <b>"+i.topSpeed.toFixed(1) + "km/h</b><br/>"+
+      "Avg. speed: "+i.avgSpeed.toFixed(1)+"km/h<br/>"+
+      "Data points: "+i.points+"<br/>"+
+      "<a href='javascript:show(" + (gps.tracks.length-1) + ")'>Graph</a>";
+    
+    $(".status").append(html);
 
-	});
+  });
 
-	plot = $.plot("#placeholder", d, options);
-	overview = $.plot("#overview", d, optO);
-	// now connect the two
+  plot = $.plot("#placeholder", d, options);
+  overview = $.plot("#overview", d, optO);
+  // now connect the two
 
-	$("#placeholder").bind("plotpan", updateOverview);
-	$("#placeholder").bind("plotzoom", updateOverview);
-	function updateOverview(event, ranges) {
-		var axes = plot.getAxes();
-		for(var axis in axes) {
-			axes[axis].from = axes[axis].min;
-			axes[axis].to = axes[axis].max;
-		}
-		overview.setSelection(axes, true);
-	}
-	$("#overview").bind("plotselected", function (event, ranges) {
-		$.each(plot.getXAxes(), function(_, axis) {
-			var opts = axis.options;
-			opts.min = ranges.xaxis.from;
-			opts.max = ranges.xaxis.to;
-		});
-		plot.setupGrid();
-		plot.draw();
-	});
-	
-	$("#whole").click(function () {
-		setZoom(false,false);
-	});
-	$("#right").click(function () {
-		var min = plot.getXAxes()[0].options.min;
-		var max = plot.getXAxes()[0].options.max;
-		if(min != null && max != null)
-			setZoom((min+max)/2, max * 1.5 - min/2);
-	});
-	$("#left").click(function () {
-		var min = plot.getXAxes()[0].options.min;
-		var max = plot.getXAxes()[0].options.max;
-		if(min != null && max != null)
-			setZoom(min * 1.5 - max/2, (min+max)/2);
-	});
+  $("#placeholder").bind("plotpan", updateOverview);
+  $("#placeholder").bind("plotzoom", updateOverview);
+  function updateOverview(event, ranges) {
+    var axes = plot.getAxes();
+    for(var axis in axes) {
+      axes[axis].from = axes[axis].min;
+      axes[axis].to = axes[axis].max;
+    }
+    overview.setSelection(axes, true);
+  }
+  $("#overview").bind("plotselected", function (event, ranges) {
+    $.each(plot.getXAxes(), function(_, axis) {
+      var opts = axis.options;
+      opts.min = ranges.xaxis.from;
+      opts.max = ranges.xaxis.to;
+    });
+    plot.setupGrid();
+    plot.draw();
+  });
+  
+  $("#whole").click(function () {
+    setZoom(false,false);
+  });
+  $("#right").click(function () {
+    var min = plot.getXAxes()[0].options.min;
+    var max = plot.getXAxes()[0].options.max;
+    if(min != null && max != null)
+      setZoom((min+max)/2, max * 1.5 - min/2);
+  });
+  $("#left").click(function () {
+    var min = plot.getXAxes()[0].options.min;
+    var max = plot.getXAxes()[0].options.max;
+    if(min != null && max != null)
+      setZoom(min * 1.5 - max/2, (min+max)/2);
+  });
 }
 
 let firstShow = true;
 
 function show(id) {
-	if(firstShow) {
-		firstShow = false;
-		
-		d[0].data = smoothOut(gps.data.map(v => v.alt), 0.85);
-		d[1].data = smoothOut(gps.data.map(v => v.speed), 0.85);
-		
-		for(let i=0; i<gps.data.length; i++) {
-			const cur = gps.data[i];
-			if((cur.timeDiff > 60 && cur.speed < 0.5) || cur.speed === null) {
-				d[0].data[i] = null;
-				d[1].data[i] = null;
-			}
-			d[0].data[i] = [cur.timestamp * 1000, d[0].data[i]];
-			d[1].data[i] = [cur.timestamp * 1000, d[1].data[i]];
-		}
-		let opts = plot.getXAxes()[0].options;
-		opts.panRange = [
-			gps.data[0].timestamp * 1000,
-			gps.data[gps.data.length-1].timestamp * 1000
-		];
-		
-		plot.setData(d);
-		plot.setupGrid(); //only necessary if your new data will change the axes or grid
-		plot.draw();
-		overview.setData(d);
-		overview.setupGrid(); //only necessary if your new data will change the axes or grid
-		overview.draw();
-		
-		for(let t of gps.tracks) {
-			$("#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>");
-		}
-	}
-	
-	$("#shadow").css("visibility", "visible");
-	$("#frame").css("visibility", "visible");
-	console.log(id);
-	
-	let start = gps.tracks[id].info.startTime*1000;
-	let end = gps.tracks[id].info.endTime*1000;
-	
-	setZoom(start,end);
-	
-	$("#track").click(function () {
-		setZoom(start,end);
-	});
+  if(firstShow) {
+    firstShow = false;
+    
+    d[0].data = smoothOut(gps.data.map(v => v.alt), 0.85);
+    d[1].data = smoothOut(gps.data.map(v => v.speed), 0.85);
+    
+    for(let i=0; i<gps.data.length; i++) {
+      const cur = gps.data[i];
+      if((cur.timeDiff > 60 && cur.speed < 0.5) || cur.speed === null) {
+        d[0].data[i] = null;
+        d[1].data[i] = null;
+      }
+      d[0].data[i] = [Date.parse(cur.timestamp), d[0].data[i]];
+      d[1].data[i] = [Date.parse(cur.timestamp), d[1].data[i]];
+    }
+    let opts = plot.getXAxes()[0].options;
+    opts.panRange = [
+      Date.parse(gps.data[0].timestamp),
+      Date.parse(gps.data[gps.data.length-1].timestamp)
+    ];
+    
+    plot.setData(d);
+    plot.setupGrid(); //only necessary if your new data will change the axes or grid
+    plot.draw();
+    overview.setData(d);
+    overview.setupGrid(); //only necessary if your new data will change the axes or grid
+    overview.draw();
+    
+    for(let t of gps.tracks) {
+      $("#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>");
+    }
+  }
+  
+  $("#shadow").css("visibility", "visible");
+  $("#frame").css("visibility", "visible");
+  console.log(id);
+  
+  let start = gps.tracks[id].info.startTime*1000;
+  let end = gps.tracks[id].info.endTime*1000;
+  
+  setZoom(start,end);
+  
+  $("#track").click(function () {
+    setZoom(start,end);
+  });
 };
 function hide() {
-	$(".popup").css("visibility", "hidden");
+  $(".popup").css("visibility", "hidden");
 };
 function setZoom(Xmin, Xmax) {
-	var opts = plot.getXAxes()[0].options;
-	if(Xmax==false) {
-		Xmax = +new Date() - 60 * new Date().getTimezoneOffset() * 1000;
-	}
-	if(Xmin==false) {
-		Xmin = d[0].data[0][0];
-		overview.clearSelection();
-	} else {
-		overview.setSelection({ xaxis: { from: Xmin, to: Xmax}});
-	}
-	opts.min=Xmin;
-	opts.max=Xmax;
+  var opts = plot.getXAxes()[0].options;
+  if(Xmax==false) {
+    Xmax = +new Date() - 60 * new Date().getTimezoneOffset() * 1000;
+  }
+  if(Xmin==false) {
+    Xmin = d[0].data[0][0];
+    overview.clearSelection();
+  } else {
+    overview.setSelection({ xaxis: { from: Xmin, to: Xmax}});
+  }
+  opts.min=Xmin;
+  opts.max=Xmax;
 
-	plot.setupGrid();
-	plot.draw();
-	plot.clearSelection();
+  plot.setupGrid();
+  plot.draw();
+  plot.clearSelection();
 
-	return false;
+  return false;
 }
 
 function addInfo(bingMapsProvider, viewer, id, data) {
@@ -292,7 +292,7 @@ function addInfo(bingMapsProvider, viewer, id, data) {
   fetch(url).then(res=>res.json()).then(function(result) {
     if (result.statusDescription === 'OK') {
       const address = result?.resourceSets[0]?.resources[0]?.addressOfLocation[0];
-			if (address) {
+      if (address) {
 
         let txt = address.locality + " " + address.neighborhood;
         if (!address.locality && !address.neighborhood) {
@@ -302,45 +302,22 @@ function addInfo(bingMapsProvider, viewer, id, data) {
           txt += " " + address.countryIso2;
         }
 
-				$('.sidebar').append("<div class='info' id='info_" + id + "'>" + 
-					timeFormat(data.timestamp, "short") + "<br>" +
+        $('.sidebar').append("<div class='info' id='info_" + id + "'>" + 
+          timeFormat(data.timestamp, "short") + "<br>" +
           txt + "<br><b>" +
-					(data.distance/1000).toFixed(1) + " km</b></div>");
-					
-				$('#info_'+id).click(function() {
+          (data.distance/1000).toFixed(1) + " km</b></div>");
+          
+        $('#info_'+id).click(function() {
           viewer.camera.flyTo({
             destination: Cesium.Cartesian3.fromDegrees(data.lng, data.lat, data.distance),
             offset: new Cesium.HeadingPitchRange(0, -90, 0)
           });
-				});
-			} else {
-				console.log('No results found');
-			}
-		} else {
-			console.log('Geocoder failed due to: ' + status);
-		}
-	});
-}	
-
-function bindInfoWindow(path, map, infoWindow, html) {
-	google.maps.event.addListener(path, 'click', function(e) {
-		infoWindow.setContent(html);
-		console.log(e);
-		infoWindow.setPosition(e.latLng);
-		infoWindow.open(map);				
-	});
-	google.maps.event.addListener(path, 'mouseover', function() {
-		path.setOptions({
-			zIndex: 3,
-			strokeWeight: 6
-		});
-	});
-	google.maps.event.addListener(path, 'mouseout', function() {
-		setTimeout(function() {
-			path.setOptions({
-				zIndex: 2,
-				strokeWeight: 4
-			});
-		}, 1000);
-	});
+        });
+      } else {
+        console.log('No results found');
+      }
+    } else {
+      console.log('Geocoder failed due to: ' + status);
+    }
+  });
 }

+ 3 - 3
static/gps_logger/js/preprocessor.js

@@ -130,7 +130,7 @@ function readTxtFile(file) {
         lng: parseFloat(fields[2]),
         alt: parseFloat(fields[3]),
         hdop: parseInt(fields[4]),
-        speed: parseInt(fields[5])
+        speed: parseFloat(fields[5])
       })
     }
     process(file.name, result)
@@ -157,7 +157,7 @@ function parseNmeaFile(file) {
             lng: obj.lon,
             alt: ggaObj.alt,
             hdop: ggaObj.hdop,
-            speed: Math.round(obj.speed)
+            speed: obj.speed
           })
         }
         ggaObj = null
@@ -189,7 +189,7 @@ let markersToUpload;
 function process(name, markers) {
   d.push({
     label: `${name} ${markers.length}`,
-    data: markers.map(m => [m.lat, m.lng])
+    data: markers.map(m => [m.lng, m.lat])
   })
   let valid = []
   for(let i=0; i<markers.length-2; i++) {

+ 212 - 211
static/gps_logger/js/tracks.js

@@ -1,240 +1,241 @@
 
 class GPS {
-	parse(data) {
-		this.data = data;
-		this.index = 0;
-		
-		this.maxTimeDiff = 3600 * 6;
-		this.maxDist = 3000;
-		
-		this.tracks = [];
-		let t;
-		while(t = gps.next()) {
-			if (t.length > 1)
-				this.tracks.push(new Track(t));
-		}
-		this.regions = this.getRegions();
-	}
-	next() {
-		if(this.index >= this.data.length)
-			return;
-		let points = [];
-		while(this.index < this.data.length - 1) {
-			const cur = this.data[this.index];
-			const nxt = this.data[this.index+1];
-			
-			cur.timeDiff = Date.parse(nxt.timestamp) / 1000 - Date.parse(cur.timestamp) / 1000;
-			cur.distance = distance(cur, nxt);
-			
-			if(isNaN(cur.distance))
-				cur.distance = 0;
-			
-			if(cur.speed > 0) {
-				cur.speed = cur.speed * 1.852 / 100;
-			} else {
-				cur.speed = cur.distance / cur.timeDiff * 3.6;
-			}
-			
-			if(cur.timeDiff > this.maxTimeDiff || cur.distance > this.maxDist) {
-				cur.distance = null;
-				cur.timeDiff = null;
-				cur.speed = null;
-				break;
-			}
-			
-			points.push(cur);
-			this.index++;
-		}
-		if(this.index == this.data.length - 1) {
-			this.data[this.index].distance = 0;
-			this.data[this.index].timeDiff = 0;
-			this.data[this.index].speed = 0;
-		}
-		points.push(this.data[this.index]);
-		this.index++;
-		
-		return points;
-	}
-	getRegions() {
-		var trips = [];
-		var s = 0;
-		for(let i=0; i<=this.tracks.length; i++) {
-			while(i < this.tracks.length - 2) {
-				const cur = this.tracks[i].info;
-				const nxt = this.tracks[i+1].info;
-				if((nxt.startTime - cur.endTime) > 3600 * 24 * 7 || distance(cur.center, nxt.center) > 100000) {
-					break;
-				}
-				i++;
-			}
-			
-			let trip = this.tracks.slice(s, i);
-			let dist = trip.reduce((sum, p) => p.info.distance + sum, 0);
-			if (dist > 30000) {
-				trips.push({
-					lat: median(trip.map(v => v.info.center.lat)),
-					lng: median(trip.map(v => v.info.center.lng)),
-					timestamp: median(trip.map(v => v.info.startTime)),
-					distance: dist
-				});
-			}
-			s = i;
-		}
-		return trips;
-	}
-	getInfo() {
-		let i = {
-			points: this.data.length,
-			distance: this.tracks.reduce((sum, p) => p.info.distance + sum, 0),
-			topSpeed: this.tracks.reduce((max, p) => p.info.topSpeed > max ? p.info.topSpeed : max, 0),
-			ascend: this.tracks.reduce((sum, p) => p.info.ascend + sum, 0),
-			descend: this.tracks.reduce((sum, p) => p.info.descend + sum, 0),
-			movTime: this.tracks.reduce((sum, p) => p.info.movTime + sum, 0),
-			totalTime: this.tracks.reduce((sum, p) => p.info.totalTime + sum, 0)
-		};
-		i.avgSpeed = i.distance / i.movTime * 3.6;
-		return i;
-	}
+  parse(data) {
+    this.data = data;
+    this.index = 0;
+    
+    this.maxTimeDiff = 3600 * 6;
+    this.maxDist = 3000;
+    
+    this.tracks = [];
+    let t;
+    while(t = gps.next()) {
+      if (t.length > 1)
+        this.tracks.push(new Track(t));
+    }
+    this.regions = this.getRegions();
+  }
+  next() {
+    if(this.index >= this.data.length)
+      return;
+    let points = [];
+    while(this.index < this.data.length - 1) {
+      const cur = this.data[this.index];
+      const nxt = this.data[this.index+1];
+      
+      cur.timeDiff = Date.parse(nxt.timestamp) / 1000 - Date.parse(cur.timestamp) / 1000;
+      cur.distance = distance(cur, nxt);
+      
+      if(isNaN(cur.distance))
+        cur.distance = 0;
+      
+      if(cur.speed <= 0) {
+        cur.speed = cur.distance / cur.timeDiff * 3.6; // km/h
+      }
+      
+      if(cur.timeDiff > this.maxTimeDiff || cur.distance > this.maxDist) {
+        cur.distance = null;
+        cur.timeDiff = null;
+        cur.speed = null;
+        break;
+      }
+      
+      points.push(cur);
+      this.index++;
+    }
+    if(this.index == this.data.length - 1) {
+      this.data[this.index].distance = 0;
+      this.data[this.index].timeDiff = 0;
+      this.data[this.index].speed = 0;
+    }
+    points.push(this.data[this.index]);
+    this.index++;
+    
+    return points;
+  }
+  getRegions() {
+    var trips = [];
+    var s = 0;
+    for(let i=0; i<=this.tracks.length; i++) {
+      while(i < this.tracks.length - 2) {
+        const cur = this.tracks[i].info;
+        const nxt = this.tracks[i+1].info;
+        if((nxt.startTime - cur.endTime) > 3600 * 24 * 7 || distance(cur.center, nxt.center) > 100000) {
+          break;
+        }
+        i++;
+      }
+      
+      let trip = this.tracks.slice(s, i);
+      let dist = trip.reduce((sum, p) => p.info.distance + sum, 0);
+      if (dist > 30000) {
+        trips.push({
+          lat: median(trip.map(v => v.info.center.lat)),
+          lng: median(trip.map(v => v.info.center.lng)),
+          timestamp: median(trip.map(v => v.info.startTime)),
+          distance: dist
+        });
+      }
+      s = i;
+    }
+    return trips;
+  }
+  getInfo() {
+    let i = {
+      points: this.data.length,
+      distance: this.tracks.reduce((sum, p) => p.info.distance + sum, 0),
+      topSpeed: this.tracks.reduce((max, p) => p.info.topSpeed > max ? p.info.topSpeed : max, 0),
+      ascend: this.tracks.reduce((sum, p) => p.info.ascend + sum, 0),
+      descend: this.tracks.reduce((sum, p) => p.info.descend + sum, 0),
+      movTime: this.tracks.reduce((sum, p) => p.info.movTime + sum, 0),
+      totalTime: this.tracks.reduce((sum, p) => p.info.totalTime + sum, 0)
+    };
+    i.avgSpeed = i.distance / i.movTime * 3.6;
+    return i;
+  }
 }
 let trackId = 0;
 
 class Track {
-	constructor(data) {
-		this.data = data;
-		this.id = trackId++;
-		
-		this.deleted = this.clean();
-		this.info = this.getInfo();
-	}
-	
-	clean() {
-		let last = this.data[this.data.length-2];
-		let speed = last.speed, alt = last.alt;
-		let res = [];
-		for(let i=this.data.length-2; i>=0; i--) {
-			let cur = this.data[i];
-			let lst = this.data[i+1];
-			if(Math.abs(cur.speed - lst.speed) / cur.timeDiff > 10 || Math.abs(cur.speed - speed) > 50) {
-				res.push(this.data.splice(i, 1)[0]);
-			} else
-			speed = speed * 0.9 + cur.speed * 0.1;
-			alt = alt * 0.9 + cur.alt * 0.1;
-		}
-		return res;
-	}
-	getInfo() {
-		let i = {
-			id: this.id,
-			points: this.data.length,
-      color: getRandomColor(Date.parse(this.data[0].timestamp)),
-			distance: this.data.reduce((sum, p) => p.distance + sum, 0),
-			topSpeed: this.data.reduce((max, p) => p.speed > max ? p.speed : max, this.data[0].speed),
-			ascend: 0,
-			descend: 0,
-			startTime: Date.parse(this.data[0].timestamp) / 1000,
-			endTime: Date.parse(this.data[this.data.length-1].timestamp) / 1000,
-			movTime: this.data.reduce((sum, p) => p.speed > 2 ? p.timeDiff + sum : sum, 0),
-			minLat: this.data.reduce((min, p) => p.lat < min ? p.lat : min, this.data[0].lat),
-			maxLat: this.data.reduce((max, p) => p.lat > max ? p.lat : max, this.data[0].lat),
-			minLng: this.data.reduce((min, p) => p.lng < min ? p.lng : min, this.data[0].lng),
-			maxLng: this.data.reduce((max, p) => p.lng > max ? p.lng : max, this.data[0].lng),
-			
-		};
-		for(let n=1; n<this.data.length; n++) {
-			const cur = this.data[n].alt;
-			const lst = this.data[n-1].alt;
-			if(cur > lst)
-				i.ascend  += cur - lst;
-			else
-				i.descend += lst - cur;
-		}
-		i.totalAscend = i.ascend - i.descend;
-		i.avgSpeed = i.distance / i.movTime * 3.6;
-		i.center = {
-			lat: (i.maxLat+i.minLat)/2, 
-			lng: (i.maxLng+i.minLng)/2
-		},
-		i.totalTime = i.endTime - i.startTime;
-		
-		return i;
-	}
-	
+  constructor(data) {
+    this.data = data;
+    this.id = trackId++;
+    
+    this.deleted = this.clean();
+    this.info = this.getInfo();
+  }
+  
+  clean() {
+    let last = this.data[this.data.length-2];
+    let speed = last.speed, alt = last.alt;
+    let res = [];
+    for(let i=this.data.length-2; i>=0; i--) {
+      let cur = this.data[i];
+      let lst = this.data[i+1];
+      if(Math.abs(cur.speed - lst.speed) / cur.timeDiff > 10 || Math.abs(cur.speed - speed) > 50) {
+        res.push(this.data.splice(i, 1)[0]);
+      } else
+      speed = speed * 0.9 + cur.speed * 0.1;
+      alt = alt * 0.9 + cur.alt * 0.1;
+    }
+    return res;
+  }
+  getInfo() {
+    let i = {
+      id: this.id,
+      points: this.data.length,
+      color: getRandomColor(this.data[0].timestamp),
+      distance: this.data.reduce((sum, p) => p.distance + sum, 0),
+      topSpeed: this.data.reduce((max, p) => p.speed > max ? p.speed : max, this.data[0].speed),
+      ascend: 0,
+      descend: 0,
+      startTime: Date.parse(this.data[0].timestamp) / 1000,
+      endTime: Date.parse(this.data[this.data.length-1].timestamp) / 1000,
+      movTime: this.data.reduce((sum, p) => p.speed > 2 ? p.timeDiff + sum : sum, 0),
+      minLat: this.data.reduce((min, p) => p.lat < min ? p.lat : min, this.data[0].lat),
+      maxLat: this.data.reduce((max, p) => p.lat > max ? p.lat : max, this.data[0].lat),
+      minLng: this.data.reduce((min, p) => p.lng < min ? p.lng : min, this.data[0].lng),
+      maxLng: this.data.reduce((max, p) => p.lng > max ? p.lng : max, this.data[0].lng),
+      
+    };
+    for(let n=1; n<this.data.length; n++) {
+      const cur = this.data[n].alt;
+      const lst = this.data[n-1].alt;
+      if(cur > lst)
+        i.ascend  += cur - lst;
+      else
+        i.descend += lst - cur;
+    }
+    i.totalAscend = i.ascend - i.descend;
+    i.avgSpeed = i.distance / i.movTime * 3.6;
+    i.center = this.data[parseInt(this.data.length/2)];
+    i.totalTime = i.endTime - i.startTime;
+    
+    return i;
+  }
+  
 }
 
-function getRandomColor(i) {
+function getRandomColor(str) {
+  let arr = str.split('');
+  let i = Math.abs(arr.reduce(
+    (hashCode, currentVal) =>
+      (hashCode = currentVal.charCodeAt(0) + (hashCode << 6) + (hashCode << 16) - hashCode),
+    0
+  ));
   return "hsl(" + (i * 2 % 360) + ", " + (i * 3 % 60 + 40) + "%, " + (i * 5 % 40 + 30) + "%)";
 }
 function distance (a, b) {
  
-	// Convert degrees to radians
-	var lat1 = a.lat * Math.PI / 180.0;
-	var lon1 = a.lng * Math.PI / 180.0;
+  // Convert degrees to radians
+  var lat1 = a.lat * Math.PI / 180.0;
+  var lon1 = a.lng * Math.PI / 180.0;
  
-	var lat2 = b.lat * Math.PI / 180.0;
-	var lon2 = b.lng * Math.PI / 180.0;
+  var lat2 = b.lat * Math.PI / 180.0;
+  var lon2 = b.lng * Math.PI / 180.0;
  
-	// radius of earth in metres
-	var r = 6378100;
+  // radius of earth in metres
+  var r = 6378100;
  
-	// P
-	var rho1 = r * Math.cos(lat1);
-	var z1 = r * Math.sin(lat1);
-	var x1 = rho1 * Math.cos(lon1);
-	var y1 = rho1 * Math.sin(lon1);
+  // P
+  var rho1 = r * Math.cos(lat1);
+  var z1 = r * Math.sin(lat1);
+  var x1 = rho1 * Math.cos(lon1);
+  var y1 = rho1 * Math.sin(lon1);
  
-	// Q
-	var rho2 = r * Math.cos(lat2);
-	var z2 = r * Math.sin(lat2);
-	var x2 = rho2 * Math.cos(lon2);
-	var y2 = rho2 * Math.sin(lon2);
+  // Q
+  var rho2 = r * Math.cos(lat2);
+  var z2 = r * Math.sin(lat2);
+  var x2 = rho2 * Math.cos(lon2);
+  var y2 = rho2 * Math.sin(lon2);
  
-	// Dot product
-	var dot = (x1 * x2 + y1 * y2 + z1 * z2);
-	var cos_theta = dot / (r * r);
+  // Dot product
+  var dot = (x1 * x2 + y1 * y2 + z1 * z2);
+  var cos_theta = dot / (r * r);
  
-	var theta = Math.acos(cos_theta);
+  var theta = Math.acos(cos_theta);
  
-	// Distance in Metres
-	return r * theta;
+  // Distance in Metres
+  return r * theta;
 }
 
 function timeFormat (sec, x = "time") {
-	let d = new Date(sec*1000);
-	let t = new Date(null,null,null,null,null,sec).toTimeString().match(/\d{2}:\d{2}:\d{2}/)[0];
-	
-	switch(x) {
-		case "time":
-			if(sec < 3600*24)
-				return t;
-			else
-				return Math.floor(sec/3600/24) + "d " + t;
-		case "datetime":
-			return d.toLocaleDateString('de-DE', {
-				year: "numeric",
-				month: "2-digit",
-				day: "2-digit",
-				hour: "2-digit",
-				minute: "2-digit",
-				second: "2-digit",
-			});
-		case "date":
-			return d.toLocaleDateString('de-DE', {
-				year: "numeric",
-				month: "2-digit",
-				day: "2-digit"
-			});	
-		case "short":
-			return d.toLocaleDateString('de-DE', {
-				year: "numeric",
-				month: "2-digit"
-			});	
-	}
+  let d = new Date(sec*1000);
+  let t = new Date(null,null,null,null,null,sec).toTimeString().match(/\d{2}:\d{2}:\d{2}/)[0];
+  
+  switch(x) {
+    case "time":
+      if(sec < 3600*24)
+        return t;
+      else
+        return Math.floor(sec/3600/24) + "d " + t;
+    case "datetime":
+      return d.toLocaleDateString('de-DE', {
+        year: "numeric",
+        month: "2-digit",
+        day: "2-digit",
+        hour: "2-digit",
+        minute: "2-digit",
+        second: "2-digit",
+      });
+    case "date":
+      return d.toLocaleDateString('de-DE', {
+        year: "numeric",
+        month: "2-digit",
+        day: "2-digit"
+      }); 
+    case "short":
+      return d.toLocaleDateString('de-DE', {
+        year: "numeric",
+        month: "2-digit"
+      }); 
+  }
 }
 function median(values) {
 
-	values.sort( function(a,b) {return a - b;} );
+  values.sort( function(a,b) {return a - b;} );
 
-	var half = Math.floor(values.length/2);
+  var half = Math.floor(values.length/2);
 
   return values[half];
 }

+ 1 - 1
views.py

@@ -74,7 +74,7 @@ def marker_view(request):
         marker['lat'] = location.y
       del marker['location']
     data = json.dumps(values, cls=DjangoJSONEncoder)
-    cache.set('markers', data, 3600*24)
+    cache.set('markers', data, 3600*24*30)
   return HttpResponse(data, content_type='application/json')
 
 class DeleteCensoredView(PermissionRequiredMixin, View):