Skip to content

Commit 127e76b

Browse files
bonchanhaslinghuis
andauthored
Added GPX exporter (#614)
* added gpx exporter * Added GPX exporter * fixed sonar duplicated lines * Update js/webworkers/gpx-export-worker.js: proper declaration of loop variables Co-authored-by: haslinghuis <[email protected]> * removed unused code * added missing semicolon * added missing end of line & fixed sonar code smell Co-authored-by: haslinghuis <[email protected]>
1 parent 285831a commit 127e76b

File tree

7 files changed

+116
-17
lines changed

7 files changed

+116
-17
lines changed

css/main.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1224,7 +1224,8 @@ html:not(.has-log) #status-bar {
12241224
}
12251225

12261226
html:not(.has-gps) .view-map,
1227-
html:not(.has-gps) .map-container {
1227+
html:not(.has-gps) .map-container,
1228+
html:not(.has-gps) .btn-gpx-export {
12281229
display: none !important;
12291230
}
12301231

index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ <h1>Welcome to the Enhanced Blackbox Explorer!</h1>
186186
<a class="btn btn-primary btn-workspaces-export" data-toggle="tooltip" title="Export your workspace configurations to file">
187187
Export Workspaces...</a>
188188
<a class="btn btn-primary btn-csv-export" data-toggle="tooltip" title="Export your current graphs to CSV file"> Export CSV...</a>
189+
<a class="btn btn-primary btn-gpx-export" data-toggle="tooltip" title="Export your flight to GPX file"> Export GPX...</a>
189190
<span class="btn btn-primary btn-file" data-toggle="tooltip" title="Open another log file, video file, exported workspace file or configuration dump file">
190191
Open log file/video
191192
<input type="file" class="file-open" accept=".bbl,.txt,.cfl,.bfl,.log,.avi,.mov,.mp4,.mpeg,.json" multiple />
@@ -3185,6 +3186,7 @@ <h4 class="modal-title">Advanced User Settings</h4>
31853186
<script src="js/graph_spectrum.js"></script>
31863187
<script src="js/sticks.js"></script>
31873188
<script src="js/csv-exporter.js"></script>
3189+
<script src="js/gpx-exporter.js"></script>
31883190
<script src="js/gui.js"></script>
31893191
<script src="js/header_dialog.js"></script>
31903192
<script src="js/keys_dialog.js"></script>

js/csv-exporter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,4 @@ let CsvExporter = function(flightLog, opts={}) {
4444
return {
4545
dump: dump,
4646
};
47-
};
47+
};

js/gpx-exporter.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"use strict";
2+
3+
/**
4+
* @constructor
5+
* @param {FlightLog} flightLog
6+
*/
7+
let GpxExporter = function(flightLog) {
8+
9+
/**
10+
* @param {function} success is a callback triggered when export is done
11+
*/
12+
function dump(success) {
13+
let frames = _(flightLog.getChunksInTimeRange(flightLog.getMinTime(), flightLog.getMaxTime()))
14+
.map(chunk => chunk.frames).value(),
15+
worker = new Worker("/js/webworkers/gpx-export-worker.js");
16+
17+
worker.onmessage = event => {
18+
success(event.data);
19+
worker.terminate();
20+
};
21+
worker.postMessage({
22+
sysConfig: flightLog.getSysConfig(),
23+
fieldNames: flightLog.getMainFieldNames(),
24+
frames: frames,
25+
});
26+
}
27+
28+
// exposed functions
29+
return {
30+
dump: dump,
31+
};
32+
};

