|
@@ -2,7 +2,7 @@ const options = {
|
|
legend: {
|
|
legend: {
|
|
position: "nw",
|
|
position: "nw",
|
|
show: true,
|
|
show: true,
|
|
- noColumns: 2,
|
|
|
|
|
|
+ noColumns: 3,
|
|
},
|
|
},
|
|
zoom: {
|
|
zoom: {
|
|
interactive: true
|
|
interactive: true
|
|
@@ -32,38 +32,51 @@ window.onload = function() {
|
|
const fileSelector = document.getElementById('file-selector')
|
|
const fileSelector = document.getElementById('file-selector')
|
|
|
|
|
|
fileSelector.addEventListener('change', (event) => {
|
|
fileSelector.addEventListener('change', (event) => {
|
|
- const fileList = event.target.files;
|
|
|
|
- console.log(fileList);
|
|
|
|
|
|
+ const fileList = event.target.files
|
|
importFiles(fileList)
|
|
importFiles(fileList)
|
|
})
|
|
})
|
|
|
|
|
|
const dropArea = document.getElementsByClassName('content')[0];
|
|
const dropArea = document.getElementsByClassName('content')[0];
|
|
|
|
|
|
dropArea.addEventListener('dragover', (event) => {
|
|
dropArea.addEventListener('dragover', (event) => {
|
|
- event.stopPropagation();
|
|
|
|
- event.preventDefault();
|
|
|
|
|
|
+ event.stopPropagation()
|
|
|
|
+ event.preventDefault()
|
|
// Style the drag-and-drop as a "copy file" operation.
|
|
// Style the drag-and-drop as a "copy file" operation.
|
|
- event.dataTransfer.dropEffect = 'copy';
|
|
|
|
|
|
+ event.dataTransfer.dropEffect = 'copy'
|
|
});
|
|
});
|
|
|
|
|
|
dropArea.addEventListener('drop', (event) => {
|
|
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)
|
|
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) {
|
|
function importFiles(fileList) {
|
|
d = []
|
|
d = []
|
|
plot = $.plot("#placeholder", d, options);
|
|
plot = $.plot("#placeholder", d, options);
|
|
|
|
+ markersToUpload = []
|
|
for (const file of fileList) {
|
|
for (const file of fileList) {
|
|
-
|
|
|
|
if(file.name?.toLowerCase().endsWith(".txt"))
|
|
if(file.name?.toLowerCase().endsWith(".txt"))
|
|
readTxtFile(file)
|
|
readTxtFile(file)
|
|
else if (file.name?.toLowerCase().endsWith(".nmea"))
|
|
else if (file.name?.toLowerCase().endsWith(".nmea"))
|
|
parseNmeaFile(file)
|
|
parseNmeaFile(file)
|
|
}
|
|
}
|
|
|
|
+ document.getElementById("upload-btn").style.visibility = "visible";
|
|
}
|
|
}
|
|
|
|
|
|
function readTxtFile(file) {
|
|
function readTxtFile(file) {
|
|
@@ -109,7 +122,7 @@ function parseNmeaFile(file) {
|
|
lng: ggaObj.lon,
|
|
lng: ggaObj.lon,
|
|
alt: ggaObj.alt,
|
|
alt: ggaObj.alt,
|
|
hdop: ggaObj.hdop,
|
|
hdop: ggaObj.hdop,
|
|
- speed: obj.speed
|
|
|
|
|
|
+ speed: Math.round(obj.speed)
|
|
})
|
|
})
|
|
}
|
|
}
|
|
ggaObj = null
|
|
ggaObj = null
|
|
@@ -136,20 +149,96 @@ function parseNmeaFile(file) {
|
|
reader.readAsText(file)
|
|
reader.readAsText(file)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+let markersToUpload = [];
|
|
|
|
+
|
|
function process(name, markers) {
|
|
function process(name, markers) {
|
|
d.push({
|
|
d.push({
|
|
label: `${name} ${markers.length}`,
|
|
label: `${name} ${markers.length}`,
|
|
data: markers.map(m => [m.lat, m.lng])
|
|
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({
|
|
d.push({
|
|
label: `RamerDouglasPeucker2d ${markers.length}`,
|
|
label: `RamerDouglasPeucker2d ${markers.length}`,
|
|
data: markers.map(m => [m.lat, m.lng])
|
|
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) {
|
|
function perpendicularDistance2d(ptX, ptY, l1x, l1y, l2x, l2y) {
|