Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: add signal projection endpoint for v2 #7304

Merged
merged 5 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,10 @@ fun RoutingInfra.getRouteExit(route: RouteId): DirDetectorId {
return getZonePathExit(getRoutePath(route).last())
}

fun RoutingInfra.convertRoutePath(routes: List<String>): StaticIdxList<Route> {
val res = mutableStaticIdxArrayListOf<Route>()
for (route in routes) res.add(getRouteFromName(route))
return res
}

typealias InterlockingInfra = RoutingInfra
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,9 @@ interface BlockInfra {
fun InfraSigSystemManager.findSignalingSystemOrThrow(sigSystem: String): SignalingSystemId {
return findSignalingSystem(sigSystem) ?: throw OSRDError.newSignalingError(sigSystem)
}

fun BlockInfra.convertBlockPath(blocks: List<String>): StaticIdxList<Block> {
val res = mutableStaticIdxArrayListOf<Block>()
for (block in blocks) res.add(getBlockFromName(block)!!)
return res
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Distance>() {
class DistanceAdapter : JsonAdapter<Distance?>() {
@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())
}

Expand All @@ -27,9 +31,13 @@ class DistanceAdapter : JsonAdapter<Distance>() {
* 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<T> : JsonAdapter<Offset<T>>() {
class OffsetAdapter<T> : JsonAdapter<Offset<T>?>() {
@FromJson
override fun fromJson(reader: JsonReader): Offset<T> {
override fun fromJson(reader: JsonReader): Offset<T>? {
if (reader.peek() == JsonReader.Token.NULL) {
reader.skipValue()
return null
}
return Offset(Distance(millimeters = reader.nextLong()))
}

Expand All @@ -43,9 +51,13 @@ class OffsetAdapter<T> : JsonAdapter<Offset<T>>() {
* Utility class, used to put Durations directly in json-adaptable classes. A value of type `long`
* will be expected, representing milliseconds.
*/
class DurationAdapter : JsonAdapter<Duration>() {
class DurationAdapter : JsonAdapter<Duration?>() {
@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())
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;

Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/fr/sncf/osrd/cli/ApiServerCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand Down Expand Up @@ -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<LogicalSignalId, SignalingTrainState>()
val signalingTrainStates = mutableMapOf<LogicalSignalId, SignalingTrainState>()
for (i in 0 until blockPath.size) {
val block = blockPath[i]
val blockOffset = blockOffsets[i]
Expand Down Expand Up @@ -546,9 +545,19 @@ fun pathSignalsInEnvelope(
blockPath: StaticIdxList<Block>,
blockInfra: BlockInfra,
envelope: EnvelopeTimeInterpolate,
): List<PathSignal> {
return pathSignalsInRange(startOffset, blockPath, blockInfra, 0.meters, envelope.endPos.meters)
}

fun pathSignalsInRange(
startOffset: Distance,
blockPath: StaticIdxList<Block>,
blockInfra: BlockInfra,
rangeStart: Distance,
rangeEnd: Distance,
): List<PathSignal> {
return pathSignals(startOffset, blockPath, blockInfra).filter { signal ->
signal.pathOffset >= 0.meters && signal.pathOffset <= envelope.endPos.meters
signal.pathOffset in rangeStart..rangeEnd
}
}

Expand All @@ -559,7 +568,7 @@ fun pathSignalsInEnvelope(
fun trainPathBlockOffset(
infra: RawInfra,
blockInfra: BlockInfra,
blockPath: MutableStaticIdxArrayList<Block>,
blockPath: StaticIdxList<Block>,
chunkPath: ChunkPath
): Distance {
val firstChunk = chunkPath.chunks[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -22,7 +21,7 @@ data class SignalAspectChangeEvent(val newAspect: String, val time: Long)
fun project(
fullInfra: FullInfra,
chunkPath: ChunkPath,
routePathIds: List<Int>,
routePath: StaticIdxList<Route>,
signalSightings: List<SignalSighting>,
zoneUpdates: List<ZoneUpdate>
): SignalProjectionResult {
Expand Down Expand Up @@ -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<Block>()
for (block in detailedBlockPath) blockPath.add(block.block)
Expand Down
16 changes: 16 additions & 0 deletions core/src/main/kotlin/fr/sncf/osrd/api/api_v2/CommonSchemas.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -14,3 +16,17 @@ data class TrackRange(
)

class RangeValues<T>(val boundaries: List<Distance> = listOf(), val values: List<T> = listOf())

class ZoneUpdate(
val zone: String,
val time: TimeDelta,
val position: Offset<Path>,
@Json(name = "is_entry") val isEntry: Boolean,
)

class SignalSighting(
val signal: String,
val time: TimeDelta,
val position: Offset<Path>,
val state: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Double>,
val gradients: RangeValues<Double>,
val electrifications: RangeValues<Electrification>,
val geometry: RJSLineString,
@Json(name = "operational_points") val operationalPoints: List<OperationalPointResult>
@Json(name = "operational_points") val operationalPoints: List<OperationalPointResponse>
)

data class RangeValues<T>(val boundaries: List<Distance>, val values: List<T>)
Expand All @@ -29,48 +29,48 @@ 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<Path>
)

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,
@Json(name = "ch_long_label") val chLongLabel: String,
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<Electrification> =
PolymorphicJsonAdapterFactory.of(Electrification::class.java, "type")
.withSubtype(Electrified::class.java, "electrification")
.withSubtype(Neutral::class.java, "neutral_section")
.withSubtype(NonElectrified::class.java, "non_electrified")

val pathPropResultAdapter: JsonAdapter<PathPropResult> =
val pathPropResponseAdapter: JsonAdapter<PathPropResponse> =
Moshi.Builder()
.add(polymorphicAdapter)
.addLast(UnitAdapterFactory())
.addLast(KotlinJsonAdapterFactory())
.build()
.adapter(PathPropResult::class.java)
.adapter(PathPropResponse::class.java)
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -54,8 +54,8 @@ private fun makeGeographic(path: PathProperties): RJSLineString {
private fun makeOperationalPoints(
path: PathProperties,
rawInfra: RawSignalingInfra
): List<OperationalPointResult> {
val res = mutableListOf<OperationalPointResult>()
): List<OperationalPointResponse> {
val res = mutableListOf<OperationalPointResponse>()
for ((opPartId, offset) in path.getOperationalPointParts()) {
val operationalPointId = rawInfra.getOperationalPointPartOpId(opPartId)
val trackSection =
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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.convertBlockPath
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 blockPath = infra.blockInfra.convertBlockPath(request.blocks)
val routePath = infra.rawInfra.convertRoutePath(request.routes)

val signalProjections = mutableMapOf<Long, List<SignalUpdate>>()
for ((id, trainSimulation) in request.trainSimulations) {
val signalProjection =
projectSignals(
infra,
chunkPath,
blockPath,
routePath,
trainSimulation.signalSightings,
trainSimulation.zoneUpdates,
trainSimulation.simulationEndTime
)
signalProjections[id] = signalProjection
}

RsJson(
RsWithBody(
signalProjectionResponseAdapter.toJson(
SignalProjectionResponse(signalProjections.toMap())
)
)
)
} catch (ex: Throwable) {
ExceptionHandler.handle(ex)
}
}
}
Loading
Loading