import { MoveRight, MoveUpRight, MoveDownRight, CornerUpRight, CornerUpLeft, MoveLeft, MoveUpLeft, MoveDownLeft, CircleDot, RotateCw, GitMerge, CornerRightDown, CornerRightUp, Navigation, Mountain, Map, AlertTriangle, Compass, ArrowUp, ArrowUpRight, ArrowRight, ArrowDownRight, ArrowDown, ArrowDownLeft, ArrowLeft, ArrowUpLeft, MapPin } from 'lucide-react' import { useStore } from '../store' /** * Format distance with commas for feet, one decimal for miles. * Under 1 mile: "2,640 ft" * 1+ miles: "1.3 mi" */ function formatDistance(distanceM, distanceKm) { let meters = null if (distanceM !== undefined && distanceM !== null) { meters = distanceM } else if (distanceKm !== undefined && distanceKm !== null) { meters = distanceKm * 1000 } if (meters === null) return '' const miles = meters / 1609.34 if (miles < 1) { const feet = Math.round(meters * 3.28084) return feet.toLocaleString() + ' ft' } return miles.toFixed(1) + ' mi' } function formatTimeMin(minutes) { if (minutes < 60) return Math.round(minutes) + ' min' const h = Math.floor(minutes / 60) const m = Math.round(minutes % 60) return m > 0 ? h + 'h ' + m + 'm' : h + 'h' } // Compass arrow icon based on cardinal direction with rotation function CompassIcon({ cardinal, bearing, size = 16 }) { // Use bearing to rotate arrow, or fall back to cardinal-based icon if (bearing !== undefined && bearing !== null) { return ( ) } const props = { size, strokeWidth: 2 } const arrowMap = { 'N': ArrowUp, 'NNE': ArrowUpRight, 'NE': ArrowUpRight, 'ENE': ArrowRight, 'E': ArrowRight, 'ESE': ArrowRight, 'SE': ArrowDownRight, 'SSE': ArrowDownRight, 'S': ArrowDown, 'SSW': ArrowDownLeft, 'SW': ArrowDownLeft, 'WSW': ArrowLeft, 'W': ArrowLeft, 'WNW': ArrowLeft, 'NW': ArrowUpLeft, 'NNW': ArrowUpLeft, } const Icon = arrowMap[cardinal] || Compass return } // Wilderness maneuver icon function WildernessIcon({ type, cardinal, bearing, size = 16 }) { if (type === 'arrival') { return } return } // Network maneuver icon (Valhalla types) function ManeuverIcon({ type }) { const size = 16 const props = { size, strokeWidth: 1.5 } switch (type) { case 0: return case 1: return case 2: return case 3: return case 4: case 5: return case 6: return case 7: return case 8: return case 9: return case 10: case 11: case 12: return case 15: case 16: return case 24: return case 25: return case 26: return default: return } } /** * Add transport mode prefix to network maneuver instruction. * "Drive east on..." for auto, "Walk south on..." for foot, "Ride north on..." for mtb */ function formatNetworkInstruction(instruction, mode) { if (!instruction) return '' // Get verb based on mode const modeVerbs = { 'auto': 'Drive', 'foot': 'Walk', 'pedestrian': 'Walk', 'mtb': 'Ride', 'bicycle': 'Ride', 'atv': 'Drive', 'vehicle': 'Drive', } const verb = modeVerbs[mode] || 'Go' // Check if instruction starts with a direction verb we should replace const startsWithVerbs = [ 'Turn left', 'Turn right', 'Bear left', 'Bear right', 'Keep left', 'Keep right', 'Continue', 'Head', 'Go', 'Proceed', 'Make a', 'Take a', 'Start', 'Merge', 'Exit' ] for (const v of startsWithVerbs) { if (instruction.startsWith(v)) { // Already has a verb, return as-is (Valhalla instructions are already good) return instruction } } // If instruction starts with direction (north, south, etc.), prepend verb const directions = ['north', 'south', 'east', 'west', 'onto', 'on '] for (const dir of directions) { if (instruction.toLowerCase().startsWith(dir)) { return `${verb} ${instruction}` } } return instruction } export default function ManeuverList() { const routeResult = useStore((s) => s.routeResult) const routeLoading = useStore((s) => s.routeLoading) const routeError = useStore((s) => s.routeError) const routeMode = useStore((s) => s.routeMode) if (routeLoading) { return (
Calculating route...
) } if (routeError) { return (
{routeError}
) } if (!routeResult?.summary) return null const summary = routeResult.summary const features = routeResult.route?.features || [] const networkMode = summary.network_mode || routeMode || 'foot' // Extract maneuvers from each segment type const wildernessStartFeature = features.find(f => f.properties?.segment_type === 'wilderness' && f.properties?.segment_position === 'start' ) const networkFeature = features.find(f => f.properties?.segment_type === 'network') const wildernessEndFeature = features.find(f => f.properties?.segment_type === 'wilderness' && f.properties?.segment_position === 'end' ) const wildernessStartManeuvers = wildernessStartFeature?.properties?.maneuvers || [] const networkManeuvers = networkFeature?.properties?.maneuvers || [] const wildernessEndManeuvers = wildernessEndFeature?.properties?.maneuvers || [] const hasManeuvers = wildernessStartManeuvers.length > 0 || networkManeuvers.length > 0 || wildernessEndManeuvers.length > 0 return (
{/* Total summary */}
{formatDistance(null, summary.total_distance_km)} {formatTimeMin(summary.total_effort_minutes)}
{/* Segment breakdown */}
{summary.wilderness_distance_km > 0 && (
Wilderness {formatDistance(null, summary.wilderness_distance_km)} / {formatTimeMin(summary.wilderness_effort_minutes)}
)} {summary.network_distance_km > 0 && (
Road/Trail {formatDistance(null, summary.network_distance_km)} / {formatTimeMin(summary.network_duration_minutes)}
)}
{/* Warnings */} {(summary.barrier_crossings > 0 || summary.mvum_closed_crossings > 0) && (
{summary.barrier_crossings > 0 && (
{summary.barrier_crossings} barrier crossing{summary.barrier_crossings > 1 ? 's' : ''}
)} {summary.mvum_closed_crossings > 0 && (
{summary.mvum_closed_crossings} MVUM closure{summary.mvum_closed_crossings > 1 ? 's' : ''}
)}
)} {/* Turn-by-turn directions */} {hasManeuvers && (
Directions
{/* Wilderness start maneuvers */} {wildernessStartManeuvers.length > 0 && ( <>
Wilderness — On Foot
{wildernessStartManeuvers.map((man, i) => (

{man.instruction}

))} )} {/* Network maneuvers */} {networkManeuvers.length > 0 && ( <> {wildernessStartManeuvers.length > 0 && (
Road/Trail
)} {networkManeuvers.map((man, i) => (

{formatNetworkInstruction(man.instruction, networkMode)}

{formatDistance(null, man.distance_km)}

))} )} {/* Wilderness end maneuvers */} {wildernessEndManeuvers.length > 0 && ( <>
Wilderness — On Foot
{wildernessEndManeuvers.map((man, i) => (

{man.instruction}

))} )}
)}
) }