Gradient Routing
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user