Concatenate total track

+ handle server voicehint time removed, times with 3 digits
This commit is contained in:
Norbert Renner 2021-03-12 21:20:35 +01:00
parent 954812cf52
commit 25f8828ae7
10 changed files with 412 additions and 47 deletions

View file

@ -31,8 +31,9 @@ BR.Export = L.Class.extend({
this.update([]);
},
update: function (latLngs) {
update: function (latLngs, segments) {
this.latLngs = latLngs;
this.segments = segments;
if (latLngs.length < 2) {
this.exportButton.addClass('disabled');
@ -47,15 +48,38 @@ BR.Export = L.Class.extend({
var name = encodeURIComponent(exportForm['trackname'].value);
var includeWaypoints = exportForm['include-waypoints'].checked;
var uri = this.router.getUrl(this.latLngs, this.pois.getMarkers(), null, format, name, includeWaypoints);
e.preventDefault();
var evt = document.createEvent('MouseEvents');
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
var link = document.createElement('a');
link.href = uri;
link.dispatchEvent(evt);
if (true) {
var uri = this.router.getUrl(this.latLngs, this.pois.getMarkers(), null, format, name, includeWaypoints);
// var evt = document.createEvent('MouseEvents');
// evt.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
// var link = document.createElement('a');
// link.href = uri;
// link.dispatchEvent(evt);
//} else {
const track = this._formatTrack(format, name, includeWaypoints);
BR.Export.diff(uri, track, format);
}
},
_formatTrack: function (format, name, includeWaypoints) {
const track = BR.Export._concatTotalTrack(this.segments);
//console.log('GeoJson: ', trackGeoJson);
//console.log('GeoJson: ', JSON.stringify(trackGeoJson, null, 4));
switch (format) {
case 'gpx':
//console.log('gpx: ', gpx);
return BR.Gpx.format(track, 2, 'bike'); // TODO parse turnInstructionMode, transportMode
case 'geojson':
return JSON.stringify(track, null, 2);
case 'kml':
default:
break;
}
console.error('Export format not implemented: ' + format);
},
_validationMessage: function () {
@ -72,6 +96,7 @@ BR.Export = L.Class.extend({
},
_generateTrackname: function () {
return; // TODO remove
var trackname = this.trackname;
this._getCityAtPosition(
this.latLngs[0],
@ -143,3 +168,150 @@ BR.Export = L.Class.extend({
BR.export = function () {
return new BR.Export();
};
BR.Export._concatTotalTrack = function (segments) {
const sumProperties = (p, fp, keys) => {
for (const key of keys) {
p[key] = (+p[key] + +fp[key]).toString();
}
};
let coordinates = [];
let properties;
//console.time('_concatTotalTrack');
for (const [segmentIndex, segment] of segments.entries()) {
const feature = segment.feature;
if (!feature) continue;
const coordOffset = coordinates.length > 0 ? coordinates.length - 1 : 0;
if (properties) {
const p = properties;
const fp = feature.properties;
sumProperties(p, fp, [
'cost',
'filtered ascend',
'plain-ascend',
'total-energy',
'total-time',
'track-length',
]);
p.messages = p.messages.concat(fp.messages.slice(1));
if (p.times && fp.times) {
const lastTime = p.times[p.times.length - 1];
for (const [timeIndex, time] of fp.times.entries()) {
if (timeIndex > 0) {
p.times.push(+(lastTime + time).toFixed(3));
}
}
}
if (fp.voicehints) {
if (!p.voicehints) p.voicehints = [];
for (const fpHint of fp.voicehints) {
const hint = fpHint.slice();
hint[0] += coordOffset;
p.voicehints.push(hint);
}
}
} else {
// clone
properties = Object.assign({}, feature.properties);
if (properties.voicehints) {
properties.voicehints = properties.voicehints.slice();
}
if (properties.times) {
properties.times = properties.times.slice();
}
}
let featureCoordinates = feature.geometry.coordinates;
if (segmentIndex > 0) {
// remove first segment coordinate, same as previous last
featureCoordinates = featureCoordinates.slice(1);
}
coordinates = coordinates.concat(featureCoordinates);
}
//console.timeEnd('_concatTotalTrack');
return turf.featureCollection([turf.lineString(coordinates, properties)]);
};
// <script src="https://unpkg.com/googlediff@0.1.0/javascript/diff_match_patch.js"></script>
BR.Export.diff = function (uri, track, format) {
BR.Util.get(
uri,
((err, text) => {
if (err) {
console.error('Error exporting "' + profileUrl + '": ' + err);
return;
}
if (format === 'gpx') {
text = BR.Gpx.pretty(BR.Export.adoptGpx(text));
} else if (format === 'geojson') {
text = JSON.stringify(JSON.parse(text), null, 2);
}
var dmp = new diff_match_patch();
var diff = dmp.diff_main(text, track);
dmp.diff_cleanupSemantic(diff);
if (dmp.diff_levenshtein(diff) > 0) {
let i = 0;
while (i < diff.length - 2) {
if (
diff[i][0] === 0 &&
diff[i + 1][0] === -1 &&
diff[i + 2][0] === 1 &&
(/(rteTime|rteSpeed)>\d+\.\d{0,2}$/.test(diff[i][1]) || /time=[0-9h ]*m \d$/.test(diff[i][1]))
) {
const del = +diff[i + 1][1];
const ins = +diff[i + 2][1];
if (Number.isInteger(del) && Number.isInteger(ins) && Math.abs(del - ins) === 1) {
diff.splice(i + 1, 2);
}
}
i++;
}
}
if (dmp.diff_levenshtein(diff) > 0) {
//console.log('server: ', text);
//console.log('client: ', track);
console.log(diff);
bootbox.alert(dmp.diff_prettyHtml(diff));
} else {
console.log('diff equal');
}
}).bind(this)
);
};
// TODO remove
// copied from Gpx.test.js
BR.Export.adoptGpx = function (gpx, replaceCreator = true) {
const creator = 'togpx';
const name = 'Track';
const newline = '\n';
gpx = gpx.replace(/=\.(?=\d)/, '=0.');
if (replaceCreator) {
gpx = gpx.replace(/creator="[^"]*"/, `creator="${creator}"`);
}
gpx = gpx.replace(/creator="([^"]*)" version="1.1"/, 'version="1.1" \n creator="$1"');
//gpx = gpx.replace(/<trk>\n <name>[^<]*<\/name>/, `<trk>\n <name>${name}</name>`);
gpx = gpx
.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('</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
return gpx;
};

View file

@ -49,14 +49,25 @@ BR.Gpx = {
comment += ' energy=' + (props['total-energy'] / 3600000).toFixed(1) + 'kwh';
}
if (props['total-time']) {
// TODO format, e.g. total-time=14833 -> time=4h 7m 13s
// see brouter OsmTrack.getFormattedTime2
comment += ' time=' + props['total-time'] + 's';
comment += ' time=' + BR.Gpx.formatTime(props['total-time']);
}
comment += ' -->';
return comment;
},
// 14833 -> 4h 7m 13s
// see BRouter OsmTrack.getFormattedTime2
formatTime(seconds) {
const hours = Math.trunc(seconds / 3600);
const minutes = Math.trunc((seconds - hours * 3600) / 60);
seconds = seconds - hours * 3600 - minutes * 60;
let time = '';
if (hours != 0) time += hours + 'h ';
if (minutes != 0) time += minutes + 'm ';
if (seconds != 0) time += seconds + 's';
return time;
},
// modified version of
// https://gist.github.com/sente/1083506#gistcomment-2254622
// MIT License, Copyright (c) 2016 Stuart Powers, ES6 version by Jonathan Gruber

View file

@ -11,21 +11,25 @@
class RoundaboutCommand extends Command {
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;
super(
command.name + exitNumber,
command.locus + exitNumber,
command.orux + exitNumber,
command.symbol + exitNumber,
command.message + exitNumber
);
}
}
class RoundaboutLeftCommand extends 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;
super(
command.name + -exitNumber,
command.locus + -exitNumber,
command.orux + exitNumber,
command.symbol + -exitNumber,
command.message + -exitNumber
);
}
}
@ -82,36 +86,25 @@
},
_getDuration: function (voicehintsIndex) {
const timeList = this.track.properties.times;
const times = this.track.properties.times;
if (!times) return 0;
const indexInTrack = this.voicehints[voicehintsIndex][0];
const currTime = timeList[indexInTrack];
const currentTime = times[indexInTrack];
const len = this.voicehints.length;
const nextIndex = voicehintsIndex < len - 1 ? this.voicehints[voicehintsIndex + 1][0] : timeList.length - 1;
const nextTime = timeList[nextIndex];
const nextIndex = voicehintsIndex < len - 1 ? this.voicehints[voicehintsIndex + 1][0] : times.length - 1;
const nextTime = times[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;
return nextTime - currentTime;
},
_loopHints: function (hintCallback) {
if (!this.voicehints) return;
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 };
const [indexInTrack, commandId, exitNumber, distance, angle, geometry] = values;
const hint = { indexInTrack, commandId, exitNumber, distance, angle, geometry };
// TODO remove server hint time
//hint.time = this._getDuration(i);
this._getDuration(i);
hint.time = this._getDuration(i);
if (hint.time > 0) {
hint.speed = distance / hint.time;
}
@ -229,8 +222,8 @@
extensions['locus:rteDistance'] = hint.distance;
if (hint.time > 0) {
extensions['locus:rteTime'] = hint.time;
extensions['locus:rteSpeed'] = hint.speed;
extensions['locus:rteTime'] = hint.time.toFixed(3);
extensions['locus:rteSpeed'] = hint.speed.toFixed(3);
}
extensions['locus:rtePointAction'] = cmd.locus;

View file

@ -300,7 +300,7 @@
trackMessages.update(track, segments);
trackAnalysis.update(track, segments);
exportRoute.update(latLngs);
exportRoute.update(latLngs, segments);
}
routing.addTo(map);