js/main.js

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,12 @@ function BlackboxLogViewer() {
111111
animationFrameIsQueued = false,
112112

113113
playbackRate = PLAYBACK_DEFAULT_RATE,
114-
114+
115115
graphZoom = GRAPH_DEFAULT_ZOOM,
116116
lastGraphZoom = GRAPH_DEFAULT_ZOOM, // QuickZoom function.
117-
117+
118118
mapGrapher = new MapGrapher();
119-
119+
120120

121121
function createNewBlackboxWindow(fileToOpen) {
122122

@@ -309,7 +309,7 @@ function BlackboxLogViewer() {
309309
if(flightLog.hasGpsData()) {
310310
mapGrapher.resize(width, height);
311311
}
312-
312+
313313
invalidateGraph();
314314
}
315315
}
@@ -918,28 +918,35 @@ function BlackboxLogViewer() {
918918
reader.readAsText(file);
919919
}
920920

921-
function exportCsv(file, options={}) {
922-
923-
function onSuccess(data) {
924-
console.debug("CSV export finished in", (performance.now() - startTime) / 1000, "secs");
921+
function createExportCallback(fileExtension, fileType, file, startTime) {
922+
const callback = function(data) {
923+
console.debug(`${fileExtension.toUpperCase()} export finished in ${(performance.now() - startTime) / 1000} secs`);
925924
if (!data) {
926925
console.debug("Empty data, nothing to save");
927926
return;
928927
}
929-
let blob = new Blob([data], {type: 'text/csv'}),
928+
let blob = new Blob([data], {type: fileType}),
930929
e = document.createEvent('MouseEvents'),
931930
a = document.createElement('a');
932-
a.download = file || $(".log-filename").text() + ".csv";
931+
a.download = file || $(".log-filename").text() + "." + fileExtension;
933932
a.href = window.URL.createObjectURL(blob);
934-
a.dataset.downloadurl = ['text/csv', a.download, a.href].join(':');
933+
a.dataset.downloadurl = [fileType, a.download, a.href].join(':');
935934
e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
936935
a.dispatchEvent(e);
937-
}
936+
};
937+
return callback;
938+
}
938939

939-
let startTime = performance.now();
940+
function exportCsv(file, options={}) {
941+
const onSuccess = createExportCallback('csv', 'text/csv', file, performance.now());
940942
CsvExporter(flightLog, options).dump(onSuccess);
941943
}
942944

945+
function exportGpx(file) {
946+
const onSuccess = createExportCallback('gpx', 'GPX File', file, performance.now());
947+
GpxExporter(flightLog).dump(onSuccess);
948+
}
949+
943950
function newGraphConfig(newConfig) {
944951
lastGraphConfig = graphConfig; // Remember the last configuration.
945952
graphConfig = newConfig;
@@ -1129,7 +1136,7 @@ function BlackboxLogViewer() {
11291136

11301137
$(".view-map").click(function() {
11311138
hasMap = !hasMap;
1132-
html.toggleClass("has-map", hasMap);
1139+
html.toggleClass("has-map", hasMap);
11331140
prefs.set('hasMap', hasMap);
11341141
if(flightLog.hasGpsData()) {
11351142
mapGrapher.initialize(userSettings);
@@ -1470,6 +1477,11 @@ function BlackboxLogViewer() {
14701477
exportCsv();
14711478
e.preventDefault();
14721479
});
1480+
$(".btn-gpx-export").click(function(e) {
1481+
setGraphState(GRAPH_STATE_PAUSED);
1482+
exportGpx();
1483+
e.preventDefault();
1484+
});
14731485

14741486
if (FlightLogVideoRenderer.isSupported()) {
14751487
$(".btn-video-export").click(function(e) {

js/webworkers/csv-export-worker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@ onmessage = function(event) {
4242

4343
postMessage(result);
4444

45-
};
45+
};

js/webworkers/gpx-export-worker.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
importScripts("/node_modules/lodash/lodash.min.js");
2+
3+
onmessage = function (event) {
4+
const header = `<?xml version="1.0" encoding="UTF-8"?>
5+
<gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.topografix.com/GPX/gpx_style/0/2 http://www.topografix.com/GPX/gpx_style/0/2/gpx_style.xsd" xmlns:gpx_style="http://www.topografix.com/GPX/gpx_style/0/2"
6+
version="1.1"
7+
creator="https://github.com/betaflight/blackbox-log-viewer">
8+
<metadata>
9+
<author>
10+
<name>Betaflight Blackbox Explorer</name>
11+
<link href="https://github.com/betaflight/blackbox-log-viewer"></link>
12+
</author>
13+
</metadata>`;
14+
15+
const footer = `</gpx>`;
16+
17+
const timeIndex = event.data.fieldNames.indexOf("time");
18+
const latIndex = event.data.fieldNames.indexOf("GPS_coord[0]");
19+
const lngIndex = event.data.fieldNames.indexOf("GPS_coord[1]");
20+
const altitudeIndex = event.data.fieldNames.indexOf("GPS_altitude");
21+
22+
let trkpts = "";
23+
for (const chunk of event.data.frames) {
24+
for (const frame of chunk) {
25+
if (!frame[latIndex] || !frame[lngIndex]) {
26+
continue;
27+
}
28+
const timeMillis = Math.floor(frame[timeIndex] / 1000);
29+
const lat = frame[latIndex] / 10000000;
30+
const lng = frame[lngIndex] / 10000000;
31+
const altitude = frame[altitudeIndex] / 10;
32+
33+
let date = new Date(event.data.sysConfig["Log start datetime"]);
34+
date.setTime(date.getTime() + timeMillis);
35+
36+
let trkpt = `<trkpt lat="${lat}" lon="${lng}">`;
37+
trkpt += `<ele>${altitude}</ele>`;
38+
trkpt += `<time>${date.toISOString()}</time>`;
39+
trkpt += `</trkpt>\n`;
40+
41+
trkpts += trkpt;
42+
}
43+
}
44+
45+
let trk = ` <trk>
46+
<trkseg>
47+
${trkpts}
48+
</trkseg>
49+
</trk>`;
50+
51+
postMessage(header + "\n" + trk + "\n" + footer);
52+
};

0 commit comments

Comments
 (0)