Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add “Maximum Speed” to analysis sidebar #830

Merged
merged 3 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: segments route segments? Shouldn't it be route segments alone?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first segments refers to the variable name, the three following words are the description.

As per JSDoc a hyphen should be inserted after the parameter name. I'll add this in a follow-up pull request.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah ok my bad, 👍

*/
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';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: not possible to have it within the i18n content directly?

Eg add sidebar.analysis.data.maxspeed.XXX and in English set to %s km/h?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good catch. I'll change it in a follow-up PR.

}
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') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: probably would have used a switch case for this but not a blocker at all :)

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
Loading