From a285751416945eef7a80c97bf78941c2c4f2b420 Mon Sep 17 00:00:00 2001 From: Marcus Jaschen Date: Sat, 19 Oct 2024 14:41:29 +0200 Subject: [PATCH] =?UTF-8?q?Add=20=E2=80=9CMaximum=20Speed=E2=80=9D=20to=20?= =?UTF-8?q?analysis=20sidebar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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`. --- css/style.css | 4 - js/control/TrackAnalysis.js | 210 ++++++++++++++++++++---------------- locales/en.json | 58 +++++++++- 3 files changed, 177 insertions(+), 95 deletions(-) diff --git a/css/style.css b/css/style.css index ac505ab..3c784e5 100644 --- a/css/style.css +++ b/css/style.css @@ -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; } diff --git a/js/control/TrackAnalysis.js b/js/control/TrackAnalysis.js index 6f198b0..02b8532 100644 --- a/js/control/TrackAnalysis.js +++ b/js/control/TrackAnalysis.js @@ -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) { @@ -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); @@ -132,6 +132,7 @@ BR.TrackAnalysis = L.Class.extend({ calcStats(polyline, segments) { const analysis = { highway: {}, + maxspeed: {}, surface: {}, smoothness: {}, }; @@ -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, @@ -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 {*[]} @@ -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; } @@ -279,10 +302,10 @@ 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; } @@ -290,18 +313,24 @@ BR.TrackAnalysis = L.Class.extend({ 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]; } } @@ -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]; } @@ -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( - $('

' + i18next.t('sidebar.analysis.header.highway') + '

') - ); + $content.append($(`

${i18next.t('sidebar.analysis.header.highway')}

`)); $content.append(this.renderTable('highway', analysis.highway)); - $content.append( - $('

' + i18next.t('sidebar.analysis.header.surface') + '

') - ); + $content.append($(`

${i18next.t('sidebar.analysis.header.surface')}

`)); $content.append(this.renderTable('surface', analysis.surface)); $content.append( - $('

' + i18next.t('sidebar.analysis.header.smoothness') + '

') + $(`

${i18next.t('sidebar.analysis.header.smoothness')}

`) ); $content.append(this.renderTable('smoothness', analysis.smoothness)); + $content.append($(`

${i18next.t('sidebar.analysis.header.maxspeed')}

`)); + $content.append(this.renderTable('maxspeed', analysis.maxspeed)); }, /** @@ -356,67 +383,45 @@ BR.TrackAnalysis = L.Class.extend({ * @returns {jQuery} */ renderTable(type, data) { - var index; - var $table = $( - '
' - ); - var $thead = $(''); + let index; + const $table = $(`
`); + const $thead = $(''); $thead.append( $('') .append( - '' + - i18next.t('sidebar.analysis.table.category') + - '' + `${i18next.t('sidebar.analysis.table.category')}` ) .append( - $( - '' + - i18next.t('sidebar.analysis.table.length') + - '' - ) + $(`${i18next.t('sidebar.analysis.table.length')}`) ) ); $table.append($thead); - var $tbody = $(''); + const $tbody = $(''); - var totalDistance = 0.0; + let totalDistance = 0.0; for (index in data) { if (!data.hasOwnProperty(index)) { continue; } - var $row = $( - '' - ); - $row.append('' + data[index].formatted_name + ''); - $row.append( - '' + this.formatDistance(data[index].distance) + ' km' - ); + const $row = $(``); + $row.append(`${data[index].formatted_name}`); + $row.append(`${this.formatDistance(data[index].distance)} km`); $tbody.append($row); totalDistance += data[index].distance; } if (totalDistance < this.totalRouteDistance) { $tbody.append( - $( - '' - ) - .append( - $('' + i18next.t('sidebar.analysis.table.unknown') + '') - ) + $(``) + .append($(`${i18next.t('sidebar.analysis.table.unknown')}`)) .append( $( - '' + - this.formatDistance(this.totalRouteDistance - totalDistance) + - ' km' + `${this.formatDistance( + this.totalRouteDistance - totalDistance + )} km` ) ) ); @@ -427,12 +432,12 @@ BR.TrackAnalysis = L.Class.extend({ $table.append( $('') .append('') - .append($('' + i18next.t('sidebar.analysis.table.total_known') + '')) + .append($(`${i18next.t('sidebar.analysis.table.total_known')}`)) .append( $( - '' + - this.formatDistance(totalDistance) + - ' km' + `${this.formatDistance( + totalDistance + )} km` ) ) ); @@ -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); }, @@ -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) { @@ -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), @@ -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) { diff --git a/locales/en.json b/locales/en.json index 4688089..5ecb534 100644 --- a/locales/en.json +++ b/locales/en.json @@ -285,10 +285,66 @@ }, "sidebar": { "analysis": { + "data": { + "highway": { + "footway": "Footway", + "path": "Path", + "residential": "Residential", + "cycleway": "Cycleway", + "track": "Track", + "service": "Service", + "tertiary": "Tertiary", + "secondary": "Secondary", + "primary": "Primary", + "trunk": "Trunk", + "motorway": "Motorway", + "motorway_link": "Motorway Link", + "primary_link": "Primary Link", + "secondary_link": "Secondary Link", + "tertiary_link": "Tertiary Link", + "trunk_link": "Trunk Link", + "living_street": "Living Street", + "pedestrian": "Pedestrian", + "road": "Road", + "bridleway": "Bridleway", + "steps": "Steps", + "sidewalk": "Sidewalk", + "crossing": "Crossing", + "unclassified": "Unclassified" + }, + "surface": { + "asphalt": "Asphalt", + "cobblestone": "Cobblestone", + "compacted": "Compacted", + "dirt": "Dirt", + "fine_gravel": "Fine Gravel", + "grass": "Grass", + "gravel": "Gravel", + "ground": "Ground", + "paved": "Paved", + "sand": "Sand", + "unpaved": "Unpaved", + "wood": "Wood", + "concrete": "Concrete", + "paving_stones": "Paving Stones", + "sett": "Sett" + }, + "smoothness": { + "excellent": "Excellent", + "good": "Good", + "intermediate": "Intermediate", + "bad": "Bad", + "very_bad": "Very Bad", + "horrible": "Horrible", + "very_horrible": "Very Horrible", + "impassable": "Impassable" + } + }, "header": { "highway": "Highway", "smoothness": "Smoothness", - "surface": "Surface" + "surface": "Surface", + "maxspeed": "Maximum Speed" }, "table": { "category": "Category",