diff --git a/js/control/Export.js b/js/control/Export.js index 64f8337..f59f305 100644 --- a/js/control/Export.js +++ b/js/control/Export.js @@ -69,6 +69,9 @@ BR.Export = L.Class.extend({ _formatTrack: function (format, name, includeWaypoints) { const track = BR.Export._concatTotalTrack(this.segments); + if (includeWaypoints) { + this._addRouteWaypoints(track); + } //console.log('GeoJson: ', trackGeoJson); //console.log('GeoJson: ', JSON.stringify(trackGeoJson, null, 4)); switch (format) { @@ -85,6 +88,24 @@ BR.Export = L.Class.extend({ console.error('Export format not implemented: ' + format); }, + _addRouteWaypoints: function (track) { + const routePoints = []; + for (const [i, latLng] of this.latLngs.entries()) { + let name = 'via' + i; + let type = 'via'; + if (i === 0) { + name = 'from'; + type = 'from'; + } else if (i === this.latLngs.length - 1) { + name = 'to'; + type = 'to'; + } + const properties = { name, type }; + routePoints.push(turf.point([latLng.lng, latLng.lat], properties)); + } + track.features.push(...routePoints); + }, + _validationMessage: function () { var trackname = this.trackname; var replaceRegex = new RegExp('[^' + this.tracknameAllowedChars + ']', 'g'); diff --git a/js/format/Gpx.js b/js/format/Gpx.js index 89772f4..b3ad6d4 100644 --- a/js/format/Gpx.js +++ b/js/format/Gpx.js @@ -2,9 +2,34 @@ BR.Gpx = { format: function (geoJson, turnInstructionMode = 0, transportMode = 'bike') { if (!geoJson?.features) return ''; - const trkNameTransform = { - comment: '', - trk: function (trk, feature, coordsList) { + class GpxTransform { + constructor(voiceHintsTransform) { + this.voiceHintsTransform = voiceHintsTransform; + this.comment = voiceHintsTransform?.comment || ''; + + if (this.voiceHintsTransform) { + Object.keys(this.voiceHintsTransform).forEach((member) => { + if (!GpxTransform.prototype.hasOwnProperty(member)) { + this[member] = this.voiceHintsTransform[member]; + } + }); + } + } + + wpt(wpt, feature, coord, index) { + // not in use right now, just to be safe in case of future overrides + wpt = (voiceHintsTransform?.wpt && voiceHintsTransform.wpt(wpt, feature, coord, index)) || wpt; + if (feature.properties.name) { + wpt.name = feature.properties.name; + } + if (feature.properties.type) { + wpt.type = feature.properties.type; + } + return wpt; + } + + trk(trk, feature, coordsList) { + trk = (voiceHintsTransform?.trk && voiceHintsTransform.trk(trk, feature, coordsList)) || trk; // name as first tag, by using assign and in this order return Object.assign( { @@ -12,14 +37,15 @@ BR.Gpx = { }, trk ); - }, - }; - let gpxTransform = trkNameTransform; + } + } + let voiceHintsTransform; if (turnInstructionMode > 1) { const voiceHints = BR.voiceHints(geoJson, turnInstructionMode, transportMode); - gpxTransform = voiceHints.getGpxTransform(); + voiceHintsTransform = voiceHints.getGpxTransform(); } + const gpxTransform = new GpxTransform(voiceHintsTransform); let gpx = togpx(geoJson, { featureTitle: function () {}, diff --git a/js/format/VoiceHints.js b/js/format/VoiceHints.js index e149a0f..eb95e42 100644 --- a/js/format/VoiceHints.js +++ b/js/format/VoiceHints.js @@ -48,14 +48,7 @@ comment: '', trk: function (trk, feature, coordsList) { const properties = this._getTrk(); - - return Object.assign( - { - name: feature.properties.name, - }, - properties, - trk - ); + return Object.assign(properties, trk); }.bind(this), }; @@ -146,12 +139,14 @@ } _addWaypoints(gpx) { + const waypoints = []; this._loopHints((hint, cmd, coord) => { const properties = this._getWpt(hint, cmd, coord); const wpt = this._createWpt(coord, properties); - gpx.wpt.push(wpt); + waypoints.push(wpt); }); + gpx.wpt.unshift(...waypoints); } _createWpt(coord, properties) { diff --git a/js/util/Diff.js b/js/util/Diff.js index a1c4a95..26f3407 100644 --- a/js/util/Diff.js +++ b/js/util/Diff.js @@ -112,10 +112,10 @@ BR.Diff.adoptGpx = function (gpx, replaceCreator = true) { gpx = gpx.replace(/(lon|lat)="([-0-9]+.[0-9]+?)0+"/g, '$1="$2"'); // remove trailing zeros // remove trailing zeros comment-style voicehints gpx = gpx.replace(/;\s*([-0-9]+.[0-9]+?)0+;/g, (match, p1) => `;${p1.padStart(10)};`); + gpx = gpx.replace(/>([-0-9]+?\.\d*0+)<\//g, (match, p1) => `>${+p1}\n', ''); // added - gpx = gpx.replace(/>([-.0-9]+?0+)<\//g, (match, p1) => `>${+p1}([^<]*)<\//g, (match, p1, p2) => `${p1}>${(+p2).toFixed(3)}\n?\s*/, ''); // ignore (invalid) double tag diff --git a/tests/control/Export.test.js b/tests/control/Export.test.js index 5d1f8a4..e95b9b3 100644 --- a/tests/control/Export.test.js +++ b/tests/control/Export.test.js @@ -1,7 +1,13 @@ BR = {}; +BR.conf = {}; +$ = require('jquery'); require('leaflet'); turf = require('@turf/turf'); require('../../js/control/Export.js'); +const fs = require('fs'); + +const indexHtmlString = fs.readFileSync('index.html', 'utf8'); +const indexHtml = new DOMParser().parseFromString(indexHtmlString, 'text/html'); // &lonlats=8.467712,49.488117;8.469354,49.488394;8.470556,49.488946;8.469982,49.489176 + turnInstructionMode=2 const segments = require('./data/segments.json'); @@ -24,6 +30,10 @@ function adopt(total, brouterTotal) { total.features[0].properties.name = brouterTotal.features[0].properties.name; } +beforeEach(() => { + document.body = indexHtml.body.cloneNode(true); +}); + test('total track', () => { const segmentsString = JSON.stringify(segments, null, 2); let total = BR.Export._concatTotalTrack(segments); @@ -38,3 +48,30 @@ test('total track', () => { adopt(total, brouterTotal); expect(total).toEqual(brouterTotal); }); + +test('include route points', () => { + const getLngCoord = (i) => track.features[i].geometry.coordinates[0]; + const getProperty = (i, p) => track.features[i].properties[p]; + const latLngs = [L.latLng(0, 0), L.latLng(1, 1), L.latLng(2, 2)]; + const trackFeature = turf.lineString([ + [0, 0], + [1, 1], + [2, 2], + ]); + const track = turf.featureCollection([trackFeature]); + const exportRoute = new BR.Export(); + + exportRoute.update(latLngs, null); + exportRoute._addRouteWaypoints(track); + + expect(track.features[0].geometry.type).toEqual('LineString'); + expect(getLngCoord(1)).toEqual(0); + expect(getLngCoord(2)).toEqual(1); + expect(getLngCoord(3)).toEqual(2); + expect(getProperty(1, 'name')).toEqual('from'); + expect(getProperty(2, 'name')).toEqual('via1'); + expect(getProperty(3, 'name')).toEqual('to'); + expect(getProperty(1, 'type')).toEqual('from'); + expect(getProperty(2, 'type')).toEqual('via'); + expect(getProperty(3, 'type')).toEqual('to'); +}); diff --git a/tests/format/Gpx.test.js b/tests/format/Gpx.test.js index 7e83640..574bf05 100644 --- a/tests/format/Gpx.test.js +++ b/tests/format/Gpx.test.js @@ -8,6 +8,9 @@ require('../../js/format/Gpx.js'); const fs = require('fs'); const geoJson = require('./data/track.json'); +// lonlats=8.467712,49.488117;8.469354,49.488394;8.470556,49.488946;8.469982,49.489176 + turnInstructionMode = 5 +// console log in Export._formatTrack +const waypointsGeoJson = require('./data/waypoints.json'); const path = 'tests/format/data/'; // resolve intended/accepted differences before comparing @@ -26,7 +29,8 @@ function adoptGpx(gpx, replaceCreator = true) { .split(newline) .map((line) => line.replace(/lon="([^"]*)" lat="([^"]*)"/, 'lat="$2" lon="$1"')) .join(newline); - gpx = gpx.replace(/(lon|lat)="([-0-9]+.[0-9]+?)0+"/g, '$1="$2"'); // remove trailing zeros + gpx = gpx.replace(/(lon|lat)="([-0-9]+\.[0-9]+?)0+"/g, '$1="$2"'); // remove trailing zeros + gpx = gpx.replace(/>([-0-9]+\.\d*0+)<\//g, (match, p1) => `>${+p1}\n', ''); return gpx; @@ -42,10 +46,15 @@ test('simple track', () => { expect(gpx).toEqual(brouterGpx); }); +test('waypoints', () => { + const brouterGpx = read('waypoints.gpx'); + const gpx = BR.Gpx.format(waypointsGeoJson, 5); + expect(gpx).toEqual(brouterGpx); +}); + describe('voice hints', () => { test('2-locus', () => { let brouterGpx = read('2-locus.gpx'); - brouterGpx = brouterGpx.replace(/.0<\/locus:rteDistance/g, '\n\s*/, ''); // ignore (invalid) double tag // ignore float rounding differences brouterGpx = brouterGpx.replace( diff --git a/tests/format/data/waypoints.gpx b/tests/format/data/waypoints.gpx new file mode 100644 index 0000000..865da04 --- /dev/null +++ b/tests/format/data/waypoints.gpx @@ -0,0 +1,42 @@ + + + + rightrightRight + leftleftLeft + leftleftLeft + + from + from + + + via1 + via + + + via2 + via + + + to + to + + + waypoints + 5-gpsies + + 101.5 + 101.5 + 101.5 + 101.5 + 101.75 + 102.0 + 103.5 + 99.5 + 99.5 + 100.0 + + + diff --git a/tests/format/data/waypoints.json b/tests/format/data/waypoints.json new file mode 100644 index 0000000..3df0648 --- /dev/null +++ b/tests/format/data/waypoints.json @@ -0,0 +1,231 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "creator": "BRouter-1.1", + "name": "Track", + "track-length": "388", + "filtered ascend": "1", + "plain-ascend": "0", + "total-time": "44", + "total-energy": "4420", + "cost": "703", + "voicehints": [ + [ + 1, + 5, + 0, + 88, + 89 + ], + [ + 6, + 2, + 0, + 99, + -90 + ], + [ + 7, + 2, + 0, + 10, + -90 + ] + ], + "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", + "" + ], + [ + "8469971", + "49488151", + "102", + "88", + "1150", + "0", + "90", + "0", + "0", + "highway=residential surface=asphalt oneway=yes smoothness=good", + "" + ], + [ + "8469852", + "49489230", + "99", + "162", + "1150", + "0", + "180", + "0", + "0", + "highway=residential surface=asphalt oneway=yes smoothness=good", + "" + ], + [ + "8469852", + "49489230", + "100", + "49", + "1150", + "0", + "0", + "0", + "0", + "highway=residential surface=asphalt oneway=yes smoothness=good", + "" + ] + ], + "times": [ + 0, + 9.592, + 12.271, + 14.13, + 19.406, + 22.134, + 28.832, + 37.817, + 38.938, + 44.217 + ] + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 8.467714, + 49.488115, + 101.5 + ], + [ + 8.46834, + 49.488794, + 101.5 + ], + [ + 8.468586, + 49.488698, + 101.5 + ], + [ + 8.468743, + 49.488636, + 101.5 + ], + [ + 8.469161, + 49.488473, + 101.75 + ], + [ + 8.469355, + 49.488395, + 102 + ], + [ + 8.469971, + 49.488151, + 103.5 + ], + [ + 8.470671, + 49.488909, + 99.5 + ], + [ + 8.470561, + 49.488951, + 99.5 + ], + [ + 8.469984, + 49.489178, + 100 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "name": "from", + "type": "from" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 8.467712, + 49.488117 + ] + } + }, + { + "type": "Feature", + "properties": { + "name": "via1", + "type": "via" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 8.469354, + 49.488394 + ] + } + }, + { + "type": "Feature", + "properties": { + "name": "via2", + "type": "via" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 8.470556, + 49.488946 + ] + } + }, + { + "type": "Feature", + "properties": { + "name": "to", + "type": "to" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 8.469982, + 49.489176 + ] + } + } + ] +}