Skip to content

Commit 9338525

Browse files
committed
Add curve-aware speed analysis for rail paths
Closes #128 Implement curve radius analysis for rail transit paths (tram, metro, rail, monorail, etc.) that estimates speed limits imposed by track curvature and adjusts travel times accordingly when geometry is precise enough. transition-backend: - Add railCurves/ module with geometry analysis (radius estimation via inscribed circle method), curvature segmentation, speed profile generation (forward-backward pass with acceleration/deceleration), and segment travel time calculation - Add transitPaths.curveAnalysis socket API endpoint for on-demand curve analysis from the frontend - Add transitPathCurves.socketRoutes.test.ts with 14 tests covering edge cases, multi-segment paths, all rail modes, large angle detection, dwell time impact, and speed impact - Register new socket route in all.socketRoutes.ts transition-common: - Add railCurves/types.ts to transition-common (shared types) - Add geometry resolution detection (high/low/none/almostStraight) to skip curve analysis when path shapes are too coarse transition-frontend: - Add CurveStatsPanel with interactive speed profile chart (Recharts) supporting time and distance X-axis modes, proportional chart width, and configurable tick intervals - Add CurveStatsTable for per-segment travel time breakdown - Add map layers for curve segments (orange) and large-angle vertices (orange circles) to help users identify imprecise path geometry - Update TransitPathEdit to fetch curve analysis from backend API, auto-refresh on geometry changes (waypoint drag, node add/remove, undo/redo), and show progress notification during calculation - Apply curve-aware travel times to path data (operatingTime, speeds) so schedules and saved paths use curve-adjusted values - Add pathStale warning when parameters or geometry change, cleared after route recalculation - Update TransitPathStatistics to show curve-aware travel times for rail modes, hiding curve comparison when geometry is not high-res - Add styles for curve stats panel, table, chart, and toggle controls Locales: - Add English and French translations for all new curve analysis UI strings, warnings, labels, and progress notifications New package: - recharts for curve stats panel chart
1 parent b937403 commit 9338525

31 files changed

+5794
-210
lines changed

locales/en/main.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"Locked": "Locked",
2424
"delete": "delete",
2525
"Delete": "Delete",
26+
"Close": "Close",
2627
"loading": "loading",
2728
"Loading": "Loading",
2829
"Next": "Next",
@@ -307,5 +308,6 @@
307308
},
308309
"osmAttribution": "© OpenStreetMap contributors",
309310
"aerialAttribution": "Aerial imagery"
310-
}
311+
},
312+
"DownloadSvg": "Download SVG"
311313
}

locales/en/notifications.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"CalculateOdRouting": "OD routing calculation",
5656
"CalculateAccessibilityMap": "Accessibility polygon calculation",
5757
"UpdatingPathRoute": "Updating path route",
58+
"CalculatingCurves": "Calculating curve analysis",
5859
"UpdatingTransferableNodes": "Updating transferable nodes",
5960
"UploadingFile": "Uploading file",
6061
"UploadingGtfsFile": "Uploading GTFS file",

