Răsfoiți Sursa

add js upload preprocessor

subDesTagesMitExtraKaese 2 ani în urmă
părinte
comite
92d8aee635

+ 18 - 0
migrations/0004_alter_marker_hdop.py

@@ -0,0 +1,18 @@
+# Generated by Django 3.2.13 on 2022-08-14 12:53
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('gps_logger', '0003_rename_location_trip_name'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='marker',
+            name='hdop',
+            field=models.IntegerField(blank=True, null=True),
+        ),
+    ]

+ 1 - 1
models.py

@@ -5,7 +5,7 @@ class Marker(models.Model):
   lat = models.FloatField()
   lng = models.FloatField()
   alt = models.FloatField()
-  hdop = models.IntegerField()
+  hdop = models.IntegerField(null=True, blank=True)
   speed = models.IntegerField(null=True, blank=True)
 
 class Trip(models.Model):

+ 106 - 17
static/gps_logger/js/preprocessor.js

@@ -2,7 +2,7 @@ const options = {
   legend: {
     position: "nw",
     show: true,
-    noColumns: 2,
+    noColumns: 3,
   },
   zoom: {
     interactive: true
@@ -32,38 +32,51 @@ window.onload = function() {
   const fileSelector = document.getElementById('file-selector')
 
   fileSelector.addEventListener('change', (event) => {
-    const fileList = event.target.files;
-    console.log(fileList);
+    const fileList = event.target.files
     importFiles(fileList)
   })
 
   const dropArea = document.getElementsByClassName('content')[0];
 
   dropArea.addEventListener('dragover', (event) => {
-    event.stopPropagation();
-    event.preventDefault();
+    event.stopPropagation()
+    event.preventDefault()
     // Style the drag-and-drop as a "copy file" operation.
-    event.dataTransfer.dropEffect = 'copy';
+    event.dataTransfer.dropEffect = 'copy'
   });
 
   dropArea.addEventListener('drop', (event) => {
-    event.stopPropagation();
-    event.preventDefault();
-    const fileList = event.dataTransfer.files;
-    console.log(fileList);
+    event.stopPropagation()
+    event.preventDefault()
+    const fileList = event.dataTransfer.files
     importFiles(fileList)
-  });
+  })
+
+  document.getElementById("upload-btn").addEventListener('click', (event) => {
+    const xmlhttp = new XMLHttpRequest()
+    xmlhttp.open("POST", "markers/create", true);
+    xmlhttp.setRequestHeader("Content-type", "application/json")
+    const token = document.querySelector('[name=csrfmiddlewaretoken]').value
+    xmlhttp.setRequestHeader('X-CSRFToken', token)
+    xmlhttp.onreadystatechange = () => {
+      if(xmlhttp.readyState == 4) {
+        alert(xmlhttp.responseText)
+      }
+    }
+    xmlhttp.send(JSON.stringify(markersToUpload))
+  })
 }
 function importFiles(fileList) {
   d = []
   plot = $.plot("#placeholder", d, options);
+  markersToUpload = []
   for (const file of fileList) {
-
     if(file.name?.toLowerCase().endsWith(".txt"))
       readTxtFile(file)
     else if (file.name?.toLowerCase().endsWith(".nmea"))
       parseNmeaFile(file)
   }
+  document.getElementById("upload-btn").style.visibility = "visible";
 }
 
 function readTxtFile(file) {
@@ -109,7 +122,7 @@ function parseNmeaFile(file) {
             lng: ggaObj.lon,
             alt: ggaObj.alt,
             hdop: ggaObj.hdop,
-            speed: obj.speed
+            speed: Math.round(obj.speed)
           })
         }
         ggaObj = null
@@ -136,20 +149,96 @@ function parseNmeaFile(file) {
   reader.readAsText(file)
 }
 
