Gradient Routing
This commit is contained in:
@@ -4,7 +4,7 @@ import React, { useEffect, useRef, useState } from "react";
|
|||||||
import mapboxgl from "mapbox-gl";
|
import mapboxgl from "mapbox-gl";
|
||||||
import GeocodeInput from './GeocodeInput';
|
import GeocodeInput from './GeocodeInput';
|
||||||
import { useCrashData } from '../hooks/useCrashData';
|
import { useCrashData } from '../hooks/useCrashData';
|
||||||
import { calculateRouteDensity } from '../../lib/mapUtils';
|
import { calculateRouteDensity, calculateRouteSegmentDensities, createRouteGradientStops } from '../../lib/mapUtils';
|
||||||
import { fetchSafeRoute, type SafeRouteData } from '../../lib/flaskApi';
|
import { fetchSafeRoute, type SafeRouteData } from '../../lib/flaskApi';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -315,14 +315,12 @@ export default function DirectionsSidebar({ mapRef, profile = "mapbox/driving",
|
|||||||
// Apply crash density gradient to all routes if crash data is available and gradient routes enabled
|
// Apply crash density gradient to all routes if crash data is available and gradient routes enabled
|
||||||
if (gradientRoutes && crashDataHook.data.length > 0) {
|
if (gradientRoutes && crashDataHook.data.length > 0) {
|
||||||
const routeCoordinates = (route.geometry as any).coordinates as [number, number][];
|
const routeCoordinates = (route.geometry as any).coordinates as [number, number][];
|
||||||
const crashDensity = calculateRouteDensity(routeCoordinates, crashDataHook.data, 150);
|
const segmentDensities = calculateRouteSegmentDensities(routeCoordinates, crashDataHook.data, 150);
|
||||||
// For now, create a simple gradient based on the density value
|
const gradientExpression = createRouteGradientStops(segmentDensities);
|
||||||
const gradientStops = [
|
|
||||||
[0, crashDensity > 2 ? '#ff0000' : crashDensity > 1 ? '#ff6600' : '#00ff00'],
|
|
||||||
[1, crashDensity > 2 ? '#8b0000' : crashDensity > 1 ? '#ff4500' : '#006400']
|
|
||||||
];
|
|
||||||
|
|
||||||
map.setPaintProperty(layerId, 'line-gradient', gradientStops as [string, ...any[]]);
|
console.log('🎨 Applying gradient to route:', layerId, 'segments:', segmentDensities.length, 'expression:', gradientExpression);
|
||||||
|
|
||||||
|
map.setPaintProperty(layerId, 'line-gradient', gradientExpression);
|
||||||
map.setPaintProperty(layerId, 'line-color', undefined); // Remove solid color when using gradient
|
map.setPaintProperty(layerId, 'line-color', undefined); // Remove solid color when using gradient
|
||||||
map.setPaintProperty(layerId, 'line-width', isSelected ? routeWidths[0] : routeWidths[1]);
|
map.setPaintProperty(layerId, 'line-width', isSelected ? routeWidths[0] : routeWidths[1]);
|
||||||
map.setPaintProperty(layerId, 'line-opacity', isSelected ? routeOpacities[0] : routeOpacities[1]);
|
map.setPaintProperty(layerId, 'line-opacity', isSelected ? routeOpacities[0] : routeOpacities[1]);
|
||||||
@@ -435,7 +433,7 @@ export default function DirectionsSidebar({ mapRef, profile = "mapbox/driving",
|
|||||||
|
|
||||||
// Add alternate route source and layer
|
// Add alternate route source and layer
|
||||||
if (!map.getSource("alternate-route")) {
|
if (!map.getSource("alternate-route")) {
|
||||||
map.addSource("alternate-route", { type: "geojson", data: alternateGeo });
|
map.addSource("alternate-route", { type: "geojson", data: alternateGeo, lineMetrics: true });
|
||||||
} else {
|
} else {
|
||||||
(map.getSource("alternate-route") as mapboxgl.GeoJSONSource).setData(alternateGeo);
|
(map.getSource("alternate-route") as mapboxgl.GeoJSONSource).setData(alternateGeo);
|
||||||
}
|
}
|
||||||
@@ -569,7 +567,7 @@ export default function DirectionsSidebar({ mapRef, profile = "mapbox/driving",
|
|||||||
|
|
||||||
// Add safe route to map
|
// Add safe route to map
|
||||||
if (!map.getSource("safe-route")) {
|
if (!map.getSource("safe-route")) {
|
||||||
map.addSource("safe-route", { type: "geojson", data: safeRouteGeo });
|
map.addSource("safe-route", { type: "geojson", data: safeRouteGeo, lineMetrics: true });
|
||||||
} else {
|
} else {
|
||||||
(map.getSource("safe-route") as mapboxgl.GeoJSONSource).setData(safeRouteGeo);
|
(map.getSource("safe-route") as mapboxgl.GeoJSONSource).setData(safeRouteGeo);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,4 +176,82 @@ export const calculateRouteDensity = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return totalCrashes / Math.max(1, routeLength);
|
return totalCrashes / Math.max(1, routeLength);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate crash density for each segment of a route (for gradient visualization)
|
||||||
|
*/
|
||||||
|
export const calculateRouteSegmentDensities = (
|
||||||
|
routeCoordinates: [number, number][],
|
||||||
|
crashes: CrashData[],
|
||||||
|
bufferMeters = 100
|
||||||
|
): number[] => {
|
||||||
|
const densities: number[] = [];
|
||||||
|
const routeLength = routeCoordinates.length;
|
||||||
|
|
||||||
|
if (routeLength < 2) return [];
|
||||||
|
|
||||||
|
for (let i = 0; i < routeLength - 1; i++) {
|
||||||
|
const segmentStart = routeCoordinates[i];
|
||||||
|
const segmentEnd = routeCoordinates[i + 1];
|
||||||
|
|
||||||
|
// Count crashes near this segment
|
||||||
|
const segmentCrashes = crashes.filter(crash => {
|
||||||
|
const crashPoint: [number, number] = [crash.longitude, crash.latitude];
|
||||||
|
const distanceToSegment = Math.min(
|
||||||
|
haversine(crashPoint, segmentStart),
|
||||||
|
haversine(crashPoint, segmentEnd)
|
||||||
|
);
|
||||||
|
return distanceToSegment <= bufferMeters;
|
||||||
|
});
|
||||||
|
|
||||||
|
densities.push(segmentCrashes.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return densities;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Mapbox gradient stops from route segment densities
|
||||||
|
*/
|
||||||
|
export const createRouteGradientStops = (segmentDensities: number[]): any => {
|
||||||
|
if (segmentDensities.length === 0) {
|
||||||
|
return [
|
||||||
|
'interpolate',
|
||||||
|
['linear'],
|
||||||
|
['line-progress'],
|
||||||
|
0, '#00ff00',
|
||||||
|
1, '#00ff00'
|
||||||
|
]; // Default green if no data
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxDensity = Math.max(...segmentDensities, 1); // Ensure at least 1 to avoid division by zero
|
||||||
|
const expression: any[] = ['interpolate', ['linear'], ['line-progress']];
|
||||||
|
|
||||||
|
segmentDensities.forEach((density, index) => {
|
||||||
|
const position = index / Math.max(1, segmentDensities.length - 1); // Position along route (0 to 1)
|
||||||
|
|
||||||
|
// Color based on density relative to max density
|
||||||
|
const intensity = density / maxDensity;
|
||||||
|
let color: string;
|
||||||
|
|
||||||
|
if (intensity > 0.7) {
|
||||||
|
color = '#ff0000'; // High density: red
|
||||||
|
} else if (intensity > 0.4) {
|
||||||
|
color = '#ff6600'; // Medium density: orange
|
||||||
|
} else if (intensity > 0.2) {
|
||||||
|
color = '#ffff00'; // Low-medium density: yellow
|
||||||
|
} else {
|
||||||
|
color = '#00ff00'; // Low density: green
|
||||||
|
}
|
||||||
|
|
||||||
|
expression.push(position, color);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure we have at least start and end
|
||||||
|
if (segmentDensities.length === 1) {
|
||||||
|
expression.push(1, expression[expression.length - 1]); // Duplicate last color for end
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user