Update hovering over route to show time and distance to next and previous routes

This commit is contained in:
Eugene Burmakin 2024-05-31 20:10:22 +02:00
parent 4962d48910
commit fe73b5040a
4 changed files with 75 additions and 21 deletions

View file

@ -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.
---

View file

@ -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 */
}

View file

@ -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}<br>
End: ${lastTimestamp}<br>
Time on route: ${timeOnRoute} minutes<br>
Distance to previous route: ${Math.round(distanceToPrev)} meters<br>
Distance to next route: ${Math.round(distanceToNext)} meters<br>
Time from previous route: ${timeBetweenPrev} minutes<br>
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}<br>Route ended: ${lastTimestamp}<br>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);

View file

@ -54,7 +54,6 @@
</summary>
<ul class="p-2 bg-base-100 rounded-t-none z-10">
<li><%= link_to 'Settings', edit_user_registration_path %></li>
<li><%= link_to 'Your data', export_path %></li>
<li><%= link_to 'Logout', destroy_user_session_path, method: :delete, data: { turbo_method: :delete } %></li>
</ul>
</details>