From fe73b5040aae84ca55379da86c7657b77d48a870 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Fri, 31 May 2024 20:10:22 +0200 Subject: [PATCH] Update hovering over route to show time and distance to next and previous routes --- CHANGELOG.md | 5 +- app/assets/stylesheets/application.css | 6 ++ app/javascript/controllers/maps_controller.js | 84 ++++++++++++++----- app/views/shared/_navbar.html.erb | 1 - 4 files changed, 75 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fffba474..74ff66a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [0.4.3] — 2024-05-30 +## [0.4.3] — 2024-05-31 ### Added - Now user can hover on route and see when it started, when it ended and how much time it took to travel - New buttons to quickly move to today's, yesterday's and 7 days data on the map - "Download JSON" button to points page +- For debugging purposes, now user can use `?meters_between_routes=500` and `?minutes_between_routes=60` query parameters to set the distance and time between routes to split them on the map. This is useful to understand why routes might not be connected on the map. +- Added scale indicator to the map ### Fixed @@ -21,6 +23,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Removed "Your data" page as its function was replaced by "Download JSON" button on the points page +- Hovering over a route now also shows time and distance to next route as well as time and distance to previous route. This allows user to understand why routes might not be connected on the map. --- diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 288b9ab7..268f770e 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -13,3 +13,9 @@ *= require_tree . *= require_self */ + +.emoji-icon { + font-size: 36px; /* Adjust size as needed */ + text-align: center; + line-height: 36px; /* Same as font-size for perfect centering */ +} diff --git a/app/javascript/controllers/maps_controller.js b/app/javascript/controllers/maps_controller.js index 4f213fe8..ea94576f 100644 --- a/app/javascript/controllers/maps_controller.js +++ b/app/javascript/controllers/maps_controller.js @@ -11,6 +11,7 @@ export default class extends Controller { var markers = JSON.parse(this.element.dataset.coordinates) var center = markers[markers.length - 1] || JSON.parse(this.element.dataset.center) var center = (center === undefined) ? [52.516667, 13.383333] : center; + var timezone = this.element.dataset.timezone; var map = L.map(this.containerTarget, { layers: [this.osmMapLayer(), this.osmHotMapLayer()] @@ -33,7 +34,11 @@ export default class extends Controller { return R * c * 1000; // Distance in meters } - function addHighlightOnHover(polyline, map, popupContent) { + function getURLParameter(name) { + return new URLSearchParams(window.location.search).get(name); + } + + function addHighlightOnHover(polyline, map, startPoint, endPoint, prevPoint, nextPoint, timezone) { // Define the original and highlight styles const originalStyle = { color: 'blue', opacity: 0.6, weight: 3 }; const highlightStyle = { color: 'yellow', opacity: 1, weight: 5 }; @@ -41,24 +46,57 @@ export default class extends Controller { // Apply original style to the polyline initially polyline.setStyle(originalStyle); - // Add mouseover event to highlight the polyline and show the popup + // Create the popup content for the route + var firstTimestamp = new Date(startPoint[4] * 1000).toLocaleString('en-GB', { timeZone: timezone }); + var lastTimestamp = new Date(endPoint[4] * 1000).toLocaleString('en-GB', { timeZone: timezone }); + var timeOnRoute = Math.round((endPoint[4] - startPoint[4]) / 60); // Time in minutes + + // Calculate distances to previous and next points + var distanceToPrev = prevPoint ? haversineDistance(prevPoint[0], prevPoint[1], startPoint[0], startPoint[1]) : 'N/A'; + var distanceToNext = nextPoint ? haversineDistance(endPoint[0], endPoint[1], nextPoint[0], nextPoint[1]) : 'N/A'; + + // Calculate time between routes + var timeBetweenPrev = prevPoint ? Math.round((startPoint[4] - prevPoint[4]) / 60) : 'N/A'; + var timeBetweenNext = nextPoint ? Math.round((nextPoint[4] - endPoint[4]) / 60) : 'N/A'; + + // Create custom emoji icons + const startIcon = L.divIcon({ html: '🚥', className: 'emoji-icon' }); + const finishIcon = L.divIcon({ html: '🏁', className: 'emoji-icon' }); + + // Create markers for the start and end points + const startMarker = L.marker([startPoint[0], startPoint[1]], { icon: startIcon }).bindPopup(`Start: ${firstTimestamp}`); + + const endMarker = L.marker([endPoint[0], endPoint[1]], { icon: finishIcon }).bindPopup(` + Start: ${firstTimestamp}
+ End: ${lastTimestamp}
+ Time on route: ${timeOnRoute} minutes
+ Distance to previous route: ${Math.round(distanceToPrev)} meters
+ Distance to next route: ${Math.round(distanceToNext)} meters
+ Time from previous route: ${timeBetweenPrev} minutes
+ Time to next route: ${timeBetweenNext} minutes + `); + + // Add mouseover event to highlight the polyline and show the start and end markers polyline.on('mouseover', function(e) { polyline.setStyle(highlightStyle); - var popup = L.popup() - .setLatLng(e.latlng) - .setContent(popupContent) - .openOn(map); + startMarker.addTo(map); + endMarker.addTo(map).openPopup(); }); - // Add mouseout event to revert the polyline style and close the popup + // Add mouseout event to revert the polyline style and remove the start and end markers polyline.on('mouseout', function(e) { - polyline.setStyle(originalStyle); - map.closePopup(); + polyline.setStyle(originalStyle); + map.closePopup(); + map.removeLayer(startMarker); + map.removeLayer(endMarker); }); - } + } + var splitPolylines = []; var currentPolyline = []; + var distanceThresholdMeters = parseInt(getURLParameter('meters_between_routes')) || 500; + var timeThresholdMinutes = parseInt(getURLParameter('minutes_between_routes')) || 60; // Process markers and split polylines based on the distance and time for (let i = 0, len = markers.length; i < len; i++) { @@ -70,7 +108,7 @@ export default class extends Controller { var distance = haversineDistance(lastPoint[0], lastPoint[1], currentPoint[0], currentPoint[1]); var timeDifference = (currentPoint[4] - lastPoint[4]) / 60; // Time difference in minutes - if (distance > 500 || timeDifference > 60) { + if (distance > distanceThresholdMeters || timeDifference > timeThresholdMinutes) { splitPolylines.push([...currentPolyline]); // Use spread operator to clone the array currentPolyline = [currentPoint]; } else { @@ -84,22 +122,23 @@ export default class extends Controller { } // Assuming each polylineCoordinates is an array of objects with lat, lng, and timestamp properties - var polylineLayers = splitPolylines.map(polylineCoordinates => { + var polylineLayers = splitPolylines.map((polylineCoordinates, index) => { // Extract lat-lng pairs for the polyline var latLngs = polylineCoordinates.map(point => [point[0], point[1]]); // Create a polyline with the given coordinates var polyline = L.polyline(latLngs, { color: 'blue', opacity: 0.6, weight: 3 }); - // Get the timestamps of the first and last points - var firstTimestamp = this.formatDate(polylineCoordinates[0][4]) - var lastTimestamp = this.formatDate(polylineCoordinates[polylineCoordinates.length - 1][4]) - var timeEnRoute = Math.round((polylineCoordinates[polylineCoordinates.length - 1][4] - polylineCoordinates[0][4]) / 60); // Time in minutes + // Get the start and end points + var startPoint = polylineCoordinates[0]; + var endPoint = polylineCoordinates[polylineCoordinates.length - 1]; - // Create the popup content - var popupContent = `Route started: ${firstTimestamp}
Route ended: ${lastTimestamp}
Time en route: ${timeEnRoute} minutes`; + // Get the previous and next points + var prevPoint = index > 0 ? splitPolylines[index - 1][splitPolylines[index - 1].length - 1] : null; + var nextPoint = index < splitPolylines.length - 1 ? splitPolylines[index + 1][0] : null; - addHighlightOnHover(polyline, map, popupContent); + // Add highlighting and popups on hover + addHighlightOnHover(polyline, map, startPoint, endPoint, prevPoint, nextPoint, timezone); return polyline; }); @@ -113,6 +152,13 @@ export default class extends Controller { "Heatmap": heatmapLayer }; + L.control.scale({ + position: 'bottomright', // The default position is 'bottomleft' + metric: true, // Display metric scale + imperial: false, // Display imperial scale + maxWidth: 120 // Maximum width of the scale control in pixels + }).addTo(map); + L.control.layers(this.baseMaps(), controlsLayer).addTo(map); this.addTileLayer(map); diff --git a/app/views/shared/_navbar.html.erb b/app/views/shared/_navbar.html.erb index 77b07f62..2c74c8a5 100644 --- a/app/views/shared/_navbar.html.erb +++ b/app/views/shared/_navbar.html.erb @@ -54,7 +54,6 @@