diff --git a/app/javascript/controllers/maps_controller.js b/app/javascript/controllers/maps_controller.js
index 40893763..028240b2 100644
--- a/app/javascript/controllers/maps_controller.js
+++ b/app/javascript/controllers/maps_controller.js
@@ -758,10 +758,9 @@ export default class extends Controller {
this.clearFogRadius = parseInt(newSettings.fog_of_war_meters) || 50;
this.routeOpacity = parseFloat(newSettings.route_opacity) || 0.6;
- // Preserve existing layer instances if they exist
+ // Preserve existing layers except polylines which need to be recreated
const preserveLayers = {
Points: this.markersLayer,
- Polylines: this.polylinesLayer,
Heatmap: this.heatmapLayer,
"Fog of War": this.fogOverlay,
Areas: this.areasLayer,
@@ -774,12 +773,15 @@ export default class extends Controller {
}
});
- // Recreate layers only if they don't exist
- this.markersLayer = preserveLayers.Points || L.layerGroup(createMarkersArray(this.markers, newSettings));
- this.polylinesLayer = preserveLayers.Polylines || createPolylinesLayer(this.markers, this.map, this.timezone, this.routeOpacity, this.userSettings, this.distanceUnit);
- this.heatmapLayer = preserveLayers.Heatmap || L.heatLayer(this.markers.map((element) => [element[0], element[1], 0.2]), { radius: 20 });
- this.fogOverlay = preserveLayers["Fog of War"] || L.layerGroup();
- this.areasLayer = preserveLayers.Areas || L.layerGroup();
+ // Recreate polylines layer with new settings
+ this.polylinesLayer = createPolylinesLayer(
+ this.markers,
+ this.map,
+ this.timezone,
+ this.routeOpacity,
+ newSettings,
+ this.distanceUnit
+ );
// Redraw areas
fetchAndDrawAreas(this.areasLayer, this.apiKey);
diff --git a/app/javascript/maps/polylines.js b/app/javascript/maps/polylines.js
index 2c09022d..bd0a1595 100644
--- a/app/javascript/maps/polylines.js
+++ b/app/javascript/maps/polylines.js
@@ -4,11 +4,50 @@ import { getUrlParameter } from "../maps/helpers";
import { minutesToDaysHoursMinutes } from "../maps/helpers";
import { haversineDistance } from "../maps/helpers";
-export function addHighlightOnHover(polyline, map, polylineCoordinates, userSettings, distanceUnit) {
- const originalStyle = { color: "blue", opacity: userSettings.routeOpacity, weight: 3 };
- const highlightStyle = { color: "yellow", opacity: 1, weight: 5 };
+function getSpeedColor(speedKmh) {
+ console.log('Speed to color:', speedKmh + ' km/h');
- polyline.setStyle(originalStyle);
+ if (speedKmh > 100) {
+ console.log('Red - Very fast');
+ return '#FF0000';
+ }
+ if (speedKmh > 70) {
+ console.log('Orange - Fast');
+ return '#FFA500';
+ }
+ if (speedKmh > 40) {
+ console.log('Yellow - Moderate');
+ return '#FFFF00';
+ }
+ if (speedKmh > 20) {
+ console.log('Light green - Normal');
+ return '#90EE90';
+ }
+ console.log('Green - Slow');
+ return '#008000';
+}
+
+function calculateSpeed(point1, point2) {
+ const distanceKm = haversineDistance(point1[0], point1[1], point2[0], point2[1]); // in kilometers
+ const timeDiffSeconds = point2[4] - point1[4];
+
+ // Convert to km/h: (kilometers / seconds) * (3600 seconds / hour)
+ const speed = (distanceKm / timeDiffSeconds) * 3600;
+
+ console.log('Speed calculation:', {
+ distance: distanceKm + ' km',
+ timeDiff: timeDiffSeconds + ' seconds',
+ speed: speed + ' km/h',
+ point1: point1,
+ point2: point2
+ });
+
+ return speed;
+}
+
+export function addHighlightOnHover(polylineGroup, map, polylineCoordinates, userSettings, distanceUnit) {
+ const highlightStyle = { opacity: 1, weight: 5 };
+ const normalStyle = { opacity: userSettings.routeOpacity, weight: 3 };
const startPoint = polylineCoordinates[0];
const endPoint = polylineCoordinates[polylineCoordinates.length - 1];
@@ -28,66 +67,112 @@ export function addHighlightOnHover(polyline, map, polylineCoordinates, userSett
const startIcon = L.divIcon({ html: "🚥", className: "emoji-icon" });
const finishIcon = L.divIcon({ html: "🏁", className: "emoji-icon" });
- const isDebugMode = getUrlParameter("debug") === "true";
-
- let popupContent = `
- Start: ${firstTimestamp}
- End: ${lastTimestamp}
- Duration: ${timeOnRoute}
- Total Distance: ${formatDistance(totalDistance, distanceUnit)}
- `;
-
- if (isDebugMode) {
- const prevPoint = polylineCoordinates[0];
- const nextPoint = polylineCoordinates[polylineCoordinates.length - 1];
- const distanceToPrev = haversineDistance(prevPoint[0], prevPoint[1], startPoint[0], startPoint[1]);
- const distanceToNext = haversineDistance(endPoint[0], endPoint[1], nextPoint[0], nextPoint[1]);
-
- const timeBetweenPrev = Math.round((startPoint[4] - prevPoint[4]) / 60);
- const timeBetweenNext = Math.round((endPoint[4] - nextPoint[4]) / 60);
- const pointsNumber = polylineCoordinates.length;
-
- popupContent += `
- Prev Route: ${Math.round(distanceToPrev)}m and ${minutesToDaysHoursMinutes(timeBetweenPrev)} away
- Next Route: ${Math.round(distanceToNext)}m and ${minutesToDaysHoursMinutes(timeBetweenNext)} away
- Points: ${pointsNumber}
- `;
- }
-
- const startMarker = L.marker([startPoint[0], startPoint[1]], { icon: startIcon }).bindPopup(`Start: ${firstTimestamp}`);
- const endMarker = L.marker([endPoint[0], endPoint[1]], { icon: finishIcon }).bindPopup(popupContent);
+ const startMarker = L.marker([startPoint[0], startPoint[1]], { icon: startIcon });
+ const endMarker = L.marker([endPoint[0], endPoint[1]], { icon: finishIcon });
let hoverPopup = null;
- polyline.on("mouseover", function (e) {
- polyline.setStyle(highlightStyle);
+ polylineGroup.on("mouseover", function (e) {
+ // Find the closest segment and its speed
+ let closestSegment = null;
+ let minDistance = Infinity;
+ let currentSpeed = 0;
+
+ polylineGroup.eachLayer((layer) => {
+ if (layer instanceof L.Polyline) {
+ const layerLatLngs = layer.getLatLngs();
+ const distance = L.LineUtil.pointToSegmentDistance(
+ e.latlng,
+ layerLatLngs[0],
+ layerLatLngs[1]
+ );
+
+ if (distance < minDistance) {
+ minDistance = distance;
+ closestSegment = layer;
+
+ // Get the coordinates of the segment
+ const startPoint = layerLatLngs[0];
+ const endPoint = layerLatLngs[1];
+
+ console.log('Closest segment found:', {
+ startPoint,
+ endPoint,
+ distance
+ });
+
+ // Find matching points in polylineCoordinates
+ const startIdx = polylineCoordinates.findIndex(p => {
+ const latMatch = Math.abs(p[0] - startPoint.lat) < 0.0000001;
+ const lngMatch = Math.abs(p[1] - startPoint.lng) < 0.0000001;
+ return latMatch && lngMatch;
+ });
+
+ console.log('Start point index:', startIdx);
+ console.log('Original point:', startIdx !== -1 ? polylineCoordinates[startIdx] : 'not found');
+
+ if (startIdx !== -1 && startIdx < polylineCoordinates.length - 1) {
+ currentSpeed = calculateSpeed(
+ polylineCoordinates[startIdx],
+ polylineCoordinates[startIdx + 1]
+ );
+ console.log('Speed calculated:', currentSpeed);
+ }
+ }
+ }
+ });
+
+ // Highlight all segments in the group
+ polylineGroup.eachLayer((layer) => {
+ if (layer instanceof L.Polyline) {
+ layer.setStyle({
+ ...highlightStyle,
+ color: layer.options.originalColor
+ });
+ }
+ });
+
startMarker.addTo(map);
endMarker.addTo(map);
- const latLng = e.latlng;
+ const popupContent = `
+ Start: ${firstTimestamp}
+ End: ${lastTimestamp}
+ Duration: ${timeOnRoute}
+ Total Distance: ${formatDistance(totalDistance, distanceUnit)}
+ Current Speed: ${Math.round(currentSpeed)} km/h
+ `;
+
if (hoverPopup) {
map.closePopup(hoverPopup);
}
+
hoverPopup = L.popup()
- .setLatLng(latLng)
+ .setLatLng(e.latlng)
.setContent(popupContent)
.openOn(map);
});
- polyline.on("mouseout", function () {
- polyline.setStyle(originalStyle);
- map.closePopup(hoverPopup);
+ polylineGroup.on("mouseout", function () {
+ // Restore original styles for all segments
+ polylineGroup.eachLayer((layer) => {
+ if (layer instanceof L.Polyline) {
+ layer.setStyle({
+ ...normalStyle,
+ color: layer.options.originalColor
+ });
+ }
+ });
+
+ if (hoverPopup) {
+ map.closePopup(hoverPopup);
+ }
map.removeLayer(startMarker);
map.removeLayer(endMarker);
});
- polyline.on("click", function () {
- map.fitBounds(polyline.getBounds());
- });
-
- // Close the popup when clicking elsewhere on the map
- map.on("click", function () {
- map.closePopup(hoverPopup);
+ polylineGroup.on("click", function () {
+ map.fitBounds(polylineGroup.getBounds());
});
}
@@ -97,6 +182,7 @@ export function createPolylinesLayer(markers, map, timezone, routeOpacity, userS
const distanceThresholdMeters = parseInt(userSettings.meters_between_routes) || 500;
const timeThresholdMinutes = parseInt(userSettings.minutes_between_routes) || 60;
+ // Split into separate polylines based on distance/time thresholds
for (let i = 0, len = markers.length; i < len; i++) {
if (currentPolyline.length === 0) {
currentPolyline.push(markers[i]);
@@ -121,26 +207,45 @@ export function createPolylinesLayer(markers, map, timezone, routeOpacity, userS
return L.layerGroup(
splitPolylines.map((polylineCoordinates) => {
- const latLngs = polylineCoordinates.map((point) => [point[0], point[1]]);
- const polyline = L.polyline(latLngs, {
- color: "blue",
- opacity: 0.6,
- weight: 3,
- zIndexOffset: 400,
- pane: 'overlayPane'
- });
+ const segmentGroup = L.featureGroup();
- addHighlightOnHover(polyline, map, polylineCoordinates, userSettings, distanceUnit);
+ // Create segments with different colors based on speed
+ for (let i = 0; i < polylineCoordinates.length - 1; i++) {
+ const speed = calculateSpeed(polylineCoordinates[i], polylineCoordinates[i + 1]);
+ const color = getSpeedColor(speed);
- return polyline;
+ const segment = L.polyline(
+ [
+ [polylineCoordinates[i][0], polylineCoordinates[i][1]],
+ [polylineCoordinates[i + 1][0], polylineCoordinates[i + 1][1]]
+ ],
+ {
+ color: color,
+ originalColor: color,
+ opacity: routeOpacity,
+ weight: 3
+ }
+ );
+
+ segmentGroup.addLayer(segment);
+ }
+
+ // Add hover effect to the entire group of segments
+ addHighlightOnHover(segmentGroup, map, polylineCoordinates, userSettings, distanceUnit);
+
+ return segmentGroup;
})
).addTo(map);
}
export function updatePolylinesOpacity(polylinesLayer, opacity) {
- polylinesLayer.eachLayer((layer) => {
- if (layer instanceof L.Polyline) {
- layer.setStyle({ opacity: opacity });
+ polylinesLayer.eachLayer((groupLayer) => {
+ if (groupLayer instanceof L.LayerGroup) {
+ groupLayer.eachLayer((segment) => {
+ if (segment instanceof L.Polyline) {
+ segment.setStyle({ opacity: opacity });
+ }
+ });
}
});
}