From 50e85509ac9c11bfc5b7c2542931048eb9bcb0ea Mon Sep 17 00:00:00 2001 From: Marcus Jaschen Date: Sat, 19 Oct 2024 14:41:29 +0200 Subject: [PATCH 1/3] =?UTF-8?q?Add=20=E2=80=9CMaximum=20Speed=E2=80=9D=20t?= =?UTF-8?q?o=20analysis=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 ac505ab1..3c784e5e 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 6f198b00..02b8532d 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 46880895..5ecb5349 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", From bb3bfbaef604a53c6326db12d6f958ac2ecf8505 Mon Sep 17 00:00:00 2001 From: Marcus Jaschen Date: Sun, 20 Oct 2024 17:30:27 +0200 Subject: [PATCH 2/3] update JSDoc add hyphens between parameter name and description --- js/control/TrackAnalysis.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/js/control/TrackAnalysis.js b/js/control/TrackAnalysis.js index 02b8532d..3b92528a 100644 --- a/js/control/TrackAnalysis.js +++ b/js/control/TrackAnalysis.js @@ -82,7 +82,7 @@ BR.TrackAnalysis = L.Class.extend({ * table row for highlighting matching track segments * * @param {Polyline} polyline - * @param {Array} segments route segments between waypoints + * @param {Array} segments - route segments between waypoints */ update(polyline, segments) { if (!this.active) { @@ -219,8 +219,8 @@ BR.TrackAnalysis = L.Class.extend({ * `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.) + * @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 {*[]} */ normalizeWayTags(wayTags, routingType) { @@ -502,9 +502,9 @@ BR.TrackAnalysis = L.Class.extend({ * track edge matches the search, create a Leaflet polyline * and add it to the result array. * - * @param {string} dataType `highway`, `surface`, `smoothness` - * @param {string} dataName `primary`, `track, `asphalt`, etc. - * @param {string} trackType the tracktype is passed here (e.g. + * @param {string} dataType - `highway`, `surface`, `smoothness` + * @param {string} dataName - `primary`, `track, `asphalt`, etc. + * @param {string} trackType - the tracktype is passed here (e.g. * `grade3`), but only in the case that `dataName` is `track` * * @returns {Polyline[]} @@ -535,11 +535,11 @@ BR.TrackAnalysis = L.Class.extend({ * which matches if a tag-pair is missing. Special handling for * tracktypes again. * - * @param {string} wayTags The way tags as provided by brouter, e.g. + * @param {string} wayTags - The way tags as provided by brouter, e.g. * `highway=secondary surface=asphalt smoothness=good` - * @param {string} dataType `highway`, `surface`, `smoothness` - * @param {string} dataName `primary`, `track, `asphalt`, etc. - * @param {string} trackType the tracktype is passed here (e.g. + * @param {string} dataType - `highway`, `surface`, `smoothness` + * @param {string} dataName - `primary`, `track, `asphalt`, etc. + * @param {string} trackType - the tracktype is passed here (e.g. * `grade3`), but only in the case that `dataName` is `track` * * @returns {boolean} @@ -616,7 +616,7 @@ BR.TrackAnalysis = L.Class.extend({ * * 'highway=primary surface=asphalt' => { highway: 'primary', surface: 'asphalt' } * - * @param wayTags The way tags as provided by brouter, e.g. + * @param wayTags - The way tags as provided by brouter, e.g. * `highway=secondary surface=asphalt smoothness=good` * * @returns {object} @@ -638,7 +638,7 @@ BR.TrackAnalysis = L.Class.extend({ * * { 'highway' : 'path', 'surface' : 'sand' } => ['highway=path', 'surface=sand'] * - * @param wayTags The way tags in object representation + * @param wayTags - The way tags in object representation * * @returns {object} */ From 6606d8b454bf368023362d6385593a4e6dcc0365 Mon Sep 17 00:00:00 2001 From: Marcus Jaschen Date: Sun, 20 Oct 2024 17:37:34 +0200 Subject: [PATCH 3/3] use i18next interpolation instead of concatenating strings --- js/control/TrackAnalysis.js | 16 +++++++++++----- locales/en.json | 3 ++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/js/control/TrackAnalysis.js b/js/control/TrackAnalysis.js index 3b92528a..abcb9276 100644 --- a/js/control/TrackAnalysis.js +++ b/js/control/TrackAnalysis.js @@ -180,13 +180,19 @@ BR.TrackAnalysis = L.Class.extend({ case 'surface': case 'smoothness': if (typeof analysis[tagName][wayTagParts[1]] === 'undefined') { - let formattedName = i18next.t([ - 'sidebar.analysis.data.' + tagName + '.' + wayTagParts[1], - wayTagParts[1], - ]); + let formattedName; + if (tagName.indexOf('maxspeed') === 0) { - formattedName += ' km/h'; + formattedName = i18next.t('sidebar.analysis.data.maxspeed', { + maxspeed: wayTagParts[1], + }); + } else { + formattedName = i18next.t([ + 'sidebar.analysis.data.' + tagName + '.' + wayTagParts[1], + wayTagParts[1], + ]); } + analysis[tagName][wayTagParts[1]] = { formatted_name: formattedName, name: wayTagParts[1], diff --git a/locales/en.json b/locales/en.json index 5ecb5349..309d85ed 100644 --- a/locales/en.json +++ b/locales/en.json @@ -338,7 +338,8 @@ "horrible": "Horrible", "very_horrible": "Very Horrible", "impassable": "Impassable" - } + }, + "maxspeed": "{{maxspeed}} km/h" }, "header": { "highway": "Highway",