From abe384d920e3ac3c785efc2dc5ae8c0d38c3461b Mon Sep 17 00:00:00 2001 From: Erashin Date: Wed, 24 Apr 2024 12:02:30 +0200 Subject: [PATCH 1/5] core: add signal projection v2 endpoint --- .../osrd/sim_infra/api/InterlockingInfra.kt | 6 + .../osrd/api/SignalProjectionEndpoint.java | 3 +- .../fr/sncf/osrd/cli/ApiServerCommand.java | 2 + .../ScheduleMetadataExtractor.kt | 12 +- .../osrd/standalone_sim/SignalProjection.kt | 4 +- .../fr/sncf/osrd/api/api_v2/CommonSchemas.kt | 16 + .../SignalProjectionEndpointV2.kt | 59 ++++ .../SignalProjectionRequest.kt | 33 ++ .../SignalProjectionResponse.kt | 32 ++ .../standalone_sim/SimulationResponse.kt | 16 +- .../signal_projection/SignalProjectionV2.kt | 299 ++++++++++++++++++ .../ScheduleMetadataExtractorV2.kt | 2 + 12 files changed, 465 insertions(+), 19 deletions(-) create mode 100644 core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionEndpointV2.kt create mode 100644 core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionRequest.kt create mode 100644 core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionResponse.kt create mode 100644 core/src/main/kotlin/fr/sncf/osrd/signal_projection/SignalProjectionV2.kt diff --git a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/InterlockingInfra.kt b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/InterlockingInfra.kt index eab20f6451f..4a38e985240 100644 --- a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/InterlockingInfra.kt +++ b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/InterlockingInfra.kt @@ -120,4 +120,10 @@ fun RoutingInfra.getRouteExit(route: RouteId): DirDetectorId { return getZonePathExit(getRoutePath(route).last()) } +fun RoutingInfra.convertRoutePath(routes: List): StaticIdxList { + val res = mutableStaticIdxArrayListOf() + for (route in routes) res.add(getRouteFromName(route)) + return res +} + typealias InterlockingInfra = RoutingInfra diff --git a/core/src/main/java/fr/sncf/osrd/api/SignalProjectionEndpoint.java b/core/src/main/java/fr/sncf/osrd/api/SignalProjectionEndpoint.java index 0e919e80870..aaa4d29d9fe 100644 --- a/core/src/main/java/fr/sncf/osrd/api/SignalProjectionEndpoint.java +++ b/core/src/main/java/fr/sncf/osrd/api/SignalProjectionEndpoint.java @@ -1,6 +1,7 @@ package fr.sncf.osrd.api; import static fr.sncf.osrd.api.pathfinding.PathPropUtilsKt.makeChunkPath; +import static fr.sncf.osrd.utils.JavaInteroperabilityToolsKt.toRouteIdList; import com.squareup.moshi.Json; import com.squareup.moshi.JsonAdapter; @@ -62,7 +63,7 @@ public Response act(Request req) throws Exception { .map(rjsRoutePath -> infra.rawInfra().getRouteFromName(rjsRoutePath.route)) .toList(); var result = SignalProjectionKt.project( - infra, chunkPath, routePath, request.signalSightings, request.zoneUpdates); + infra, chunkPath, toRouteIdList(routePath), request.signalSightings, request.zoneUpdates); result.warnings = recorder.warnings; diff --git a/core/src/main/java/fr/sncf/osrd/cli/ApiServerCommand.java b/core/src/main/java/fr/sncf/osrd/cli/ApiServerCommand.java index 479b5c401e1..30654089642 100644 --- a/core/src/main/java/fr/sncf/osrd/cli/ApiServerCommand.java +++ b/core/src/main/java/fr/sncf/osrd/cli/ApiServerCommand.java @@ -5,6 +5,7 @@ import fr.sncf.osrd.api.*; import fr.sncf.osrd.api.api_v2.path_properties.PathPropEndpoint; import fr.sncf.osrd.api.api_v2.pathfinding.PathfindingBlocksEndpointV2; +import fr.sncf.osrd.api.api_v2.project_signals.SignalProjectionEndpointV2; import fr.sncf.osrd.api.api_v2.standalone_sim.SimulationEndpoint; import fr.sncf.osrd.api.pathfinding.PathfindingBlocksEndpoint; import fr.sncf.osrd.api.stdcm.STDCMEndpoint; @@ -94,6 +95,7 @@ public int run() { "/v2/standalone_simulation", new SimulationEndpoint(infraManager, electricalProfileSetManager)), new FkRegex("/project_signals", new SignalProjectionEndpoint(infraManager)), + new FkRegex("/v2/project_signals", new SignalProjectionEndpointV2(infraManager)), new FkRegex("/detect_conflicts", new ConflictDetectionEndpoint()), new FkRegex("/cache_status", new InfraCacheStatusEndpoint(infraManager)), new FkRegex("/version", new VersionEndpoint()), diff --git a/core/src/main/java/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractor.kt b/core/src/main/java/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractor.kt index 2ea80bfd08d..9383366e4ea 100644 --- a/core/src/main/java/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractor.kt +++ b/core/src/main/java/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractor.kt @@ -546,9 +546,19 @@ fun pathSignalsInEnvelope( blockPath: StaticIdxList, blockInfra: BlockInfra, envelope: EnvelopeTimeInterpolate, +): List { + return pathSignalsInRange(startOffset, blockPath, blockInfra, 0.meters, envelope.endPos.meters) +} + +fun pathSignalsInRange( + startOffset: Distance, + blockPath: StaticIdxList, + blockInfra: BlockInfra, + rangeStart: Distance, + rangeEnd: Distance, ): List { return pathSignals(startOffset, blockPath, blockInfra).filter { signal -> - signal.pathOffset >= 0.meters && signal.pathOffset <= envelope.endPos.meters + signal.pathOffset in rangeStart..rangeEnd } } diff --git a/core/src/main/java/fr/sncf/osrd/standalone_sim/SignalProjection.kt b/core/src/main/java/fr/sncf/osrd/standalone_sim/SignalProjection.kt index 9da3e2c1102..ccee6391946 100644 --- a/core/src/main/java/fr/sncf/osrd/standalone_sim/SignalProjection.kt +++ b/core/src/main/java/fr/sncf/osrd/standalone_sim/SignalProjection.kt @@ -13,7 +13,6 @@ import fr.sncf.osrd.standalone_sim.result.ResultTrain.ZoneUpdate import fr.sncf.osrd.standalone_sim.result.SignalUpdate import fr.sncf.osrd.utils.indexing.StaticIdxList import fr.sncf.osrd.utils.indexing.mutableStaticIdxArrayListOf -import fr.sncf.osrd.utils.toRouteIdList import java.awt.Color import kotlin.math.abs @@ -22,7 +21,7 @@ data class SignalAspectChangeEvent(val newAspect: String, val time: Long) fun project( fullInfra: FullInfra, chunkPath: ChunkPath, - routePathIds: List, + routePath: StaticIdxList, signalSightings: List, zoneUpdates: List ): SignalProjectionResult { @@ -52,7 +51,6 @@ fun project( } // Recover blocks from the route path - val routePath = toRouteIdList(routePathIds) val detailedBlockPath = recoverBlockPath(simulator, fullInfra, routePath) val blockPath = mutableStaticIdxArrayListOf() for (block in detailedBlockPath) blockPath.add(block.block) diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/CommonSchemas.kt b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/CommonSchemas.kt index b18f11e9f55..ffc6670a3b4 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/CommonSchemas.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/CommonSchemas.kt @@ -2,9 +2,11 @@ package fr.sncf.osrd.api.api_v2 import com.squareup.moshi.* import fr.sncf.osrd.railjson.schema.common.graph.EdgeDirection +import fr.sncf.osrd.sim_infra.api.Path import fr.sncf.osrd.sim_infra.api.TrackSection import fr.sncf.osrd.utils.units.Distance import fr.sncf.osrd.utils.units.Offset +import fr.sncf.osrd.utils.units.TimeDelta data class TrackRange( @Json(name = "track_section") val trackSection: String, @@ -14,3 +16,17 @@ data class TrackRange( ) class RangeValues(val boundaries: List = listOf(), val values: List = listOf()) + +class ZoneUpdate( + val zone: String, + val time: TimeDelta, + val position: Offset, + @Json(name = "is_entry") val isEntry: Boolean, +) + +class SignalSighting( + val signal: String, + val time: TimeDelta, + val position: Offset, + val state: String, +) diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionEndpointV2.kt b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionEndpointV2.kt new file mode 100644 index 00000000000..710327022c2 --- /dev/null +++ b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionEndpointV2.kt @@ -0,0 +1,59 @@ +package fr.sncf.osrd.api.api_v2.project_signals + +import fr.sncf.osrd.api.ExceptionHandler +import fr.sncf.osrd.api.InfraManager +import fr.sncf.osrd.api.pathfinding.makeChunkPath +import fr.sncf.osrd.reporting.warnings.DiagnosticRecorderImpl +import fr.sncf.osrd.signal_projection.projectSignals +import fr.sncf.osrd.sim_infra.api.convertRoutePath +import org.takes.Request +import org.takes.Response +import org.takes.Take +import org.takes.rq.RqPrint +import org.takes.rs.RsJson +import org.takes.rs.RsText +import org.takes.rs.RsWithBody +import org.takes.rs.RsWithStatus + +class SignalProjectionEndpointV2(private val infraManager: InfraManager) : Take { + override fun act(req: Request?): Response { + val recorder = DiagnosticRecorderImpl(false) + return try { + val body = RqPrint(req).printBody() + val request = + signalProjectionRequestAdapter.fromJson(body) + ?: return RsWithStatus(RsText("missing request body"), 400) + + // Load infra + val infra = infraManager.getInfra(request.infra, request.expectedVersion, recorder) + + // Parse path + val chunkPath = makeChunkPath(infra.rawInfra, request.trackSectionRanges) + val routePath = infra.rawInfra.convertRoutePath(request.routes) + + val signalProjections = mutableMapOf>() + for ((id, trainSimulation) in request.trainSimulations) { + val signalProjection = + projectSignals( + infra, + chunkPath, + routePath, + trainSimulation.signalSightings, + trainSimulation.zoneUpdates, + trainSimulation.simulationEndTime + ) + signalProjections[id] = signalProjection + } + + RsJson( + RsWithBody( + signalProjectionResponseAdapter.toJson( + SignalProjectionResponse(signalProjections.toMap()) + ) + ) + ) + } catch (ex: Throwable) { + ExceptionHandler.handle(ex) + } + } +} diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionRequest.kt b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionRequest.kt new file mode 100644 index 00000000000..f2fa83db568 --- /dev/null +++ b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionRequest.kt @@ -0,0 +1,33 @@ +package fr.sncf.osrd.api.api_v2.project_signals + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import fr.sncf.osrd.api.api_v2.SignalSighting +import fr.sncf.osrd.api.api_v2.TrackRange +import fr.sncf.osrd.api.api_v2.ZoneUpdate +import fr.sncf.osrd.utils.json.UnitAdapterFactory +import fr.sncf.osrd.utils.units.TimeDelta + +class SignalProjectionRequest( + @Json(name = "track_section_ranges") var trackSectionRanges: List, + var routes: List, + @Json(name = "train_simulations") var trainSimulations: Map, + var infra: String, + /** The expected infrastructure version */ + @Json(name = "expected_version") var expectedVersion: String, +) + +class TrainSimulation( + @Json(name = "signal_sightings") val signalSightings: Collection, + @Json(name = "zone_updates") val zoneUpdates: Collection, + @Json(name = "simulation_end_time") val simulationEndTime: TimeDelta, +) + +val signalProjectionRequestAdapter: JsonAdapter = + Moshi.Builder() + .addLast(UnitAdapterFactory()) + .addLast(KotlinJsonAdapterFactory()) + .build() + .adapter(SignalProjectionRequest::class.java) diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionResponse.kt b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionResponse.kt new file mode 100644 index 00000000000..9e90ae8fa7e --- /dev/null +++ b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionResponse.kt @@ -0,0 +1,32 @@ +package fr.sncf.osrd.api.api_v2.project_signals + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import fr.sncf.osrd.sim_infra.api.Path +import fr.sncf.osrd.utils.json.UnitAdapterFactory +import fr.sncf.osrd.utils.units.Offset +import fr.sncf.osrd.utils.units.TimeDelta + +class SignalProjectionResponse( + @Json(name = "signal_updates") val signalUpdates: Map> +) + +class SignalUpdate( + @Json(name = "signal_id") val signalID: String, + @Json(name = "time_start") val timeStart: TimeDelta, + @Json(name = "time_end") val timeEnd: TimeDelta, + @Json(name = "position_start") val positionStart: Offset, + @Json(name = "position_end") val positionEnd: Offset, + val color: Int, + val blinking: Boolean, + @Json(name = "aspect_label") val aspectLabel: String, +) + +val signalProjectionResponseAdapter: JsonAdapter = + Moshi.Builder() + .addLast(UnitAdapterFactory()) + .addLast(KotlinJsonAdapterFactory()) + .build() + .adapter(SignalProjectionResponse::class.java) diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/standalone_sim/SimulationResponse.kt b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/standalone_sim/SimulationResponse.kt index 202efa74452..58aca52d9f9 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/standalone_sim/SimulationResponse.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/standalone_sim/SimulationResponse.kt @@ -4,6 +4,8 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import fr.sncf.osrd.api.api_v2.SignalSighting +import fr.sncf.osrd.api.api_v2.ZoneUpdate import fr.sncf.osrd.sim_infra.api.Path import fr.sncf.osrd.utils.json.UnitAdapterFactory import fr.sncf.osrd.utils.units.Offset @@ -62,20 +64,6 @@ class RoutingZoneRequirement( @Json(name = "end_time") val endTime: TimeDelta, ) -class ZoneUpdate( - val zone: String, - val time: TimeDelta, - val position: Offset, - @Json(name = "is_entry") val isEntry: Boolean, -) - -class SignalSighting( - val signal: String, - val time: TimeDelta, - val position: Offset, - val state: String, -) - open class ReportTrain( val positions: List>, val times: List, // Times are compared to the departure time diff --git a/core/src/main/kotlin/fr/sncf/osrd/signal_projection/SignalProjectionV2.kt b/core/src/main/kotlin/fr/sncf/osrd/signal_projection/SignalProjectionV2.kt new file mode 100644 index 00000000000..e3a0953813a --- /dev/null +++ b/core/src/main/kotlin/fr/sncf/osrd/signal_projection/SignalProjectionV2.kt @@ -0,0 +1,299 @@ +package fr.sncf.osrd.signal_projection + +import fr.sncf.osrd.api.FullInfra +import fr.sncf.osrd.api.api_v2.SignalSighting +import fr.sncf.osrd.api.api_v2.ZoneUpdate +import fr.sncf.osrd.api.api_v2.project_signals.SignalUpdate +import fr.sncf.osrd.reporting.exceptions.OSRDError +import fr.sncf.osrd.signaling.SignalingSimulator +import fr.sncf.osrd.signaling.ZoneStatus +import fr.sncf.osrd.sim_infra.api.* +import fr.sncf.osrd.sim_infra.impl.ChunkPath +import fr.sncf.osrd.standalone_sim.* +import fr.sncf.osrd.utils.indexing.StaticIdxList +import fr.sncf.osrd.utils.indexing.mutableStaticIdxArrayListOf +import fr.sncf.osrd.utils.units.* +import java.awt.Color + +data class SignalAspectChangeEventV2(val newAspect: String, val time: TimeDelta) + +fun projectSignals( + fullInfra: FullInfra, + chunkPath: ChunkPath, + routePath: StaticIdxList, + signalSightings: Collection, + zoneUpdates: Collection, + simulationEndTime: TimeDelta +): List { + val rawInfra = fullInfra.rawInfra + val loadedSignalInfra = fullInfra.loadedSignalInfra + val blockInfra = fullInfra.blockInfra + val simulator = fullInfra.signalingSimulator + val sigModuleManager = simulator.sigModuleManager + + // TODO: allowed signaling systems should depend on the type of train + val sigSystemManager = simulator.sigModuleManager + val bal = sigSystemManager.findSignalingSystemOrThrow("BAL") + val bapr = sigSystemManager.findSignalingSystemOrThrow("BAPR") + val tvm300 = sigSystemManager.findSignalingSystemOrThrow("TVM300") + val tvm430 = sigSystemManager.findSignalingSystemOrThrow("TVM430") + + val leastConstrainingStates = mutableMapOf() + leastConstrainingStates[bal] = (sigModuleManager.getStateSchema(bal)) { value("aspect", "VL") } + leastConstrainingStates[bapr] = (sigModuleManager.getStateSchema(bapr)) { + value("aspect", "VL") + } + leastConstrainingStates[tvm300] = (sigModuleManager.getStateSchema(tvm300)) { + value("aspect", "300VL") + } + leastConstrainingStates[tvm430] = (sigModuleManager.getStateSchema(tvm430)) { + value("aspect", "300VL") + } + + // Recover blocks from the route path + val detailedBlockPath = recoverBlockPath(simulator, fullInfra, routePath) + val blockPath = mutableStaticIdxArrayListOf() + for (block in detailedBlockPath) blockPath.add(block.block) + + val zoneMap = mutableMapOf() + var zoneCount = 0 + for (block in blockPath) { + for (zonePath in blockInfra.getBlockPath(block)) { + val zone = rawInfra.getNextZone(rawInfra.getZonePathEntry(zonePath))!! + val zoneName = rawInfra.getZoneName(zone) + zoneMap[zoneName] = zoneCount + zoneCount++ + } + } + + // Compute signal updates + val startOffset = + trainPathBlockOffset(fullInfra.rawInfra, fullInfra.blockInfra, blockPath, chunkPath) + // Compute path signals on path + val pathSignals = + pathSignalsInRange( + startOffset, + blockPath, + blockInfra, + 0.meters, + chunkPath.endOffset - chunkPath.beginOffset + ) + if (pathSignals.isEmpty()) return emptyList() + + val signalAspectChangeEvents = + computeSignalAspectChangeEvents( + blockPath, + routePath, + zoneMap, + blockInfra, + pathSignals, + zoneUpdates, + simulator, + rawInfra, + loadedSignalInfra, + leastConstrainingStates, + ) + val signalUpdates = + signalUpdates( + pathSignals, + signalAspectChangeEvents, + loadedSignalInfra, + rawInfra, + signalSightings, + Length(chunkPath.endOffset - chunkPath.beginOffset), + simulationEndTime + ) + return signalUpdates +} + +private fun computeSignalAspectChangeEvents( + blockPath: StaticIdxList, + routePath: StaticIdxList, + zoneToPathIndexMap: Map, + blockInfra: BlockInfra, + pathSignals: List, + zoneUpdates: Collection, + simulator: SignalingSimulator, + rawInfra: RawInfra, + loadedSignalInfra: LoadedSignalInfra, + leastConstrainingStates: Map, +): Map> { + val routes = routePath.toList() + val zoneCount = blockPath.sumOf { blockInfra.getBlockPath(it).size } + val zoneStates = ArrayList(zoneCount) + for (i in 0 until zoneCount) zoneStates.add(ZoneStatus.CLEAR) + + val signalAspects = + pathSignals + .associateBy( + { it.signal }, + { + leastConstrainingStates[loadedSignalInfra.getSignalingSystem(it.signal)]!! + .getEnum("aspect") + } + ) + .toMutableMap() + + val lastSignal = pathSignals.last().signal + val lastSignalDriver = loadedSignalInfra.getDrivers(lastSignal).lastOrNull() + val lastSignalInputSystem = + if (lastSignalDriver != null) { + simulator.sigModuleManager.getInputSignalingSystem(lastSignalDriver) + } else { + loadedSignalInfra.getSignalingSystem( + lastSignal + ) // If it could not connect to anything, lets pretend it does to itself + } + val nextSignalState = leastConstrainingStates[lastSignalInputSystem]!! + + val signalAspectChangeEvents = + pathSignals.associateBy({ it }, { mutableListOf() }) + for (event in zoneUpdates) { + if (!zoneToPathIndexMap.containsKey(event.zone)) continue + if (event.isEntry) zoneStates[zoneToPathIndexMap[event.zone]!!] = ZoneStatus.OCCUPIED + else zoneStates[zoneToPathIndexMap[event.zone]!!] = ZoneStatus.CLEAR + + val simulatedSignalStates = + simulator.evaluate( + rawInfra, + loadedSignalInfra, + blockInfra, + blockPath, + routes, + blockPath.size, + zoneStates, + ZoneStatus.CLEAR, + nextSignalState + ) + val simulatedAspects = simulatedSignalStates.mapValues { it.value.getEnum("aspect") } + for (pathSignal in pathSignals) { + val signal = pathSignal.signal + val aspect = simulatedAspects[signal] ?: continue + if (signalAspects[signal]!! == aspect) continue + signalAspectChangeEvents[pathSignal]!!.add( + SignalAspectChangeEventV2(aspect, event.time) + ) + signalAspects[signal] = aspect + } + } + return signalAspectChangeEvents +} + +private fun signalUpdates( + signalsOnPath: List, + signalAspectChangeEvents: Map>, + loadedSignalInfra: LoadedSignalInfra, + rawInfra: RawInfra, + signalSightings: Collection, + pathLength: Length, + simulationEndTime: TimeDelta, +): MutableList { + val signalUpdates = mutableListOf() + + // Let's generate signal updates for the SNCF GET + // The logic here is specific to that, signalUpdates shouldn't be used for anything else + // TODO: maybe have those be specific to the signaling system + // FIXME: this doesn't work if the train goes through the same signal twice + // This is probably a weird edge case anyway + fun blinking(aspect: String): Boolean { + return aspect == "(A)" + } + + fun color(aspect: String): Int { + return when (aspect) { + "VL" -> Color.GREEN.rgb + "A" -> Color.YELLOW.rgb + "(A)" -> Color.YELLOW.rgb + "S" -> Color.RED.rgb + "C" -> Color.RED.rgb + "300VL" -> Color.GREEN.rgb + "300(VL)" -> Color.GRAY.rgb + "270A" -> Color.GRAY.rgb + "220A" -> Color.GRAY.rgb + "160A" -> Color.GRAY.rgb + "080A" -> Color.GRAY.rgb + "000" -> Color.GRAY.rgb + "RRR" -> Color.RED.rgb + "OCCUPIED" -> Color.RED.rgb + else -> throw OSRDError.newAspectError(aspect) + } + } + + val signalSightingMap = signalSightings.associateBy { it.signal } + + val nextSignal = mutableMapOf() + for (i in 0 until signalsOnPath.size - 1) nextSignal[signalsOnPath[i].signal] = + signalsOnPath[i + 1] + + for ((pathSignal, events) in signalAspectChangeEvents) { + val signal = pathSignal.signal + val physicalSignalId = loadedSignalInfra.getPhysicalSignal(signal) + val physicalSignalName = rawInfra.getPhysicalSignalName(physicalSignalId) + val positionStart = pathSignal.pathOffset + val positionEnd = + if (nextSignal.contains(signal)) nextSignal[signal]!!.pathOffset + else pathLength.distance + + if (events.isEmpty()) continue + + // Compute the "green" section + // It happens before the first event + if ( + events.first().time != Duration.ZERO && signalSightingMap.contains(physicalSignalName) + ) { + val event = events.first() + val timeEnd = event.time + val timeStart = signalSightingMap[physicalSignalName]!!.time + if (timeEnd > timeStart) { + signalUpdates.add( + SignalUpdate( + physicalSignalName!!, + timeStart, + timeEnd, + Offset(positionStart), + Offset(positionEnd), + color("VL"), + blinking("VL"), + "VL" + ) + ) + } + } + + for (i in 0 until events.size - 1) { + val event = events[i] + val nextEvent = events[i + 1] + signalUpdates.add( + SignalUpdate( + physicalSignalName!!, + event.time, + nextEvent.time, + Offset(positionStart), + Offset(positionEnd), + color(event.newAspect), + blinking(event.newAspect), + event.newAspect + ) + ) + } + + // The last event only generates an update if the signal doesn't return to VL + if (events.last().newAspect != "VL" && events.last().newAspect != "300VL") { + val event = events.last() + val timeStart = event.time + signalUpdates.add( + SignalUpdate( + physicalSignalName!!, + timeStart, + simulationEndTime, + Offset(positionStart), + Offset(positionEnd), + color(event.newAspect), + blinking(event.newAspect), + event.newAspect + ) + ) + } + } + return signalUpdates +} diff --git a/core/src/main/kotlin/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractorV2.kt b/core/src/main/kotlin/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractorV2.kt index a1c086c23c1..1ea3d4dc11e 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractorV2.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractorV2.kt @@ -1,6 +1,8 @@ package fr.sncf.osrd.standalone_sim import fr.sncf.osrd.api.FullInfra +import fr.sncf.osrd.api.api_v2.SignalSighting +import fr.sncf.osrd.api.api_v2.ZoneUpdate import fr.sncf.osrd.api.api_v2.standalone_sim.* import fr.sncf.osrd.conflicts.* import fr.sncf.osrd.envelope.Envelope From 0bb4582ceb091c502ada77726db778e87b8e052d Mon Sep 17 00:00:00 2001 From: Erashin Date: Wed, 24 Apr 2024 12:03:03 +0200 Subject: [PATCH 2/5] core: modify path prop endpoint test to check operational points --- .../path_properties/PathPropEndpoint.kt | 4 +- ...{PathPropResult.kt => PathPropResponse.kt} | 24 +++++------ ...verter.kt => PathPropResponseConverter.kt} | 14 +++---- .../osrd/pathfinding/PathPropEndpointTest.kt | 40 +++++++++++++++---- 4 files changed, 53 insertions(+), 29 deletions(-) rename core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/{PathPropResult.kt => PathPropResponse.kt} (77%) rename core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/{PathPropResultConverter.kt => PathPropResponseConverter.kt} (94%) diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/PathPropEndpoint.kt b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/PathPropEndpoint.kt index d5175f2c06f..7968eb2776c 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/PathPropEndpoint.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/PathPropEndpoint.kt @@ -26,9 +26,9 @@ class PathPropEndpoint(private val infraManager: InfraManager) : Take { val infra = infraManager.getInfra(request.infra, request.expectedVersion, recorder) val pathProps = makePathProps(infra.rawInfra, request.trackSectionRanges) - val res = makePathPropResult(pathProps, infra.rawInfra) + val res = makePathPropResponse(pathProps, infra.rawInfra) - RsJson(RsWithBody(pathPropResultAdapter.toJson(res))) + RsJson(RsWithBody(pathPropResponseAdapter.toJson(res))) } catch (ex: Throwable) { ExceptionHandler.handle(ex) } diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/PathPropResult.kt b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/PathPropResponse.kt similarity index 77% rename from core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/PathPropResult.kt rename to core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/PathPropResponse.kt index 6ca7686b79b..19bb64844b9 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/PathPropResult.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/PathPropResponse.kt @@ -11,12 +11,12 @@ import fr.sncf.osrd.utils.json.UnitAdapterFactory import fr.sncf.osrd.utils.units.Distance import fr.sncf.osrd.utils.units.Offset -class PathPropResult( +class PathPropResponse( val slopes: RangeValues, val gradients: RangeValues, val electrifications: RangeValues, val geometry: RJSLineString, - @Json(name = "operational_points") val operationalPoints: List + @Json(name = "operational_points") val operationalPoints: List ) data class RangeValues(val boundaries: List, val values: List) @@ -29,25 +29,25 @@ data class Neutral(@Json(name = "lower_pantograph") val lowerPantograph: Boolean class NonElectrified : Electrification -class OperationalPointResult( +data class OperationalPointResponse( val id: String, - val part: OperationalPointPartResult, + val part: OperationalPointPartResponse, val extensions: OperationalPointExtensions?, val position: Offset ) -class OperationalPointPartResult( +data class OperationalPointPartResponse( val track: String, val position: Double, val extensions: OperationalPointPartExtension? ) -class OperationalPointExtensions( +data class OperationalPointExtensions( val sncf: OperationalPointSncfExtension?, val identifier: OperationalPointIdentifierExtension? ) -class OperationalPointSncfExtension( +data class OperationalPointSncfExtension( val ci: Long, val ch: String, @Json(name = "ch_short_label") val chShortLabel: String, @@ -55,11 +55,11 @@ class OperationalPointSncfExtension( val trigram: String ) -class OperationalPointIdentifierExtension(val name: String, val uic: Long) +data class OperationalPointIdentifierExtension(val name: String, val uic: Long) -class OperationalPointPartExtension(val sncf: OperationalPointPartSncfExtension?) +data class OperationalPointPartExtension(val sncf: OperationalPointPartSncfExtension?) -class OperationalPointPartSncfExtension(val kp: String) +data class OperationalPointPartSncfExtension(val kp: String) val polymorphicAdapter: PolymorphicJsonAdapterFactory = PolymorphicJsonAdapterFactory.of(Electrification::class.java, "type") @@ -67,10 +67,10 @@ val polymorphicAdapter: PolymorphicJsonAdapterFactory = .withSubtype(Neutral::class.java, "neutral_section") .withSubtype(NonElectrified::class.java, "non_electrified") -val pathPropResultAdapter: JsonAdapter = +val pathPropResponseAdapter: JsonAdapter = Moshi.Builder() .add(polymorphicAdapter) .addLast(UnitAdapterFactory()) .addLast(KotlinJsonAdapterFactory()) .build() - .adapter(PathPropResult::class.java) + .adapter(PathPropResponse::class.java) diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/PathPropResultConverter.kt b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/PathPropResponseConverter.kt similarity index 94% rename from core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/PathPropResultConverter.kt rename to core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/PathPropResponseConverter.kt index f431b45e612..5aeaee52d37 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/PathPropResultConverter.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/path_properties/PathPropResponseConverter.kt @@ -10,11 +10,11 @@ import fr.sncf.osrd.utils.DistanceRangeMap import fr.sncf.osrd.utils.DistanceRangeMapImpl import fr.sncf.osrd.utils.units.Distance -fun makePathPropResult( +fun makePathPropResponse( pathProperties: PathProperties, rawInfra: RawSignalingInfra -): PathPropResult { - return PathPropResult( +): PathPropResponse { + return PathPropResponse( makeSlopes(pathProperties), makeGradients(pathProperties), makeElectrifications(pathProperties), @@ -54,8 +54,8 @@ private fun makeGeographic(path: PathProperties): RJSLineString { private fun makeOperationalPoints( path: PathProperties, rawInfra: RawSignalingInfra -): List { - val res = mutableListOf() +): List { + val res = mutableListOf() for ((opPartId, offset) in path.getOperationalPointParts()) { val operationalPointId = rawInfra.getOperationalPointPartOpId(opPartId) val trackSection = @@ -67,7 +67,7 @@ private fun makeOperationalPoints( chunkOffset.distance val opPartProps = rawInfra.getOperationalPointPartProps(opPartId) val opPartResult = - OperationalPointPartResult( + OperationalPointPartResponse( trackSectionName, opPartTrackSectionOffset.meters, if (opPartProps["kp"] == null) null @@ -99,7 +99,7 @@ private fun makeOperationalPoints( if (opSncfExtension == null && opIdExtension == null) null else OperationalPointExtensions(opSncfExtension, opIdExtension) val opResult = - OperationalPointResult(operationalPointId, opPartResult, opExtensions, offset) + OperationalPointResponse(operationalPointId, opPartResult, opExtensions, offset) res.add(opResult) } return res diff --git a/core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathPropEndpointTest.kt b/core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathPropEndpointTest.kt index 87416ed4b5b..24c4d7d62c5 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathPropEndpointTest.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathPropEndpointTest.kt @@ -19,15 +19,15 @@ class PathPropEndpointTest : ApiTest() { val trackSectionRanges = listOf( TrackRange( - "TG0", + "TA0", Offset(50.meters), - Offset(1000.meters), + Offset(2000.meters), EdgeDirection.START_TO_STOP ), TrackRange( - "TG1", + "TA1", Offset(0.meters), - Offset(4000.meters), + Offset(1950.meters), EdgeDirection.START_TO_STOP ) ) @@ -42,16 +42,40 @@ class PathPropEndpointTest : ApiTest() { val rawResponse = PathPropEndpoint(infraManager).act(RqFake("POST", "v2/path_properties", requestBody)) val response = TakesUtils.readBodyResponse(rawResponse) - val parsed = pathPropResultAdapter.fromJson(response)!! + val parsed = pathPropResponseAdapter.fromJson(response)!! assertNotNull(parsed) assertEquals(parsed.slopes, RangeValues(listOf(), listOf(0.0))) assertEquals(parsed.gradients, RangeValues(listOf(), listOf(0.0))) assertEquals( parsed.electrifications, - RangeValues(listOf(4450.meters), listOf(Electrified("25000V"), Neutral(true))) + RangeValues( + listOf(1800.meters, 1950.meters), + listOf(Electrified("1500V"), Neutral(true), Electrified("25000V")) + ) ) - assertEquals(parsed.geometry.coordinates.size, 14) - assertEquals(parsed.operationalPoints.size, 0) + assertEquals(parsed.geometry.coordinates.size, 6) + val oPs = + listOf( + OperationalPointResponse( + "West_station", + OperationalPointPartResponse("TA0", 700.0, null), + OperationalPointExtensions( + OperationalPointSncfExtension(0, "BV", "BV", "0", "WS"), + OperationalPointIdentifierExtension("West_station", 2) + ), + Offset(650.meters) + ), + OperationalPointResponse( + "West_station", + OperationalPointPartResponse("TA1", 500.0, null), + OperationalPointExtensions( + OperationalPointSncfExtension(0, "BV", "BV", "0", "WS"), + OperationalPointIdentifierExtension("West_station", 2) + ), + Offset(2450.meters) + ) + ) + assertEquals(parsed.operationalPoints, oPs) } } From d0edbd4012c538bb33a964f984cad9ba7d10ecdc Mon Sep 17 00:00:00 2001 From: Eloi Charpentier Date: Thu, 25 Apr 2024 16:56:36 +0200 Subject: [PATCH 3/5] core: json: make unit adapters nullable --- .../sncf/osrd/utils/json/UnitJsonAdapters.kt | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/core/kt-osrd-utils/src/main/kotlin/fr/sncf/osrd/utils/json/UnitJsonAdapters.kt b/core/kt-osrd-utils/src/main/kotlin/fr/sncf/osrd/utils/json/UnitJsonAdapters.kt index c6d26332009..aa122e6b4c4 100644 --- a/core/kt-osrd-utils/src/main/kotlin/fr/sncf/osrd/utils/json/UnitJsonAdapters.kt +++ b/core/kt-osrd-utils/src/main/kotlin/fr/sncf/osrd/utils/json/UnitJsonAdapters.kt @@ -11,9 +11,13 @@ import java.lang.reflect.Type * Utility class, used to put Distances directly in json-adaptable classes. A value of type `long` * will be expected, representing millimeters. */ -class DistanceAdapter : JsonAdapter() { +class DistanceAdapter : JsonAdapter() { @FromJson - override fun fromJson(reader: JsonReader): Distance { + override fun fromJson(reader: JsonReader): Distance? { + if (reader.peek() == JsonReader.Token.NULL) { + reader.skipValue() + return null + } return Distance(millimeters = reader.nextLong()) } @@ -27,9 +31,13 @@ class DistanceAdapter : JsonAdapter() { * Utility class, used to put Offsets and Lengths directly in json-adaptable classes. A value of * type `long` will be expected, representing millimeters. */ -class OffsetAdapter : JsonAdapter>() { +class OffsetAdapter : JsonAdapter?>() { @FromJson - override fun fromJson(reader: JsonReader): Offset { + override fun fromJson(reader: JsonReader): Offset? { + if (reader.peek() == JsonReader.Token.NULL) { + reader.skipValue() + return null + } return Offset(Distance(millimeters = reader.nextLong())) } @@ -43,9 +51,13 @@ class OffsetAdapter : JsonAdapter>() { * Utility class, used to put Durations directly in json-adaptable classes. A value of type `long` * will be expected, representing milliseconds. */ -class DurationAdapter : JsonAdapter() { +class DurationAdapter : JsonAdapter() { @FromJson - override fun fromJson(reader: JsonReader): Duration { + override fun fromJson(reader: JsonReader): Duration? { + if (reader.peek() == JsonReader.Token.NULL) { + reader.skipValue() + return null + } return Duration(milliseconds = reader.nextLong()) } From 1fb0f47fa7bc1e8f9c0c1350b3a64167ffea1af9 Mon Sep 17 00:00:00 2001 From: Erashin Date: Fri, 26 Apr 2024 10:26:58 +0200 Subject: [PATCH 4/5] core: add blocks to v2 signal projection request --- .../fr/sncf/osrd/sim_infra/api/LoadedSignalingInfra.kt | 6 ++++++ .../sncf/osrd/standalone_sim/ScheduleMetadataExtractor.kt | 5 ++--- .../api_v2/project_signals/SignalProjectionEndpointV2.kt | 3 +++ .../api/api_v2/project_signals/SignalProjectionRequest.kt | 1 + .../fr/sncf/osrd/signal_projection/SignalProjectionV2.kt | 7 +------ 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/LoadedSignalingInfra.kt b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/LoadedSignalingInfra.kt index c6d26f006ce..0f699511404 100644 --- a/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/LoadedSignalingInfra.kt +++ b/core/kt-osrd-sim-infra/src/main/kotlin/fr/sncf/osrd/sim_infra/api/LoadedSignalingInfra.kt @@ -118,3 +118,9 @@ interface BlockInfra { fun InfraSigSystemManager.findSignalingSystemOrThrow(sigSystem: String): SignalingSystemId { return findSignalingSystem(sigSystem) ?: throw OSRDError.newSignalingError(sigSystem) } + +fun BlockInfra.convertBlockPath(blocks: List): StaticIdxList { + val res = mutableStaticIdxArrayListOf() + for (block in blocks) res.add(getBlockFromName(block)!!) + return res +} diff --git a/core/src/main/java/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractor.kt b/core/src/main/java/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractor.kt index 9383366e4ea..5c33977483d 100644 --- a/core/src/main/java/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractor.kt +++ b/core/src/main/java/fr/sncf/osrd/standalone_sim/ScheduleMetadataExtractor.kt @@ -26,7 +26,6 @@ import fr.sncf.osrd.standalone_sim.result.ResultTrain.SignalSighting import fr.sncf.osrd.train.RollingStock import fr.sncf.osrd.train.StandaloneTrainSchedule import fr.sncf.osrd.utils.CurveSimplification -import fr.sncf.osrd.utils.indexing.MutableStaticIdxArrayList import fr.sncf.osrd.utils.indexing.StaticIdxList import fr.sncf.osrd.utils.indexing.mutableStaticIdxArrayListOf import fr.sncf.osrd.utils.units.* @@ -249,7 +248,7 @@ fun routingRequirements( // compute the signaling train state for each signal data class SignalingTrainStateImpl(override val speed: Speed) : SignalingTrainState - var signalingTrainStates = mutableMapOf() + val signalingTrainStates = mutableMapOf() for (i in 0 until blockPath.size) { val block = blockPath[i] val blockOffset = blockOffsets[i] @@ -569,7 +568,7 @@ fun pathSignalsInRange( fun trainPathBlockOffset( infra: RawInfra, blockInfra: BlockInfra, - blockPath: MutableStaticIdxArrayList, + blockPath: StaticIdxList, chunkPath: ChunkPath ): Distance { val firstChunk = chunkPath.chunks[0] diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionEndpointV2.kt b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionEndpointV2.kt index 710327022c2..e9b2516df05 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionEndpointV2.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionEndpointV2.kt @@ -5,6 +5,7 @@ import fr.sncf.osrd.api.InfraManager import fr.sncf.osrd.api.pathfinding.makeChunkPath import fr.sncf.osrd.reporting.warnings.DiagnosticRecorderImpl import fr.sncf.osrd.signal_projection.projectSignals +import fr.sncf.osrd.sim_infra.api.convertBlockPath import fr.sncf.osrd.sim_infra.api.convertRoutePath import org.takes.Request import org.takes.Response @@ -29,6 +30,7 @@ class SignalProjectionEndpointV2(private val infraManager: InfraManager) : Take // Parse path val chunkPath = makeChunkPath(infra.rawInfra, request.trackSectionRanges) + val blockPath = infra.blockInfra.convertBlockPath(request.blocks) val routePath = infra.rawInfra.convertRoutePath(request.routes) val signalProjections = mutableMapOf>() @@ -37,6 +39,7 @@ class SignalProjectionEndpointV2(private val infraManager: InfraManager) : Take projectSignals( infra, chunkPath, + blockPath, routePath, trainSimulation.signalSightings, trainSimulation.zoneUpdates, diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionRequest.kt b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionRequest.kt index f2fa83db568..1dda56d8831 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionRequest.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/project_signals/SignalProjectionRequest.kt @@ -11,6 +11,7 @@ import fr.sncf.osrd.utils.json.UnitAdapterFactory import fr.sncf.osrd.utils.units.TimeDelta class SignalProjectionRequest( + val blocks: List, @Json(name = "track_section_ranges") var trackSectionRanges: List, var routes: List, @Json(name = "train_simulations") var trainSimulations: Map, diff --git a/core/src/main/kotlin/fr/sncf/osrd/signal_projection/SignalProjectionV2.kt b/core/src/main/kotlin/fr/sncf/osrd/signal_projection/SignalProjectionV2.kt index e3a0953813a..d3438ff8ecc 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/signal_projection/SignalProjectionV2.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/signal_projection/SignalProjectionV2.kt @@ -11,7 +11,6 @@ import fr.sncf.osrd.sim_infra.api.* import fr.sncf.osrd.sim_infra.impl.ChunkPath import fr.sncf.osrd.standalone_sim.* import fr.sncf.osrd.utils.indexing.StaticIdxList -import fr.sncf.osrd.utils.indexing.mutableStaticIdxArrayListOf import fr.sncf.osrd.utils.units.* import java.awt.Color @@ -20,6 +19,7 @@ data class SignalAspectChangeEventV2(val newAspect: String, val time: TimeDelta) fun projectSignals( fullInfra: FullInfra, chunkPath: ChunkPath, + blockPath: StaticIdxList, routePath: StaticIdxList, signalSightings: Collection, zoneUpdates: Collection, @@ -50,11 +50,6 @@ fun projectSignals( value("aspect", "300VL") } - // Recover blocks from the route path - val detailedBlockPath = recoverBlockPath(simulator, fullInfra, routePath) - val blockPath = mutableStaticIdxArrayListOf() - for (block in detailedBlockPath) blockPath.add(block.block) - val zoneMap = mutableMapOf() var zoneCount = 0 for (block in blockPath) { From 8f3e8ed147b717d8f4255c5b16d105133f6f8b4c Mon Sep 17 00:00:00 2001 From: Florian Amsallem Date: Fri, 26 Apr 2024 15:35:47 +0200 Subject: [PATCH 5/5] editoast: add blocks to v2 path projection request --- editoast/openapi.yaml | 11 +++++++- editoast/src/core/v2/signal_updates.rs | 2 ++ .../src/views/v2/train_schedule/projection.rs | 25 +++++++++++++------ front/src/common/api/osrdEditoastApi.ts | 2 ++ 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/editoast/openapi.yaml b/editoast/openapi.yaml index fec78f868ce..f4370672f89 100644 --- a/editoast/openapi.yaml +++ b/editoast/openapi.yaml @@ -4961,6 +4961,14 @@ components: ProjectPathInput: description: Project path input is described by a list of routes and a list of track range properties: + blocks: + description: Path description as block ids + items: + maxLength: 255 + minLength: 1 + type: string + minItems: 1 + type: array routes: description: List of route ids items: @@ -4976,8 +4984,9 @@ components: minItems: 1 type: array required: - - routes - track_section_ranges + - routes + - blocks type: object ProjectPathTrainResult: allOf: diff --git a/editoast/src/core/v2/signal_updates.rs b/editoast/src/core/v2/signal_updates.rs index 929e4d5d0d1..e0bcf9bb1e1 100644 --- a/editoast/src/core/v2/signal_updates.rs +++ b/editoast/src/core/v2/signal_updates.rs @@ -20,6 +20,8 @@ pub struct SignalUpdatesRequest<'a> { pub track_section_ranges: &'a Vec, /// Path description as route ids pub routes: &'a Vec, + /// Path description as block ids + pub blocks: &'a Vec, /// List of signal sightings and zone updates for each train pub train_simulations: HashMap>, } diff --git a/editoast/src/views/v2/train_schedule/projection.rs b/editoast/src/views/v2/train_schedule/projection.rs index b5ee9ef7d17..ef5d4baf5d2 100644 --- a/editoast/src/views/v2/train_schedule/projection.rs +++ b/editoast/src/views/v2/train_schedule/projection.rs @@ -59,12 +59,15 @@ crate::routes! { /// Project path input is described by a list of routes and a list of track range #[derive(Debug, Deserialize, Serialize, ToSchema)] struct ProjectPathInput { - /// List of route ids - #[schema(inline, min_items = 1)] - routes: Vec, /// List of track ranges #[schema(min_items = 1)] track_section_ranges: Vec, + /// List of route ids + #[schema(inline, min_items = 1)] + routes: Vec, + /// Path description as block ids + #[schema(inline, min_items = 1)] + blocks: Vec, } #[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] @@ -128,6 +131,7 @@ async fn project_path( let ProjectPathInput { track_section_ranges: path_track_ranges, routes: path_routes, + blocks: path_blocks, } = data.into_inner(); let path_projection = PathProjection::new(&path_track_ranges); let query_props = params.into_inner(); @@ -208,6 +212,7 @@ async fn project_path( &train_details, &path_track_ranges, &path_routes, + &path_blocks, ); let projection: Option = redis_conn .json_get_ex(&hash, CACHE_PROJECTION_EXPIRATION) @@ -232,6 +237,7 @@ async fn project_path( &infra, &path_track_ranges, &path_routes, + &path_blocks, &miss_cache ) ); @@ -245,6 +251,7 @@ async fn project_path( &train_details, &path_track_ranges, &path_routes, + &path_blocks, ); let cached = CachedProjectPathTrainResult { space_time_curves: space_time_curves @@ -308,15 +315,17 @@ struct TrainSimulationDetails { async fn compute_batch_signal_updates<'a>( core: Arc, infra: &Infra, - track_section_ranges: &'a Vec, - routes: &'a Vec, + path_track_ranges: &'a Vec, + path_routes: &'a Vec, + path_blocks: &'a Vec, trains_details: &'a HashMap, ) -> Result>> { let request = SignalUpdatesRequest { infra: infra.id, expected_version: infra.version.clone(), - track_section_ranges, - routes, + track_section_ranges: path_track_ranges, + routes: path_routes, + blocks: path_blocks, train_simulations: trains_details .iter() .map(|(id, details)| { @@ -489,12 +498,14 @@ fn train_projection_input_hash( project_path_input: &TrainSimulationDetails, path_projection_tracks: &[TrackRange], path_routes: &[Identifier], + path_blocks: &[Identifier], ) -> String { let osrd_version = get_app_version().unwrap_or_default(); let mut hasher = DefaultHasher::new(); project_path_input.hash(&mut hasher); path_projection_tracks.hash(&mut hasher); path_routes.hash(&mut hasher); + path_blocks.hash(&mut hasher); let hash_simulation_input = hasher.finish(); format!("projection_{osrd_version}.{infra_id}.{infra_version}.{hash_simulation_input}") } diff --git a/front/src/common/api/osrdEditoastApi.ts b/front/src/common/api/osrdEditoastApi.ts index 93c81983975..f3954ddb923 100644 --- a/front/src/common/api/osrdEditoastApi.ts +++ b/front/src/common/api/osrdEditoastApi.ts @@ -3300,6 +3300,8 @@ export type ProjectPathTrainResult = { rolling_stock_length: number; }; export type ProjectPathInput = { + /** Path description as block ids */ + blocks: string[]; /** List of route ids */ routes: string[]; /** List of track ranges */