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..abcb9276 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,25 @@ 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') { - analysis[tagName][wayTagParts[1]] = { - formatted_name: i18next.t( + let formattedName; + + if (tagName.indexOf('maxspeed') === 0) { + formattedName = i18next.t('sidebar.analysis.data.maxspeed', { + maxspeed: wayTagParts[1], + }); + } else { + formattedName = i18next.t([ 'sidebar.analysis.data.' + tagName + '.' + wayTagParts[1], - wayTagParts[1] - ), + wayTagParts[1], + ]); + } + + analysis[tagName][wayTagParts[1]] = { + formatted_name: formattedName, name: wayTagParts[1], subtype: '', distance: 0.0, @@ -209,8 +221,12 @@ 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`. * - * @param wayTags tags + values for a way segment - * @param routingType currently only 'cycling' is supported, can be extended in the future (walking, driving, etc.) + * 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 {*[]} */ normalizeWayTags(wayTags, routingType) { @@ -242,6 +258,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 +308,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 +319,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 +352,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 +366,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)); }, /** @@ -356,67 +389,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>` ) ) ); @@ -427,12 +438,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>` ) ) ); @@ -451,13 +462,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 +478,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) { @@ -497,21 +508,21 @@ 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[]} */ 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), @@ -530,11 +541,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} @@ -559,19 +570,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) { @@ -586,7 +622,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} @@ -608,7 +644,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} */ diff --git a/locales/en.json b/locales/en.json index 46880895..309d85ed 100644 --- a/locales/en.json +++ b/locales/en.json @@ -285,10 +285,67 @@ }, "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" + }, + "maxspeed": "{{maxspeed}} km/h" + }, "header": { "highway": "Highway", "smoothness": "Smoothness", - "surface": "Surface" + "surface": "Surface", + "maxspeed": "Maximum Speed" }, "table": { "category": "Category",