diff --git a/core/src/main/java/fr/sncf/osrd/standalone_sim/SafetySpeed.kt b/core/src/main/java/fr/sncf/osrd/standalone_sim/SafetySpeed.kt index 1993a69743a..717d59bb534 100644 --- a/core/src/main/java/fr/sncf/osrd/standalone_sim/SafetySpeed.kt +++ b/core/src/main/java/fr/sncf/osrd/standalone_sim/SafetySpeed.kt @@ -15,6 +15,15 @@ import fr.sncf.osrd.utils.units.Speed import fr.sncf.osrd.utils.units.kilometersPerHour import fr.sncf.osrd.utils.units.meters +/** + * Simple internal class representing a stop with safety speed. Makes the function logic more + * straightforward. + */ +private data class SafetySpeedStop( + val offset: Offset, + val isShortSlip: Boolean, +) + /** * Compute safety speed ranges, areas where the train has a lower speed limit because of a scheduled * stop. For details, see https://osrd.fr/en/docs/reference/design-docs/timetable/#modifiable-fields @@ -31,19 +40,28 @@ fun makeSafetySpeedRanges( val zonePathStartOffset = getRoutePathStartOffset(rawInfra, chunkPath, zonePaths) val signalOffsets = getSignalOffsets(infra, zonePaths, zonePathStartOffset) - val stopsWithSafetySpeed = schedule.filter { it.receptionSignal.isStopOnClosedSignal } + val stopsWithSafetySpeed = + schedule + .filter { it.receptionSignal.isStopOnClosedSignal } + .map { + SafetySpeedStop( + it.pathOffset, + it.receptionSignal == RJSTrainStop.RJSReceptionSignal.SHORT_SLIP_STOP + ) + } + .toMutableList() + makeEndOfPathStop(rawInfra, routes, signalOffsets)?.let { stopsWithSafetySpeed.add(it) } val res = distanceRangeMapOf() for (stop in stopsWithSafetySpeed) { // Currently, safety speed is applied to the next signal no matter the sight distance - val nextSignalOffset = - signalOffsets.firstOrNull { it >= stop.pathOffset }?.distance ?: chunkPath.length + val nextSignalOffset = signalOffsets.first { it >= stop.offset }.distance res.put( lower = nextSignalOffset - 200.meters, upper = nextSignalOffset, value = 30.kilometersPerHour, ) - if (stop.receptionSignal == RJSTrainStop.RJSReceptionSignal.SHORT_SLIP_STOP) { + if (stop.isShortSlip) { res.put( lower = nextSignalOffset - 100.meters, upper = nextSignalOffset, @@ -55,6 +73,21 @@ fun makeSafetySpeedRanges( return res.subMap(0.meters, chunkPath.length) } +/** + * Create a safety speed range at the end of the last route, either short slip or normal stop + * depending on whether it ends at a buffer stop. + */ +private fun makeEndOfPathStop( + infra: RawSignalingInfra, + routes: StaticIdxList, + signalOffsets: List> +): SafetySpeedStop? { + val lastRouteExit = infra.getRouteExit(routes.last()) + val isBufferStop = infra.isBufferStop(lastRouteExit.value) + if (isBufferStop) return SafetySpeedStop(signalOffsets.last(), true) + return null +} + /** Return the offsets of block-delimiting signals on the path. */ fun getSignalOffsets( infra: FullInfra, @@ -79,6 +112,9 @@ fun getSignalOffsets( } prevZonePathsLength += rawInfra.getZonePathLength(zonePath).distance } + // Add one "signal" at the end of the last route no matter what. + // There must be either a signal or a buffer stop, on which we may end safety speed ranges. + res.add(Offset(prevZonePathsLength - pathStartOffset.distance)) return res.filter { it.distance >= 0.meters } } diff --git a/core/src/test/kotlin/fr/sncf/osrd/standalone_sim/StandaloneSimulationTest.kt b/core/src/test/kotlin/fr/sncf/osrd/standalone_sim/StandaloneSimulationTest.kt index 786ba6b9680..1b0e4054e02 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/standalone_sim/StandaloneSimulationTest.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/standalone_sim/StandaloneSimulationTest.kt @@ -324,9 +324,15 @@ class StandaloneSimulationTest { DistanceRangeMap.RangeMapEntry(50.meters, 150.meters, 10.kilometersPerHour), DistanceRangeMap.RangeMapEntry( 10_200.meters, - 10_400.meters, + 10_300.meters, 30.kilometersPerHour ), + // Last buffer stop short slip range + DistanceRangeMap.RangeMapEntry( + 10_300.meters, + 10_400.meters, + 10.kilometersPerHour + ), ), ) assertEquals(expected, safetySpeedRanges)