Format Locus voice hint, with additional hint data
This commit is contained in:
parent
a9d27b4674
commit
fd0ece0d31
5 changed files with 152 additions and 39 deletions
|
|
@ -1,25 +1,29 @@
|
||||||
BR.Gpx = {
|
BR.Gpx = {
|
||||||
format: function (geoJson, turnInstructionMode = 0) {
|
format: function (geoJson, turnInstructionMode = 0, transportMode = 'bike') {
|
||||||
if (!geoJson) return '';
|
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) {
|
if (turnInstructionMode > 0) {
|
||||||
BR.Gpx._addVoiceHints(geoJson, turnInstructionMode);
|
const voiceHints = BR.voiceHints(geoJson);
|
||||||
|
gpxTransform = voiceHints.getGpxTransform(turnInstructionMode, transportMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
let gpx = togpx(geoJson, {
|
let gpx = togpx(geoJson, {
|
||||||
featureTitle: function () {},
|
featureTitle: function () {},
|
||||||
featureDescription: function () {},
|
featureDescription: function () {},
|
||||||
transform: {
|
transform: gpxTransform,
|
||||||
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);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const statsComment = BR.Gpx._statsComment(geoJson);
|
const statsComment = BR.Gpx._statsComment(geoJson);
|
||||||
gpx = '<?xml version="1.0" encoding="UTF-8"?>' + statsComment + gpx;
|
gpx = '<?xml version="1.0" encoding="UTF-8"?>' + statsComment + gpx;
|
||||||
|
|
@ -27,13 +31,6 @@ BR.Gpx = {
|
||||||
return gpx;
|
return gpx;
|
||||||
},
|
},
|
||||||
|
|
||||||
_addVoiceHints: function (geoJson, turnInstructionMode) {
|
|
||||||
if (!geoJson.features) return;
|
|
||||||
|
|
||||||
const voiceHints = BR.voiceHints(geoJson);
|
|
||||||
voiceHints.add(turnInstructionMode);
|
|
||||||
},
|
|
||||||
|
|
||||||
// <!-- track-length = 319 filtered ascend = 2 plain-ascend = -1 cost=533 energy=.0kwh time=44s -->
|
// <!-- track-length = 319 filtered ascend = 2 plain-ascend = -1 cost=533 energy=.0kwh time=44s -->
|
||||||
_statsComment: function (geoJson) {
|
_statsComment: function (geoJson) {
|
||||||
const props = geoJson.features?.[0].properties;
|
const props = geoJson.features?.[0].properties;
|
||||||
|
|
@ -68,8 +65,8 @@ BR.Gpx = {
|
||||||
// Remove all the newlines and then remove all the spaces between tags
|
// Remove all the newlines and then remove all the spaces between tags
|
||||||
xml = xml.replace(/\s*(\r\n|\n|\r)\s*/gm, ' ').replace(/>\s+</g, '><');
|
xml = xml.replace(/\s*(\r\n|\n|\r)\s*/gm, ' ').replace(/>\s+</g, '><');
|
||||||
|
|
||||||
// break into lines, keep trkpt with subelement ele in single line
|
// break into lines
|
||||||
const reg = /(>)(<)(?!ele|\/trkpt)(\/?)/g;
|
const reg = /(>)(<)(\/?)/g;
|
||||||
let pad = 0;
|
let pad = 0;
|
||||||
|
|
||||||
xml = xml.replace('<metadata/>', '');
|
xml = xml.replace('<metadata/>', '');
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoundaboutCommand {
|
class RoundaboutCommand extends Command {
|
||||||
constructor(command, exitNumber) {
|
constructor(command, exitNumber) {
|
||||||
this.name = command.name + exitNumber;
|
this.name = command.name + exitNumber;
|
||||||
this.locus = command.locus + exitNumber;
|
this.locus = command.locus + exitNumber;
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoundaboutLeftCommand {
|
class RoundaboutLeftCommand extends RoundaboutCommand {
|
||||||
constructor(command, exitNumber) {
|
constructor(command, exitNumber) {
|
||||||
this.name = command.name + -exitNumber;
|
this.name = command.name + -exitNumber;
|
||||||
this.locus = command.locus + -exitNumber;
|
this.locus = command.locus + -exitNumber;
|
||||||
|
|
@ -64,17 +64,99 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
add: function (turnInstructionMode) {
|
createWpt: function (coord, properties) {
|
||||||
if (!this.voicehints) return;
|
return Object.assign(
|
||||||
|
{
|
||||||
|
'@lat': coord[1],
|
||||||
|
'@lon': coord[0],
|
||||||
|
},
|
||||||
|
properties
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
getGpxTransform: function (turnInstructionMode, transportMode) {
|
||||||
|
const mode = turnInstructionMode;
|
||||||
|
const transform = {
|
||||||
|
gpx: function (gpx, features) {
|
||||||
for (const hint of this.voicehints) {
|
for (const hint of this.voicehints) {
|
||||||
const [indexInTrack, commandId, exitNumber] = hint;
|
const [indexInTrack, commandId, exitNumber, distance, time] = hint;
|
||||||
|
let speed;
|
||||||
|
if (time > 0) {
|
||||||
|
speed = distance / time;
|
||||||
|
}
|
||||||
|
|
||||||
const coord = this.track.geometry.coordinates[indexInTrack];
|
const coord = this.track.geometry.coordinates[indexInTrack];
|
||||||
const cmd = this.getCommand(commandId, exitNumber);
|
const cmd = this.getCommand(commandId, exitNumber);
|
||||||
const properties = { name: cmd.message, sym: cmd.symbol.toLowerCase(), type: cmd.symbol };
|
if (!cmd) {
|
||||||
|
console.error(`no voicehint command for id: ${commandId} (${hint})`);
|
||||||
this.geoJson.features.push(turf.point(coord.slice(0, 2), properties));
|
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) {
|
getCommand: function (id, exitNumber) {
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ function adoptGpx(gpx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function read(fileName) {
|
function read(fileName) {
|
||||||
return adoptGpx(fs.readFileSync(path + fileName, 'utf8'));
|
return BR.Gpx.pretty(adoptGpx(fs.readFileSync(path + fileName, 'utf8')));
|
||||||
}
|
}
|
||||||
|
|
||||||
test('simple track', () => {
|
test('simple track', () => {
|
||||||
|
|
@ -41,8 +41,18 @@ test('simple track', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('voice hints', () => {
|
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, '</rteDistance'); // ignore .0 decimal
|
||||||
|
brouterGpx = brouterGpx.replace(/\n\s*<\/extensions>\n\s*<extensions>/, ''); // ignore (invalid) double tag
|
||||||
|
|
||||||
|
const gpx = BR.Gpx.format(geoJson, 2);
|
||||||
|
expect(gpx).toEqual(brouterGpx);
|
||||||
|
});
|
||||||
|
|
||||||
test('5-gpsies', () => {
|
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);
|
const gpx = BR.Gpx.format(geoJson, 5);
|
||||||
expect(gpx).toEqual(brouterGpx);
|
expect(gpx).toEqual(brouterGpx);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
25
tests/format/data/2-locus.gpx
Normal file
25
tests/format/data/2-locus.gpx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- track-length = 319 filtered ascend = 2 plain-ascend = -1 cost=533 energy=.0kwh time=44s -->
|
||||||
|
<gpx
|
||||||
|
xmlns="http://www.topografix.com/GPX/1/1"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:locus="http://www.locusmap.eu"
|
||||||
|
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
|
||||||
|
creator="BRouter-1.6.1" version="1.1">
|
||||||
|
<wpt lon="8.468340" lat="49.488794"><ele>101.5</ele><name>right</name><extensions><locus:rteDistance>140.0</locus:rteDistance><locus:rteTime>24.90994644165039</locus:rteTime><locus:rteSpeed>5.620244922161478</locus:rteSpeed><locus:rtePointAction>7</locus:rtePointAction></extensions></wpt>
|
||||||
|
<wpt lon="8.469971" lat="49.488151"><ele>103.5</ele><name>left</name><extensions><locus:rteDistance>90.0</locus:rteDistance><locus:rteTime>9.614852905273438</locus:rteTime><locus:rteSpeed>9.360517616513706</locus:rteSpeed><locus:rtePointAction>4</locus:rtePointAction></extensions></wpt>
|
||||||
|
<trk>
|
||||||
|
<name>2-locus</name>
|
||||||
|
<extensions><locus:rteComputeType>5</locus:rteComputeType></extensions>
|
||||||
|
<extensions><locus:rteSimpleRoundabouts>1</locus:rteSimpleRoundabouts></extensions>
|
||||||
|
<trkseg>
|
||||||
|
<trkpt lon="8.467714" lat="49.488115"><ele>101.5</ele></trkpt>
|
||||||
|
<trkpt lon="8.468340" lat="49.488794"><ele>101.5</ele></trkpt>
|
||||||
|
<trkpt lon="8.468586" lat="49.488698"><ele>101.5</ele></trkpt>
|
||||||
|
<trkpt lon="8.468743" lat="49.488636"><ele>101.5</ele></trkpt>
|
||||||
|
<trkpt lon="8.469161" lat="49.488473"><ele>101.75</ele></trkpt>
|
||||||
|
<trkpt lon="8.469971" lat="49.488151"><ele>103.5</ele></trkpt>
|
||||||
|
<trkpt lon="8.470610" lat="49.488842"><ele>99.75</ele></trkpt>
|
||||||
|
</trkseg>
|
||||||
|
</trk>
|
||||||
|
</gpx>
|
||||||
|
|
@ -13,8 +13,8 @@
|
||||||
"total-energy": "4412",
|
"total-energy": "4412",
|
||||||
"cost": "533",
|
"cost": "533",
|
||||||
"voicehints": [
|
"voicehints": [
|
||||||
[1,5,0],
|
[1,5,0,140.0,24.90994644165039],
|
||||||
[5,2,0]
|
[5,2,0,90.0,9.614852905273438]
|
||||||
],
|
],
|
||||||
"messages": [
|
"messages": [
|
||||||
["Longitude", "Latitude", "Elevation", "Distance", "CostPerKm", "ElevCost", "TurnCost", "NodeCost", "InitialCost", "WayTags", "NodeTags"],
|
["Longitude", "Latitude", "Elevation", "Distance", "CostPerKm", "ElevCost", "TurnCost", "NodeCost", "InitialCost", "WayTags", "NodeTags"],
|
||||||
|
|
@ -37,4 +37,3 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue