diff --git a/js/format/Gpx.js b/js/format/Gpx.js index 5064943..a56d139 100644 --- a/js/format/Gpx.js +++ b/js/format/Gpx.js @@ -24,6 +24,7 @@ BR.Gpx = { let gpx = togpx(geoJson, { featureTitle: function () {}, featureDescription: function () {}, + featureCoordTimes: function () {}, transform: gpxTransform, }); const statsComment = BR.Gpx._statsComment(geoJson); diff --git a/js/format/VoiceHints.js b/js/format/VoiceHints.js index 9cd5566..b1796f7 100644 --- a/js/format/VoiceHints.js +++ b/js/format/VoiceHints.js @@ -87,12 +87,38 @@ return transform; }, + _getDuration: function (voicehintsIndex) { + const timeList = this.track.properties.times; + const indexInTrack = this.voicehints[voicehintsIndex][0]; + const currTime = timeList[indexInTrack]; + const len = this.voicehints.length; + const nextIndex = voicehintsIndex < len - 1 ? this.voicehints[voicehintsIndex + 1][0] : timeList.length - 1; + const nextTime = timeList[nextIndex]; + + const duration = nextTime - currTime; + + // TODO remove + const time = this.voicehints[voicehintsIndex][4]; + const p = 5; + if (!(time.toPrecision(p) === duration.toPrecision(p))) { + console.error( + `${voicehintsIndex}: ${time.toPrecision(p)} =? ${duration.toPrecision(p)}, ${time} =? ${duration}` + ); + } + + return duration; + }, + _loopHints: function (hintCallback) { - for (const values of this.voicehints) { - const [indexInTrack, commandId, exitNumber, distance, time, geometry] = values; - const hint = { indexInTrack, commandId, exitNumber, distance, time, geometry }; - if (time > 0) { - hint.speed = distance / time; + for (const [i, values] of this.voicehints.entries()) { + const [indexInTrack, commandId, exitNumber, distance, time, angle, geometry] = values; + const hint = { indexInTrack, commandId, exitNumber, distance, time, angle, geometry }; + + // TODO remove server hint time + //hint.time = this._getDuration(i); + this._getDuration(i); + if (hint.time > 0) { + hint.speed = distance / hint.time; } const coord = this.track.geometry.coordinates[indexInTrack]; @@ -255,10 +281,76 @@ }, }); + BR.OsmAndVoiceHints = BR.VoiceHints.extend({ + _addToTransform: function (transform) { + transform.gpx = function (gpx, features) { + gpx['@creator'] = 'OsmAndRouter'; + + gpx.rte.push({ + rtept: this._createRoutePoints(gpx), + }); + + // reorder trk after rte + const trk = gpx.trk; + delete gpx.trk; + gpx.trk = trk; + + return gpx; + }.bind(this); + }, + + _createRoutePoints: function (gpx) { + const rteptList = []; + + const trkseg = gpx.trk[0].trkseg[0]; + let trkpt = trkseg.trkpt[0]; + const startTime = this.track.properties.times[this.voicehints[0][0]]; + rteptList.push({ + '@lat': trkpt['@lat'], + '@lon': trkpt['@lon'], + desc: 'start', + extensions: { time: Math.round(startTime), offset: 0 }, + }); + + this._loopHints((hint, cmd, coord) => { + const rtept = { + '@lat': coord[1], + '@lon': coord[0], + desc: cmd.message, + extensions: { + time: Math.round(hint.time), + turn: cmd.name, + 'turn-angle': hint.angle, + offset: hint.indexInTrack, + }, + }; + + if (hint.time === 0) { + delete properties.extensions.time; + } + + rteptList.push(rtept); + }); + + const lastIndex = trkseg.trkpt.length - 1; + trkpt = trkseg.trkpt[lastIndex]; + rteptList.push({ + '@lat': trkpt['@lat'], + '@lon': trkpt['@lon'], + desc: 'destination', + extensions: { time: 0, offset: lastIndex }, + }); + + return rteptList; + }, + }); + BR.voiceHints = function (geoJson, turnInstructionMode, transportMode) { switch (turnInstructionMode) { case 2: return new BR.LocusVoiceHints(geoJson, turnInstructionMode, transportMode); + case 3: + return new BR.OsmAndVoiceHints(geoJson, turnInstructionMode, transportMode); case 4: return new BR.CommentVoiceHints(geoJson, turnInstructionMode, transportMode); case 5: diff --git a/tests/format/Gpx.test.js b/tests/format/Gpx.test.js index f3a99ba..1fbdb4b 100644 --- a/tests/format/Gpx.test.js +++ b/tests/format/Gpx.test.js @@ -11,14 +11,16 @@ const geoJson = require('./data/track.json'); const path = 'tests/format/data/'; // resolve intended/accepted differences before comparing -function adoptGpx(gpx) { +function adoptGpx(gpx, replaceCreator = true) { const creator = 'togpx'; const name = 'Track'; const newline = '\n'; gpx = gpx.replace('=.0', '=0.0'); - gpx = gpx.replace(/creator="[^"]*"/, `creator="${creator}"`); - gpx = gpx.replace(`creator="${creator}" version="1.1"`, `version="1.1" \n creator="${creator}"`); + if (replaceCreator) { + gpx = gpx.replace(/creator="[^"]*"/, `creator="${creator}"`); + } + gpx = gpx.replace(/creator="([^"]*)" version="1.1"/, 'version="1.1" \n creator="$1"'); gpx = gpx.replace(/\n [^<]*<\/name>/, `\n ${name}`); gpx = gpx .split(newline) @@ -30,8 +32,8 @@ function adoptGpx(gpx) { return gpx; } -function read(fileName) { - return BR.Gpx.pretty(adoptGpx(fs.readFileSync(path + fileName, 'utf8'))); +function read(fileName, replaceCreator) { + return BR.Gpx.pretty(adoptGpx(fs.readFileSync(path + fileName, 'utf8'), replaceCreator)); } test('simple track', () => { @@ -51,6 +53,12 @@ describe('voice hints', () => { expect(gpx).toEqual(brouterGpx); }); + test('3-osmand', () => { + const brouterGpx = read('3-osmand.gpx', false); + const gpx = BR.Gpx.format(geoJson, 3); + expect(gpx).toEqual(brouterGpx); + }); + test('4-comment', () => { let brouterGpx = read('4-comment.gpx'); brouterGpx = brouterGpx.replace(/;\s*([-0-9]+.[0-9]+?)0+;/g, (match, p1) => `;${p1.padStart(10)};`); // remove trailing zeros diff --git a/tests/format/data/3-osmand.gpx b/tests/format/data/3-osmand.gpx new file mode 100644 index 0000000..3e3fd8b --- /dev/null +++ b/tests/format/data/3-osmand.gpx @@ -0,0 +1,54 @@ + + + + + + start + + + 0 + + + + right + + + TR + 89 + 1 + + + + left + + + TL + -90 + 5 + + + + destination + + + 6 + + + + + 3-osmand + + 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 8cf5a2c..c39541e 100644 --- a/tests/format/data/track.json +++ b/tests/format/data/track.json @@ -13,14 +13,15 @@ "total-energy": "4412", "cost": "533", "voicehints": [ - [1,5,0,140.0,24.90994644165039," 6(90)6 (0)6 (-89)2"], - [5,2,0,90.0,9.614852905273438," 6(-89)6 (0)6 (89)6"] + [1,5,0,140.0,24.90994644165039,89," 6(90)6 (0)6 (-89)2"], + [5,2,0,90.0,9.614852905273438,-90," 6(-89)6 (0)6 (89)6"] ], "messages": [ ["Longitude", "Latitude", "Elevation", "Distance", "CostPerKm", "ElevCost", "TurnCost", "NodeCost", "InitialCost", "WayTags", "NodeTags"], ["8468340", "49488794", "101", "89", "1000", "0", "0", "0", "0", "highway=residential surface=asphalt cycleway=lane oneway=yes lcn=yes smoothness=good route_bicycle_icn=yes route_bicycle_ncn=yes route_bicycle_rcn=yes", ""], ["8470671", "49488909", "99", "230", "1150", "0", "180", "0", "0", "highway=residential surface=asphalt oneway=yes smoothness=good", ""] - ] + ], + "times": [0.0,9.592433,12.270765,14.129882,19.406338,34.50238,44.117233] }, "geometry": { "type": "LineString",