locales/en/transit.json

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,8 @@
465465
"TravelTimes": "Travel times",
466466
"IncludingDwellTimes": "With dwell times",
467467
"ExcludingDwellTimes": "Without dwell times",
468+
"ExcludingDwellTimesWithCurves": "Without dwell times (with curve limits)",
469+
"ExcludingDwellTimesNoCurves": "Without dwell times (no curve limits)",
468470
"IncludingDwellTimesAndLayover": "With dwell times and layover time",
469471
"LayoverTime": "Layover time",
470472
"Speeds": "Speeds",
@@ -513,7 +515,42 @@
513515
"DefaultDecelerationIsInvalid": "Deceleration is invalid.",
514516
"DefaultDecelerationIsTooLow": "Deceleration is too low.",
515517
"DefaultDecelerationIsTooHigh": "Deceleration is too high (uncomfortable)."
516-
}
518+
},
519+
"ShowCurveSegmentsLayer": "Show speed-restricted curve segments on map",
520+
"ShowCurveSegmentsLayerHelp": "Highlights path sections where the curve radius limits the maximum speed (shown in orange on the map).",
521+
"CurveStats": "Curve Speed Statistics",
522+
"CurveStatsSummary": "Summary",
523+
"TravelTimeWithoutCurves": "Travel time (no curve limits)",
524+
"TravelTimeWithCurves": "Travel time (with curve limits)",
525+
"CurveTimeDifference": "Time added by curves",
526+
"AvgSpeed": "Avg. speed",
527+
"AvgSpeedWithoutCurves": "Avg. speed (no curve limits)",
528+
"AvgSpeedWithCurves": "Avg. speed (with curve limits)",
529+
"RunningSpeed": "Running speed (max)",
530+
"PerSegmentBreakdown": "Per-Segment Breakdown",
531+
"Segment": "Segment",
532+
"Distance": "Distance",
533+
"TimeWithoutCurves": "Time (no curves)",
534+
"TimeWithCurves": "Time (curves)",
535+
"Difference": "Diff.",
536+
"MinRadius": "Min. Radius",
537+
"CurveSpeedLimit": "Curve Limit",
538+
"SpeedProfile": "Speed Profile",
539+
"SpeedVsTime": "Speed vs Time",
540+
"SpeedVsDistance": "Speed vs Distance",
541+
"DistanceKm": "Distance (km)",
542+
"TimeMinutes": "Time (min)",
543+
"SpeedKmH": "Speed (km/h)",
544+
"ActualSpeed": "Actual Speed",
545+
"MaxSpeedByCurve": "Maximum Speed (including curve limits)",
546+
"TotalRunningTime": "Running time",
547+
"TotalDwellTime": "Dwell time",
548+
"TotalTripTime": "Total trip time",
549+
"TravelTime": "Travel time",
550+
"PathStale": "Path parameters or geometry have changed. Click the recalculate route button to update travel times and statistics.",
551+
"GeometryResolutionWarningNone": "The path shape uses only straight lines between stations. Curves are ignored in the calculation; travel times use simple kinematic calculation (acceleration/cruise/deceleration).",
552+
"GeometryResolutionWarningLow": "The path shape has low resolution (abrupt direction changes detected). Curves are ignored in the calculation; travel times use simple kinematic calculation. Vertices with insufficient precision are highlighted with orange circles on the map. Add or adjust waypoints at these locations to smooth the path for curve-aware analysis.",
553+
"GeometryResolutionWarningAlmostStraight": "The path geometry appears to be almost a straight line with no intermediate waypoints. Please validate that there are no angles greater than 10° between stations. If there are curves, please draw the path shape as precisely as possible."
517554
},
518555
"transitRouting": {
519556
"Routing": "Routing",

locales/fr/main.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"Locked": "Verrouillé",
2424
"delete": "supprimer",
2525
"Delete": "Supprimer",
26+
"Close": "Fermer",
2627
"loading": "chargement",
2728
"Loading": "Chargement",
2829
"Next": "Suivant",
@@ -307,5 +308,6 @@
307308
},
308309
"osmAttribution": "© Contributeurs OpenStreetMap",
309310
"aerialAttribution": "Imagerie aérienne"
310-
}
311+
},
312+
"DownloadSvg": "Télécharger SVG"
311313
}

locales/fr/notifications.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"CalculateOdRouting": "Chargement OD",
5656
"CalculateAccessibilityMap": "Calcul du polygone d'accessibilité",
5757
"UpdatingPathRoute": "Mise à jour du tracé du parcours",
58+
"CalculatingCurves": "Calcul de l'analyse des courbes",
5859
"UpdatingTransferableNodes": "Mise à jour des noeuds d'arrêt transférables",
5960
"UploadingFile": "Téléversement du fichier",
6061
"UploadingGtfsFile": "Téléversement du fichier GTFS",