+let markersToUpload = [];
+
 function process(name, markers) {
   d.push({
     label: `${name} ${markers.length}`,
     data: markers.map(m => [m.lat, m.lng])
   })
-  markers = RamerDouglasPeucker2d(markers, 0.00001) //.0001 = 7m
+  let valid = []
+  for(let i=0; i<markers.length-2; i++) {
+    const m1 = markers[i];
+    const m2 = markers[i+1];
+    const m3 = markers[i+2];
+    const dx12 = distance(m1, m2)
+    const dx23 = distance(m2, m3)
+    const dx13 = distance(m2, m3)
+    const dt12 = (m2.timestamp - m1.timestamp) / 1000
+    const dt23 = (m2.timestamp - m1.timestamp) / 1000
+    const dt13 = (m3.timestamp - m1.timestamp) / 1000
+    
+    if(dt12 > 0 && dx12/dt12 > 50) {// > 50 m/s = 180 km/h
+      console.log(`n=${i} ${m1.timestamp} too fast: ${dx12/dt12} m/s`)
+      if(dx12/dt12 > dx13/dt13)
+        continue
+      else
+        markers.pop(i+1)
+    }
+    else if(dt23 > 0 && dx23/dt23 > 50) {// > 50 m/s = 180 km/h
+      console.log(`n=${i} ${m1.timestamp} too fast: ${dx23/dt23} m/s`)
+      if(dx23/dt23 > dx13/dt13)
+        markers.pop(i+1)
+      else
+        markers.pop(i+2)
+    }
+    else if(dx12 > 50 && dx12 > dx13) {
+      console.log(`n=${i} ${m1.timestamp} too far: ${dx12} m`)
+      continue
+    }
+    valid.push(m1)
+  }
+
+  markers = RamerDouglasPeucker2d(valid, 0.00001) //.0001 = 7m
   d.push({
     label: `RamerDouglasPeucker2d ${markers.length}`,
     data: markers.map(m => [m.lat, m.lng])
   })
+  markers = clean(markers)
+  d.push({
+    label: `cleaned ${markers.length}`,
+    data: markers.map(m => [m.lat, m.lng])
+  })
+
+  plot.setData(d)
+  plot.setupGrid() //only necessary if your new data will change the axes or grid
+  plot.draw()
+
+  markersToUpload = markersToUpload.concat(markers)
+}
+
+function clean(markers) {
+  let cleaned = [], tmp = [], oldHeading = 0
+
+  const threshold = 5; // meter
+
+  for(const marker of markers) {
+    if(!tmp.length) {
+      tmp.push(marker)
+    }
+    const heading = GPS.Heading(tmp[tmp.length-1].lat, tmp[tmp.length-1].lng, marker.lat, marker.lng)
+    if(tmp.some(m => distance(m, marker) < threshold) && Math.abs(heading - oldHeading) > 90) {
+      tmp.push(marker)
+      oldHeading = heading
+      continue
+    }
+    oldHeading = heading
+
+    cleaned.push(marker)
+    tmp = [marker]
+  }
+  return cleaned.concat(tmp.slice(1))
+}
+
+function distance(m1, m2) {
+  const theta = m1.lng - m2.lng
+  let dist = Math.sin(m1.lat * Math.PI / 180) * Math.sin(m2.lat * Math.PI / 180) + 
+             Math.cos(m1.lat * Math.PI / 180) * Math.cos(m2.lat * Math.PI / 180) * Math.cos(theta * Math.PI / 180);
+  dist = Math.acos(dist);
+  dist = dist / Math.PI * 180;
+  const miles = dist * 60 * 1.1515;
 
-  plot.setData(d);
-  plot.setupGrid(); //only necessary if your new data will change the axes or grid
-  plot.draw();
+  return (miles * 1609.344); // Meter
 }
 
 function perpendicularDistance2d(ptX, ptY, l1x, l1y, l2x, l2y) {

+ 4 - 4
static/gps_logger/js/tracks.js

@@ -23,7 +23,7 @@ class GPS {
 			const cur = this.data[this.index];
 			const nxt = this.data[this.index+1];
 			
-			cur.timeDiff = nxt.timestamp - cur.timestamp;
+			cur.timeDiff = Date.parse(nxt.timestamp) / 1000 - Date.parse(cur.timestamp) / 1000;
 			cur.distance = distance(cur, nxt);
 			
 			if(isNaN(cur.distance))
@@ -126,13 +126,13 @@ class Track {
 		let i = {
 			id: this.id,
 			points: this.data.length,
-      color: getRandomColor(this.data[0].timestamp),
+      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: this.data[0].timestamp,
-			endTime: this.data[this.data.length-1].timestamp,
+			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),

+ 1 - 0
templates/gps_logger/index.html

@@ -25,6 +25,7 @@
 
 {% block head %}
 <link href="{% static 'gps_logger/css/stylesheet.css' %}" rel="stylesheet" type="text/css">
+<link href="{% static 'gps_logger/cesium-1.60/Widgets/widgets.css' %}" rel="stylesheet" type="text/css">
 {% endblock %}
 
 {% block content %}

+ 3 - 1
templates/gps_logger/upload.html

@@ -28,11 +28,13 @@
 {% endblock %}
 
 {% block content %}
-<input type="file" id="file-selector" accept=".txt, .nmea" multiple>
+<input type="file" id="file-selector" accept=".txt, .nmea" multiple />
 
 <div class="demo-container">
   <div id="placeholder" class="demo-placeholder"></div>
 </div>
+{% csrf_token %}
+<input type="submit" id="upload-btn" style="visibility: hidden"/>
 {% endblock %}
 
 {% block body %}