Gradient Routing

This commit is contained in:
2025-09-28 07:42:45 -04:00
parent 8708ea2273
commit 5523497e19
2 changed files with 86 additions and 10 deletions

View File

@@ -4,7 +4,7 @@ import React, { useEffect, useRef, useState } from "react";
import mapboxgl from "mapbox-gl";
import GeocodeInput from './GeocodeInput';
import { useCrashData } from '../hooks/useCrashData';
import { calculateRouteDensity } from '../../lib/mapUtils';
import { calculateRouteDensity, calculateRouteSegmentDensities, createRouteGradientStops } from '../../lib/mapUtils';
import { fetchSafeRoute, type SafeRouteData } from '../../lib/flaskApi';
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
if (gradientRoutes && crashDataHook.data.length > 0) {
const routeCoordinates = (route.geometry as any).coordinates as [number, number][];
const crashDensity = calculateRouteDensity(routeCoordinates, crashDataHook.data, 150);
// For now, create a simple gradient based on the density value
const gradientStops = [
[0, crashDensity > 2 ? '#ff0000' : crashDensity > 1 ? '#ff6600' : '#00ff00'],
[1, crashDensity > 2 ? '#8b0000' : crashDensity > 1 ? '#ff4500' : '#006400']
];
const segmentDensities = calculateRouteSegmentDensities(routeCoordinates, crashDataHook.data, 150);
const gradientExpression = createRouteGradientStops(segmentDensities);
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-width', isSelected ? routeWidths[0] : routeWidths[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
if (!map.getSource("alternate-route")) {
map.addSource("alternate-route", { type: "geojson", data: alternateGeo });
map.addSource("alternate-route", { type: "geojson", data: alternateGeo, lineMetrics: true });
} else {
(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
if (!map.getSource("safe-route")) {
map.addSource("safe-route", { type: "geojson", data: safeRouteGeo });
map.addSource("safe-route", { type: "geojson", data: safeRouteGeo, lineMetrics: true });
} else {
(map.getSource("safe-route") as mapboxgl.GeoJSONSource).setData(safeRouteGeo);
}

View File

@@ -176,4 +176,82 @@ export const calculateRouteDensity = (
}
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;
};