Format route points as waypoints

This commit is contained in:
Norbert Renner 2021-03-18 18:04:57 +01:00
parent 449a24e5ce
commit 7fc2f6bee5
8 changed files with 380 additions and 19 deletions

View file

@ -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');

View file

@ -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 () {},

View file

@ -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) {

View file

@ -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}</`); // remove trailing zeros
gpx = gpx.replace('</gpx>\n', '</gpx>');
// added
gpx = gpx.replace(/>([-.0-9]+?0+)<\//g, (match, p1) => `>${+p1}</`); // remove trailing zeros
// trunc bc. float precision diffs
gpx = gpx.replace(/(rteTime|rteSpeed)>([^<]*)<\//g, (match, p1, p2) => `${p1}>${(+p2).toFixed(3)}</`);
gpx = gpx.replace(/\n?\s*<\/extensions>\n?\s*<extensions>/, ''); // ignore (invalid) double tag

View file

@ -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');
});

View file

@ -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}</`); // remove trailing zeros
gpx = gpx.replace('</gpx>\n', '</gpx>');
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, '</locus:rteDistance'); // ignore .0 decimal
brouterGpx = brouterGpx.replace(/\n\s*<\/extensions>\n\s*<extensions>/, ''); // ignore (invalid) double tag
// ignore float rounding differences
brouterGpx = brouterGpx.replace(

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- track-length = 388 filtered ascend = 1 plain-ascend = 0 cost=703 energy=.0kwh time=44s -->
<gpx
xmlns="http://www.topografix.com/GPX/1/1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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"><name>right</name><sym>right</sym><type>Right</type></wpt>
<wpt lon="8.469971" lat="49.488151"><name>left</name><sym>left</sym><type>Left</type></wpt>
<wpt lon="8.470671" lat="49.488909"><name>left</name><sym>left</sym><type>Left</type></wpt>
<wpt lon="8.467712" lat="49.488117">
<name>from</name>
<type>from</type>
</wpt>
<wpt lon="8.469354" lat="49.488394">
<name>via1</name>
<type>via</type>
</wpt>
<wpt lon="8.470556" lat="49.488946">
<name>via2</name>
<type>via</type>
</wpt>
<wpt lon="8.469982" lat="49.489176">
<name>to</name>
<type>to</type>
</wpt>
<trk>
<name>waypoints + 5-gpsies</name>
<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.469355" lat="49.488395"><ele>102.0</ele></trkpt>
<trkpt lon="8.469971" lat="49.488151"><ele>103.5</ele></trkpt>
<trkpt lon="8.470671" lat="49.488909"><ele>99.5</ele></trkpt>
<trkpt lon="8.470561" lat="49.488951"><ele>99.5</ele></trkpt>
<trkpt lon="8.469984" lat="49.489178"><ele>100.0</ele></trkpt>
</trkseg>
</trk>
</gpx>

View file

@ -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
]
}
}
]
}