locales/fr/transit.json

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,8 @@
465465
"TravelTimes": "Temps de parcours",
466466
"IncludingDwellTimes": "Avec les temps d'arrêt",
467467
"ExcludingDwellTimes": "Sans les temps d'arrêt",
468+
"ExcludingDwellTimesWithCurves": "Sans les temps d'arrêt (avec limites de courbes)",
469+
"ExcludingDwellTimesNoCurves": "Sans les temps d'arrêt (sans limites de courbes)",
468470
"IncludingDwellTimesAndLayover": "Avec les temps d'arrêt et le battement",
469471
"LayoverTime": "Battement",
470472
"Speeds": "Vitesses",
@@ -513,7 +515,42 @@
513515
"DefaultDecelerationIsInvalid": "La décélération est invalide.",
514516
"DefaultDecelerationIsTooLow": "La décélération est trop faible.",
515517
"DefaultDecelerationIsTooHigh": "La décélération est trop élevée (inconfortable)."
516-
}
518+
},
519+
"ShowCurveSegmentsLayer": "Afficher les segments de courbe à vitesse limitée sur la carte",
520+
"ShowCurveSegmentsLayerHelp": "Met en évidence les sections du parcours où le rayon de courbure limite la vitesse maximale (affiché en orange sur la carte).",
521+
"CurveStats": "Statistiques de vitesse en courbe",
522+
"CurveStatsSummary": "Résumé",
523+
"TravelTimeWithoutCurves": "Temps de parcours (sans limites de courbe)",
524+
"TravelTimeWithCurves": "Temps de parcours (avec limites de courbe)",
525+
"CurveTimeDifference": "Temps ajouté par les courbes",
526+
"AvgSpeed": "Vitesse moy.",
527+
"AvgSpeedWithoutCurves": "Vitesse moy. (sans limites de courbe)",
528+
"AvgSpeedWithCurves": "Vitesse moy. (avec limites de courbe)",
529+
"RunningSpeed": "Vitesse de croisière (max)",
530+
"PerSegmentBreakdown": "Détail par segment",
531+
"Segment": "Segment",
532+
"Distance": "Distance",
533+
"TimeWithoutCurves": "Temps (sans courbes)",
534+
"TimeWithCurves": "Temps (courbes)",
535+
"Difference": "Diff.",
536+
"MinRadius": "Rayon min.",
537+
"CurveSpeedLimit": "Limite courbe",
538+
"SpeedProfile": "Profil de vitesse",
539+
"SpeedVsTime": "Vitesse vs Temps",
540+
"SpeedVsDistance": "Vitesse vs Distance",
541+
"DistanceKm": "Distance (km)",
542+
"TimeMinutes": "Temps (min)",
543+
"SpeedKmH": "Vitesse (km/h)",
544+
"ActualSpeed": "Vitesse réelle",
545+
"MaxSpeedByCurve": "Vitesse maximale (incluant les limites en courbe)",
546+
"TotalRunningTime": "Temps de parcours",
547+
"TotalDwellTime": "Temps d'arrêt",
548+
"TotalTripTime": "Temps total du trajet",
549+
"TravelTime": "Temps de parcours",
550+
"PathStale": "Les paramètres ou la géométrie du parcours ont changé. Cliquez sur le bouton de recalcul du tracé pour mettre à jour les temps de parcours et les statistiques.",
551+
"GeometryResolutionWarningNone": "La forme du parcours utilise seulement des lignes droites entre les stations. Les courbes sont ignorées dans le calcul; les temps de parcours utilisent un calcul cinématique simple (accélération/vitesse de croisière/décélération).",
552+
"GeometryResolutionWarningLow": "La forme du parcours a une résolution faible (changements de direction abrupts détectés). Les courbes sont ignorées dans le calcul; les temps de parcours utilisent un calcul cinématique simple. Les sommets avec une précision insuffisante sont surlignés par des cercles orange sur la carte. Ajoutez ou ajustez les points de passage à ces endroits pour lisser le parcours et activer l'analyse des courbes.",
553+
"GeometryResolutionWarningAlmostStraight": "La géométrie du parcours semble être presque une ligne droite sans points intermédiaires. Veuillez valider qu'il n'y a pas d'angles de plus de 10° entre les stations. S'il y a des courbes, veuillez dessiner la forme du parcours aussi précisément que possible."
517554
},
518555
"transitRouting": {
519556
"Routing": "Calcul de chemin",

0 commit comments

Comments
 (0)