From a9d27b467473a3bd0f64af384a01d307fe222ff6 Mon Sep 17 00:00:00 2001 From: Norbert Renner Date: Thu, 18 Feb 2021 23:23:42 +0100 Subject: [PATCH] Format basic voice hint Use togpx fork for now (outdated), see PR: https://github.com/tyrasd/togpx/pull/11 --- js/format/Gpx.js | 40 +++++++++++++-- js/format/VoiceHints.js | 94 ++++++++++++++++++++++++++++++++++ package.json | 2 +- tests/format/Gpx.test.js | 13 ++++- tests/format/data/5-gpsies.gpx | 22 ++++++++ yarn.lock | 11 ++-- 6 files changed, 169 insertions(+), 13 deletions(-) create mode 100644 js/format/VoiceHints.js create mode 100644 tests/format/data/5-gpsies.gpx diff --git a/js/format/Gpx.js b/js/format/Gpx.js index e70a3f2..63afb2f 100644 --- a/js/format/Gpx.js +++ b/js/format/Gpx.js @@ -1,11 +1,37 @@ BR.Gpx = { - format: function (geoJson) { - const gpx = togpx(geoJson, { + format: function (geoJson, turnInstructionMode = 0) { + if (!geoJson) return ''; + + if (turnInstructionMode > 0) { + BR.Gpx._addVoiceHints(geoJson, turnInstructionMode); + } + + 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); + }, + }, }); const statsComment = BR.Gpx._statsComment(geoJson); - const xml = '' + statsComment + gpx; - return BR.Gpx.pretty(xml, 1); + gpx = '' + statsComment + gpx; + gpx = BR.Gpx.pretty(gpx); + return gpx; + }, + + _addVoiceHints: function (geoJson, turnInstructionMode) { + if (!geoJson.features) return; + + const voiceHints = BR.voiceHints(geoJson); + voiceHints.add(turnInstructionMode); }, // @@ -35,9 +61,13 @@ BR.Gpx = { // modified version of // https://gist.github.com/sente/1083506#gistcomment-2254622 // MIT License, Copyright (c) 2016 Stuart Powers, ES6 version by Jonathan Gruber - pretty: function (xml, indentSize = 2) { + pretty: function (xml, indentSize = 1) { const PADDING = ' '.repeat(indentSize); const newline = '\n'; + + // 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; let pad = 0; diff --git a/js/format/VoiceHints.js b/js/format/VoiceHints.js new file mode 100644 index 0000000..8321e89 --- /dev/null +++ b/js/format/VoiceHints.js @@ -0,0 +1,94 @@ +(function () { + class Command { + constructor(name, locus, orux, symbol, message) { + this.name = name; + this.locus = locus; + this.orux = orux; + this.symbol = symbol; + this.message = message; + } + } + + class RoundaboutCommand { + constructor(command, exitNumber) { + this.name = command.name + exitNumber; + this.locus = command.locus + exitNumber; + this.orux = command.orux + exitNumber; + this.symbol = command.symbol + exitNumber; + this.message = command.message + exitNumber; + } + } + + class RoundaboutLeftCommand { + constructor(command, exitNumber) { + this.name = command.name + -exitNumber; + this.locus = command.locus + -exitNumber; + this.orux = command.orux + exitNumber; + this.symbol = command.symbol + -exitNumber; + this.message = command.message + -exitNumber; + } + } + + BR.VoiceHints = L.Class.extend({ + statics: { + commands: (function () { + return { + 1: new Command('C', 1, 1002, 'Straight', 'straight'), + 2: new Command('TL', 4, 1000, 'Left', 'left'), + 3: new Command('TSLL', 3, 1017, 'TSLL', 'slight left'), + 4: new Command('TSHL', 5, 1019, 'TSHL', 'sharp left'), + 5: new Command('TR', 7, 1001, 'Right', 'right'), + 6: new Command('TSLR', 6, 1016, 'TSLR', 'slight right'), + 7: new Command('TSHR', 8, 1018, 'TSHR', 'sharp right'), + 8: new Command('KL', 9, 1015, 'TSLL', 'keep left'), + 9: new Command('KR', 10, 1014, 'TSLR', 'keep right'), + 10: new Command('TU', 13, 1003, 'TU', 'u-turn'), + 11: new Command('TRU', 14, 1003, 'TU', 'u-turn'), // Right U-turn + 12: new Command('OFFR'), // Off route + 13: new Command('RNDB', 26, 1008, 'RNDB', 'Take exit '), // Roundabout + 14: new Command('RNLB', 26, 1008, 'RNLB', 'Take exit '), // Roundabout left + }; + })(), + }, + + initialize: function (geoJson) { + this.geoJson = geoJson; + + for (const feature of geoJson.features) { + let voicehints = feature?.properties.voicehints; + if (voicehints) { + this.voicehints = voicehints; + this.track = feature; + break; + } + } + }, + + add: function (turnInstructionMode) { + if (!this.voicehints) return; + + 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 }; + + this.geoJson.features.push(turf.point(coord.slice(0, 2), properties)); + } + }, + + getCommand: function (id, exitNumber) { + let command = BR.VoiceHints.commands[id]; + if (id === 13) { + command = new RoundaboutCommand(command, exitNumber); + } else if (id === 14) { + command = new RoundaboutLeftCommand(command, exitNumber); + } + return command; + }, + }); + + BR.voiceHints = function (geoJson) { + return new BR.VoiceHints(geoJson); + }; +})(); diff --git a/package.json b/package.json index 823c922..fb828b0 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "mapbbcode": "MapBBCode/mapbbcode#v1.2.0", "osmtogeojson": "^3.0.0-beta.4", "regenerator-runtime": "^0.13.7", - "togpx": "^0.5.4", + "togpx": "JarnoLeConte/togpx#13c65a8", "topojson-client": "^3.1.0", "url-search-params": "~0.5.0" }, diff --git a/tests/format/Gpx.test.js b/tests/format/Gpx.test.js index fc780a7..2b3e232 100644 --- a/tests/format/Gpx.test.js +++ b/tests/format/Gpx.test.js @@ -1,5 +1,8 @@ BR = {}; +turf = require('@turf/turf'); togpx = require('togpx'); +require('leaflet'); +require('../../js/format/VoiceHints.js'); require('../../js/format/Gpx.js'); const fs = require('fs'); @@ -21,7 +24,7 @@ function adoptGpx(gpx) { .split(newline) .map((line) => line.replace(/lon="([^"]*)" lat="([^"]*)"/, 'lat="$2" lon="$1"')) .join(newline); - gpx = gpx.replace(/0">/g, '">'); + gpx = gpx.replace(/(lon|lat)="([-0-9]+.[0-9]+?)0+"/g, '$1="$2"'); // remove trailing zeros gpx = gpx.replace('\n', ''); return gpx; @@ -36,3 +39,11 @@ test('simple track', () => { const gpx = BR.Gpx.format(geoJson); expect(gpx).toEqual(brouterGpx); }); + +describe('voice hints', () => { + test('5-gpsies', () => { + const brouterGpx = BR.Gpx.pretty(read('5-gpsies.gpx')); + const gpx = BR.Gpx.format(geoJson, 5); + expect(gpx).toEqual(brouterGpx); + }); +}); diff --git a/tests/format/data/5-gpsies.gpx b/tests/format/data/5-gpsies.gpx new file mode 100644 index 0000000..6a1de19 --- /dev/null +++ b/tests/format/data/5-gpsies.gpx @@ -0,0 +1,22 @@ + + + + rightrightRight + leftleftLeft + + 5-gpsies + + 101.5 + 101.5 + 101.5 + 101.5 + 101.75 + 103.5 + 99.75 + + + diff --git a/yarn.lock b/yarn.lock index c76ddab..b92361e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7189,7 +7189,7 @@ just-debounce@^1.0.0: resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea" integrity sha1-h/zPrv/AtozRnVX2cilD+SnqNeo= -jxon@~2.0.0-beta.5: +jxon@~2.0.0-beta.2: version "2.0.0-beta.5" resolved "https://registry.yarnpkg.com/jxon/-/jxon-2.0.0-beta.5.tgz#3b6a94104f9801ee682fd056645ff5473d9b343e" integrity sha1-O2qUEE+YAe5oL9BWZF/1Rz2bND4= @@ -10565,13 +10565,12 @@ to-utf8@0.0.1: resolved "https://registry.yarnpkg.com/to-utf8/-/to-utf8-0.0.1.tgz#d17aea72ff2fba39b9e43601be7b3ff72e089852" integrity sha1-0Xrqcv8vujm55DYBvns/9y4ImFI= -togpx@^0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/togpx/-/togpx-0.5.4.tgz#b33dbb0541df04bd6ba4f50b86da953424bb7773" - integrity sha1-sz27BUHfBL1rpPULhtqVNCS7d3M= +togpx@JarnoLeConte/togpx#13c65a8: + version "0.5.1" + resolved "https://codeload.github.com/JarnoLeConte/togpx/tar.gz/13c65a82e3fbe8694f189d34fb7a339fa07700ec" dependencies: concat-stream "~1.0.1" - jxon "~2.0.0-beta.5" + jxon "~2.0.0-beta.2" optimist "~0.3.5" xmldom "~0.1.17"