Skip to content

Commit

Permalink
Add “Maximum Speed” to analysis sidebar
Browse files Browse the repository at this point in the history
It shows the distribution of maximum speeds for all ways on the
current route (if that data is available, otherwise it’s summed up
under “unknown”).

`maxspeed:forward` and `maxspeed:backward` is respected in conjunction
with `reversedirection`.

Hovering/clicking table rows to highlight matching segments on the
route work the identical to the other analysis tables.

Additionally, all tags in the analysis tab (way type, surface,
smoothness) are translateable now. The values were added to `en.json`.

Some HTML is rendered with template literals now, instead of
concatenating strings.

Variable declarations were changed from `var` to `const`/`let`.
  • Loading branch information
mjaschen committed Oct 19, 2024
1 parent 76f31ae commit 50e8550
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 95 deletions.
4 changes: 0 additions & 4 deletions css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -350,10 +350,6 @@ table.dataTable.track-analysis-table tfoot td {
padding-top: 4px;
}

.track-analysis-title {
text-transform: capitalize;
}

.track-analysis-distance {
text-align: right;
}
Expand Down
210 changes: 120 additions & 90 deletions js/control/TrackAnalysis.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,14 @@ BR.TrackAnalysis = L.Class.extend({
/**
* Everytime the track changes this method is called:
*
* - calculate statistics (way type, surface, smoothness)
* - calculate statistics (way type, max speed, surface, smoothness)
* for the whole track
* - renders statistics tables
* - create event listeners which allow to hover/click a
* table row for highlighting matching track segments
*
* @param {Polyline} polyline
* @param {Array} segments
* @param {Array} segments route segments between waypoints
*/
update(polyline, segments) {
if (!this.active) {
Expand All @@ -105,7 +105,7 @@ BR.TrackAnalysis = L.Class.extend({
this.trackPolyline = polyline;
this.trackEdges = new BR.TrackEdges(segments);

var analysis = this.calcStats(polyline, segments);
const analysis = this.calcStats(polyline, segments);

this.render(analysis);

Expand All @@ -132,6 +132,7 @@ BR.TrackAnalysis = L.Class.extend({
calcStats(polyline, segments) {
const analysis = {
highway: {},
maxspeed: {},
surface: {},
smoothness: {},
};
Expand Down Expand Up @@ -175,14 +176,19 @@ BR.TrackAnalysis = L.Class.extend({
segments[segmentIndex].feature.properties.messages[messageIndex][3]
);
break;
case 'maxspeed':
case 'surface':
case 'smoothness':
if (typeof analysis[tagName][wayTagParts[1]] === 'undefined') {
let formattedName = i18next.t([
'sidebar.analysis.data.' + tagName + '.' + wayTagParts[1],
wayTagParts[1],
]);
if (tagName.indexOf('maxspeed') === 0) {
formattedName += ' km/h';
}
analysis[tagName][wayTagParts[1]] = {
formatted_name: i18next.t(
'sidebar.analysis.data.' + tagName + '.' + wayTagParts[1],
wayTagParts[1]
),
formatted_name: formattedName,
name: wayTagParts[1],
subtype: '',
distance: 0.0,
Expand All @@ -209,6 +215,10 @@ BR.TrackAnalysis = L.Class.extend({
* are dropped. If no specialized surface/smoothness tag is found, the default value
* is returned, i.e. `smoothness` or `surface`.
*
* Also, maxspeed comes in different variations, e.g. `maxspeed`, `maxspeed:forward`,
* `maxspeed:backward`. Depending on the existence of the `reversedirection` field
* we can select the correct value.
*
* @param wayTags tags + values for a way segment
* @param routingType currently only 'cycling' is supported, can be extended in the future (walking, driving, etc.)
* @returns {*[]}
Expand Down Expand Up @@ -242,6 +252,19 @@ BR.TrackAnalysis = L.Class.extend({
continue;
}

if (tagName === 'maxspeed:forward' && !wayTags.includes('reversedirection=yes')) {
normalizedWayTags['maxspeed'] = tagValue;
continue;
}
if (tagName === 'maxspeed:backward' && wayTags.includes('reversedirection=yes')) {
normalizedWayTags['maxspeed'] = tagValue;
continue;
}
if (tagName === 'maxspeed') {
normalizedWayTags[tagName] = tagValue;
continue;
}

normalizedWayTags[tagName] = tagValue;
}

Expand Down Expand Up @@ -279,29 +302,35 @@ BR.TrackAnalysis = L.Class.extend({
* @returns {Object}
*/
sortAnalysisData(analysis) {
var analysisSortable = {};
var result = {};
const analysisSortable = {};
const result = {};

for (var type in analysis) {
for (const type in analysis) {
if (!analysis.hasOwnProperty(type)) {
continue;
}

result[type] = {};
analysisSortable[type] = [];

for (var name in analysis[type]) {
for (const name in analysis[type]) {
if (!analysis[type].hasOwnProperty(name)) {
continue;
}
analysisSortable[type].push(analysis[type][name]);
}

analysisSortable[type].sort(function (a, b) {
return b.distance - a.distance;
});
if (type === 'maxspeed') {
analysisSortable[type].sort(function (a, b) {
return parseInt(a.name) - parseInt(b.name);
});
} else {
analysisSortable[type].sort(function (a, b) {
return b.distance - a.distance;
});
}

for (var j = 0; j < analysisSortable[type].length; j++) {
for (let j = 0; j < analysisSortable[type].length; j++) {
result[type][analysisSortable[type][j].formatted_name] = analysisSortable[type][j];
}
}
Expand All @@ -317,8 +346,8 @@ BR.TrackAnalysis = L.Class.extend({
* @returns {string}
*/
getTrackType(wayTags) {
for (var i = 0; i < wayTags.length; i++) {
var wayTagParts = wayTags[i].split('=');
for (let i = 0; i < wayTags.length; i++) {
const wayTagParts = wayTags[i].split('=');
if (wayTagParts[0] === 'tracktype') {
return wayTagParts[1];
}
Expand All @@ -331,21 +360,19 @@ BR.TrackAnalysis = L.Class.extend({
* @param {Object} analysis
*/
render(analysis) {
var $content = $('#track_statistics');
const $content = $('#track_statistics');

$content.html('');
$content.append(
$('<h4 class="track-analysis-heading">' + i18next.t('sidebar.analysis.header.highway') + '</h4>')
);
$content.append($(`<h4 class="track-analysis-heading">${i18next.t('sidebar.analysis.header.highway')}</h4>`));
$content.append(this.renderTable('highway', analysis.highway));
$content.append(
$('<h4 class="track-analysis-heading">' + i18next.t('sidebar.analysis.header.surface') + '</h4>')
);
$content.append($(`<h4 class="track-analysis-heading">${i18next.t('sidebar.analysis.header.surface')}</h4>`));
$content.append(this.renderTable('surface', analysis.surface));
$content.append(
$('<h4 class="track-analysis-heading">' + i18next.t('sidebar.analysis.header.smoothness') + '</h4>')
$(`<h4 class="track-analysis-heading">${i18next.t('sidebar.analysis.header.smoothness')}</h4>`)
);
$content.append(this.renderTable('smoothness', analysis.smoothness));
$content.append($(`<h4 class="track-analysis-heading">${i18next.t('sidebar.analysis.header.maxspeed')}</h4>`));
$content.append(this.renderTable('maxspeed', analysis.maxspeed));
},

/**
Expand All @@ -356,67 +383,45 @@ BR.TrackAnalysis = L.Class.extend({
* @returns {jQuery}
*/
renderTable(type, data) {
var index;
var $table = $(
'<table data-type="' + type + '" class="mini cell-border stripe dataTable track-analysis-table"></table>'
);
var $thead = $('<thead></thead>');
let index;
const $table = $(`<table data-type="${type}" class="mini stripe dataTable track-analysis-table"></table>`);
const $thead = $('<thead></thead>');
$thead.append(
$('<tr>')
.append(
'<th class="track-analysis-header-category">' +
i18next.t('sidebar.analysis.table.category') +
'</th>'
`<th class="track-analysis-header-category">${i18next.t('sidebar.analysis.table.category')}</th>`
)
.append(
$(
'<th class="track-analysis-header-distance">' +
i18next.t('sidebar.analysis.table.length') +
'</th>'
)
$(`<th class="track-analysis-header-distance">${i18next.t('sidebar.analysis.table.length')}</th>`)
)
);
$table.append($thead);
var $tbody = $('<tbody></tbody>');
const $tbody = $('<tbody></tbody>');

var totalDistance = 0.0;
let totalDistance = 0.0;

for (index in data) {
if (!data.hasOwnProperty(index)) {
continue;
}
var $row = $(
'<tr data-name="' +
data[index].name +
'" data-subtype="' +
data[index].subtype +
'" data-distance="' +
data[index].distance +
'"></tr>'
);
$row.append('<td class="track-analysis-title">' + data[index].formatted_name + '</td>');
$row.append(
'<td class="track-analysis-distance">' + this.formatDistance(data[index].distance) + ' km</td>'
);
const $row = $(`<tr data-name="${data[index].name}" \
data-subtype="${data[index].subtype}" \
data-distance="${data[index].distance}"></tr>`);
$row.append(`<td class="track-analysis-title">${data[index].formatted_name}</td>`);
$row.append(`<td class="track-analysis-distance">${this.formatDistance(data[index].distance)} km</td>`);
$tbody.append($row);
totalDistance += data[index].distance;
}

if (totalDistance < this.totalRouteDistance) {
$tbody.append(
$(
'<tr data-name="internal-unknown" data-distance="' +
(this.totalRouteDistance - totalDistance) +
'"></tr>'
)
.append(
$('<td class="track-analysis-title">' + i18next.t('sidebar.analysis.table.unknown') + '</td>')
)
$(`<tr data-name="internal-unknown" data-distance="${this.totalRouteDistance - totalDistance}"></tr>`)
.append($(`<td class="track-analysis-title">${i18next.t('sidebar.analysis.table.unknown')}</td>`))
.append(
$(
'<td class="track-analysis-distance">' +
this.formatDistance(this.totalRouteDistance - totalDistance) +
' km</td>'
`<td class="track-analysis-distance">${this.formatDistance(
this.totalRouteDistance - totalDistance
)} km</td>`
)
)
);
Expand All @@ -427,12 +432,12 @@ BR.TrackAnalysis = L.Class.extend({
$table.append(
$('<tfoot></tfoot>')
.append('<tr></tr>')
.append($('<td>' + i18next.t('sidebar.analysis.table.total_known') + '</td>'))
.append($(`<td>${i18next.t('sidebar.analysis.table.total_known')}</td>`))
.append(
$(
'<td class="track-analysis-distance track-analysis-distance-total">' +
this.formatDistance(totalDistance) +
' km</td>'
`<td class="track-analysis-distance track-analysis-distance-total">${this.formatDistance(
totalDistance
)} km</td>`
)
)
);
Expand All @@ -451,13 +456,13 @@ BR.TrackAnalysis = L.Class.extend({
},

handleHover(event) {
var $tableRow = $(event.currentTarget);
var $table = $tableRow.parents('table').first();
var dataType = $table.data('type');
var dataName = $tableRow.data('name');
var trackType = $tableRow.data('subtype');
const $tableRow = $(event.currentTarget);
const $table = $tableRow.parents('table').first();
const dataType = $table.data('type');
const dataName = $tableRow.data('name');
const trackType = $tableRow.data('subtype');

var polylinesForDataType = this.getPolylinesForDataType(dataType, dataName, trackType);
const polylinesForDataType = this.getPolylinesForDataType(dataType, dataName, trackType);

this.highlightedSegments = L.layerGroup(polylinesForDataType).addTo(this.map);
},
Expand All @@ -467,11 +472,11 @@ BR.TrackAnalysis = L.Class.extend({
},

toggleSelected(event) {
var tableRow = event.currentTarget;
var $table = $(tableRow).parents('table').first();
var dataType = $table.data('type');
var dataName = $(tableRow).data('name');
var trackType = $(tableRow).data('subtype');
const tableRow = event.currentTarget;
const $table = $(tableRow).parents('table').first();
const dataType = $table.data('type');
const dataName = $(tableRow).data('name');
const trackType = $(tableRow).data('subtype');

if (tableRow.classList.toggle('selected')) {
if (this.highlightedSegment) {
Expand Down Expand Up @@ -505,13 +510,13 @@ BR.TrackAnalysis = L.Class.extend({
* @returns {Polyline[]}
*/
getPolylinesForDataType(dataType, dataName, trackType) {
var polylines = [];
var trackLatLngs = this.trackPolyline.getLatLngs();
const polylines = [];
const trackLatLngs = this.trackPolyline.getLatLngs();

for (var i = 0; i < this.trackEdges.edges.length; i++) {
for (let i = 0; i < this.trackEdges.edges.length; i++) {
if (this.wayTagsMatchesData(trackLatLngs[this.trackEdges.edges[i]], dataType, dataName, trackType)) {
var matchedEdgeIndexStart = i > 0 ? this.trackEdges.edges[i - 1] : 0;
var matchedEdgeIndexEnd = this.trackEdges.edges[i] + 1;
const matchedEdgeIndexStart = i > 0 ? this.trackEdges.edges[i - 1] : 0;
const matchedEdgeIndexEnd = this.trackEdges.edges[i] + 1;
polylines.push(
L.polyline(
trackLatLngs.slice(matchedEdgeIndexStart, matchedEdgeIndexEnd),
Expand Down Expand Up @@ -559,19 +564,44 @@ BR.TrackAnalysis = L.Class.extend({
return this.singleWayTagMatchesData('surface', parsed, dataName);
case 'smoothness':
return this.singleWayTagMatchesData('smoothness', parsed, dataName);
case 'maxspeed':
return this.singleWayTagMatchesData('maxspeed', parsed, dataName);
}

return false;
},

singleWayTagMatchesData(category, parsedData, lookupValue) {
var foundValue = null;
if (typeof lookupValue === 'number') {
lookupValue = lookupValue.toString();
}

for (var iterationKey in parsedData) {
if (iterationKey.indexOf(category) !== -1) {
foundValue = parsedData[iterationKey];
break;
}
let foundValue = null;

// We need to handle `maxspeed:forward` and `maxspeed:backward` separately
// from all other tags, because we need to consider the `reversedirection`
// tag.
// Test URL: http://localhost:3000/#map=15/52.2292/13.6204/standard&lonlats=13.61948,52.231611;13.611327,52.227431
if (
category === 'maxspeed' &&
parsedData.hasOwnProperty('maxspeed:forward') &&
!parsedData.hasOwnProperty('reversedirection')
) {
foundValue = parsedData['maxspeed:forward'];
}
if (
category === 'maxspeed' &&
parsedData.hasOwnProperty('maxspeed:backward') &&
parsedData.hasOwnProperty('reversedirection') &&
parsedData.reversedirection === 'yes'
) {
foundValue = parsedData['maxspeed:backward'];
}

// if the special handling for `maxspeed` didn't find a result,
// check wayTags for matching property:
if (foundValue === null && parsedData.hasOwnProperty(category)) {
foundValue = parsedData[category];
}

if (lookupValue === 'internal-unknown' && foundValue === null) {
Expand Down
Loading

0 comments on commit 50e8550

Please sign in to comment.