diff --git a/js/format/Gpx.js b/js/format/Gpx.js
index 63afb2f..b7eddcc 100644
--- a/js/format/Gpx.js
+++ b/js/format/Gpx.js
@@ -1,25 +1,29 @@
BR.Gpx = {
- format: function (geoJson, turnInstructionMode = 0) {
- if (!geoJson) return '';
+ format: function (geoJson, turnInstructionMode = 0, transportMode = 'bike') {
+ if (!geoJson?.features) return '';
+
+ const trkNameTransform = {
+ trk: function (trk, feature, coordsList) {
+ // name as first tag, by using assign and in this order
+ return Object.assign(
+ {
+ name: feature.properties.name,
+ },
+ trk
+ );
+ },
+ };
+ let gpxTransform = trkNameTransform;
if (turnInstructionMode > 0) {
- BR.Gpx._addVoiceHints(geoJson, turnInstructionMode);
+ const voiceHints = BR.voiceHints(geoJson);
+ gpxTransform = voiceHints.getGpxTransform(turnInstructionMode, transportMode);
}
let gpx = togpx(geoJson, {
featureTitle: function () {},
featureDescription: function () {},
- transform: {
- trk: function (trk, feature, coordsList) {
- return {
- name: feature.properties.name,
- trkseg: trk.trkseg,
- };
- },
- wpt: function (wpt, feature, coord, index) {
- return Object.assign(wpt, feature.properties);
- },
- },
+ transform: gpxTransform,
});
const statsComment = BR.Gpx._statsComment(geoJson);
gpx = '' + statsComment + gpx;
@@ -27,13 +31,6 @@ BR.Gpx = {
return gpx;
},
- _addVoiceHints: function (geoJson, turnInstructionMode) {
- if (!geoJson.features) return;
-
- const voiceHints = BR.voiceHints(geoJson);
- voiceHints.add(turnInstructionMode);
- },
-
//
_statsComment: function (geoJson) {
const props = geoJson.features?.[0].properties;
@@ -68,8 +65,8 @@ BR.Gpx = {
// Remove all the newlines and then remove all the spaces between tags
xml = xml.replace(/\s*(\r\n|\n|\r)\s*/gm, ' ').replace(/>\s+<');
- // break into lines, keep trkpt with subelement ele in single line
- const reg = /(>)(<)(?!ele|\/trkpt)(\/?)/g;
+ // break into lines
+ const reg = /(>)(<)(\/?)/g;
let pad = 0;
xml = xml.replace('', '');
diff --git a/js/format/VoiceHints.js b/js/format/VoiceHints.js
index 8321e89..1fcbfb6 100644
--- a/js/format/VoiceHints.js
+++ b/js/format/VoiceHints.js
@@ -9,7 +9,7 @@
}
}
- class RoundaboutCommand {
+ class RoundaboutCommand extends Command {
constructor(command, exitNumber) {
this.name = command.name + exitNumber;
this.locus = command.locus + exitNumber;
@@ -19,7 +19,7 @@
}
}
- class RoundaboutLeftCommand {
+ class RoundaboutLeftCommand extends RoundaboutCommand {
constructor(command, exitNumber) {
this.name = command.name + -exitNumber;
this.locus = command.locus + -exitNumber;
@@ -64,17 +64,99 @@
}
},
- add: function (turnInstructionMode) {
- if (!this.voicehints) return;
+ createWpt: function (coord, properties) {
+ return Object.assign(
+ {
+ '@lat': coord[1],
+ '@lon': coord[0],
+ },
+ properties
+ );
+ },
- for (const hint of this.voicehints) {
- const [indexInTrack, commandId, exitNumber] = hint;
- const coord = this.track.geometry.coordinates[indexInTrack];
- const cmd = this.getCommand(commandId, exitNumber);
- const properties = { name: cmd.message, sym: cmd.symbol.toLowerCase(), type: cmd.symbol };
+ getGpxTransform: function (turnInstructionMode, transportMode) {
+ const mode = turnInstructionMode;
+ const transform = {
+ gpx: function (gpx, features) {
+ for (const hint of this.voicehints) {
+ const [indexInTrack, commandId, exitNumber, distance, time] = hint;
+ let speed;
+ if (time > 0) {
+ speed = distance / time;
+ }
- this.geoJson.features.push(turf.point(coord.slice(0, 2), properties));
- }
+ const coord = this.track.geometry.coordinates[indexInTrack];
+ const cmd = this.getCommand(commandId, exitNumber);
+ if (!cmd) {
+ console.error(`no voicehint command for id: ${commandId} (${hint})`);
+ continue;
+ }
+
+ let properties;
+ if (mode === 2) {
+ const extensions = {};
+ // TODO 'locus:' namespace gets removed
+ extensions['locus:rteDistance'] = distance;
+ if (time > 0) {
+ extensions['locus:rteTime'] = time;
+ extensions['locus:rteSpeed'] = speed;
+ }
+ extensions['locus:rtePointAction'] = cmd.locus;
+
+ properties = {
+ ele: coord[2],
+ name: cmd.message,
+ extensions: extensions,
+ };
+ } else if (mode === 5) {
+ properties = { name: cmd.message, sym: cmd.symbol.toLowerCase(), type: cmd.symbol };
+ } else {
+ console.error('unhandled turnInstructionMode: ' + mode);
+ }
+
+ const wpt = this.createWpt(coord, properties);
+ gpx.wpt.push(wpt);
+ }
+
+ if (mode === 2) {
+ // hack to insert attribute after the other `xmlns`s
+ gpx = Object.assign(
+ {
+ '@xmlns': gpx['@xmlns'],
+ '@xmlns:xsi': gpx['@xmlns:xsi'],
+ '@xmlns:locus': 'http://www.locusmap.eu',
+ },
+ gpx
+ );
+ }
+ return gpx;
+ }.bind(this),
+ trk: function (trk, feature, coordsList) {
+ const properties = {
+ name: feature.properties.name,
+ };
+
+ const getLocusRouteType = (transportMode) => {
+ switch (transportMode) {
+ case 'car':
+ return 0;
+ case 'bike':
+ return 5;
+ default:
+ return 3; // foot
+ }
+ };
+
+ if (mode === 2) {
+ properties.extensions = {
+ 'locus:rteComputeType': getLocusRouteType(transportMode),
+ 'locus:rteSimpleRoundabouts': 1,
+ };
+ }
+ return Object.assign(properties, trk);
+ },
+ };
+ return transform;
},
getCommand: function (id, exitNumber) {
diff --git a/tests/format/Gpx.test.js b/tests/format/Gpx.test.js
index 2b3e232..e6ea31b 100644
--- a/tests/format/Gpx.test.js
+++ b/tests/format/Gpx.test.js
@@ -31,7 +31,7 @@ function adoptGpx(gpx) {
}
function read(fileName) {
- return adoptGpx(fs.readFileSync(path + fileName, 'utf8'));
+ return BR.Gpx.pretty(adoptGpx(fs.readFileSync(path + fileName, 'utf8')));
}
test('simple track', () => {
@@ -41,8 +41,18 @@ test('simple track', () => {
});
describe('voice hints', () => {
+ test('2-locus', () => {
+ let brouterGpx = read('2-locus.gpx');
+ brouterGpx = brouterGpx.replace(/<(\/?)locus:/g, '<$1'); // TODO 'locus:' namespace
+ brouterGpx = brouterGpx.replace(/.0<\/rteDistance/g, '\n\s*/, ''); // ignore (invalid) double tag
+
+ const gpx = BR.Gpx.format(geoJson, 2);
+ expect(gpx).toEqual(brouterGpx);
+ });
+
test('5-gpsies', () => {
- const brouterGpx = BR.Gpx.pretty(read('5-gpsies.gpx'));
+ const brouterGpx = read('5-gpsies.gpx');
const gpx = BR.Gpx.format(geoJson, 5);
expect(gpx).toEqual(brouterGpx);
});
diff --git a/tests/format/data/2-locus.gpx b/tests/format/data/2-locus.gpx
new file mode 100644
index 0000000..c90df93
--- /dev/null
+++ b/tests/format/data/2-locus.gpx
@@ -0,0 +1,25 @@
+
+
+
+ 101.5right140.024.909946441650395.6202449221614787
+ 103.5left90.09.6148529052734389.3605176165137064
+
+ 2-locus
+ 5
+ 1
+
+ 101.5
+ 101.5
+ 101.5
+ 101.5
+ 101.75
+ 103.5
+ 99.75
+
+
+
diff --git a/tests/format/data/track.json b/tests/format/data/track.json
index 6c8902f..fd1c324 100644
--- a/tests/format/data/track.json
+++ b/tests/format/data/track.json
@@ -13,8 +13,8 @@
"total-energy": "4412",
"cost": "533",
"voicehints": [
- [1,5,0],
- [5,2,0]
+ [1,5,0,140.0,24.90994644165039],
+ [5,2,0,90.0,9.614852905273438]
],
"messages": [
["Longitude", "Latitude", "Elevation", "Distance", "CostPerKm", "ElevCost", "TurnCost", "NodeCost", "InitialCost", "WayTags", "NodeTags"],
@@ -37,4 +37,3 @@
}
]
}
-