|
| 1 | +#!/usr/bin/env python3 |
| 2 | +from dataclasses import dataclass |
| 3 | +from typing import List |
| 4 | + |
| 5 | +from railjson_generator import ( |
| 6 | + ApplicableDirection, |
| 7 | + Direction, |
| 8 | + InfraBuilder, |
| 9 | + get_output_dir, |
| 10 | +) |
| 11 | +from railjson_generator.schema.infra.electrification import Electrification |
| 12 | +from railjson_generator.schema.infra.track_section import TrackSection |
| 13 | + |
| 14 | +OUTPUT_DIR = get_output_dir() |
| 15 | + |
| 16 | +# See https://github.com/OpenRailAssociation/osrd/issues/10570 for context |
| 17 | +# The goal of this infra is to test minimal takeover setups in STDCM |
| 18 | +# |
| 19 | +# |
| 20 | +# t.takeover |
| 21 | +# _>__>______>>__ |
| 22 | +# t.1 / op.center \ t.3 |
| 23 | +# op___>___>___>___>___s________>___op___s___>___>___>___>___>___op |
| 24 | +# op.start s.1 t.2 s.2 op.end |
| 25 | +# |
| 26 | +# op.* = operational point id |
| 27 | +# t.* = track id |
| 28 | +# s.* = switch id |
| 29 | +# > = signal |
| 30 | +# |
| 31 | +# Signals are placed roughly every 2km on t.1 and t.3. |
| 32 | +# Signal placements are described more accurately on t.takeover |
| 33 | +# in the signal array as comments. |
| 34 | + |
| 35 | + |
| 36 | +# GENERATE INFRA |
| 37 | +builder = InfraBuilder() |
| 38 | + |
| 39 | +# Create operational points |
| 40 | +op_start = builder.add_operational_point("op.start", trigram="STA", uic=0, weight=1) |
| 41 | +op_center = builder.add_operational_point("op.center", trigram="CEN", uic=1, weight=0.5) |
| 42 | +op_takeover = builder.add_operational_point("op.takeover", trigram="TAK", uic=3, weight=0.5) |
| 43 | +op_end = builder.add_operational_point("op.end", trigram="END", uic=2, weight=1) |
| 44 | + |
| 45 | +# Create track sections |
| 46 | + |
| 47 | +t_1 = builder.add_track_section(length=20_000, label="t_1") |
| 48 | +t_2 = builder.add_track_section(length=2_000, label="t_2") |
| 49 | +t_takeover = builder.add_track_section(length=2_100, label="t_takeover") |
| 50 | +t_3 = builder.add_track_section(length=20_000, label="t_3") |
| 51 | + |
| 52 | +# Add objects on tracks |
| 53 | + |
| 54 | +op_start.add_part(t_1, 0) |
| 55 | +op_end.add_part(t_3, 20_000) |
| 56 | +op_center.add_part(t_2, 1_500) |
| 57 | +op_takeover.add_part(t_takeover, 1_500) |
| 58 | +t_1.add_buffer_stop(label="bf.1", position=0) |
| 59 | +t_3.add_buffer_stop(label="bf.3", position=20_000) |
| 60 | + |
| 61 | +builder.infra.electrifications.append(Electrification("electrification_1500", "1500V", [t_1, t_2, t_3, t_takeover])) |
| 62 | + |
| 63 | +speed_limit = builder.add_speed_section(30 / 3.6) |
| 64 | +speed_limit.add_track_range(t_takeover, 0, t_takeover.length, ApplicableDirection.BOTH) |
| 65 | + |
| 66 | +# Signals |
| 67 | + |
| 68 | +default_sight_distance = 400 |
| 69 | + |
| 70 | + |
| 71 | +@dataclass |
| 72 | +class Signal: |
| 73 | + name: str |
| 74 | + track: TrackSection |
| 75 | + position: int |
| 76 | + is_route_delimiter: bool |
| 77 | + sight_distance: int = default_sight_distance |
| 78 | + |
| 79 | + |
| 80 | +raw_signals: List[Signal] = [ |
| 81 | + Signal("s.2.1500", t_2, 1_500, False), |
| 82 | + Signal("s.takeover.start.1", t_takeover, 1, True), |
| 83 | + # start.1 -> start.2 : needs to be at least as long as the new train |
| 84 | + # (we still have constraints from the main path while the tail hasn't left) |
| 85 | + Signal("s.takeover.start.2", t_takeover, 500, True), |
| 86 | + # start.2 -> end.1 : where the engineering allowance happens. |
| 87 | + # Needs to be long enough to almost stop and speed up from/to the maximum |
| 88 | + # speed set on the takeover track (here 30km/h) |
| 89 | + Signal("s.takeover.end.1", t_takeover, 2_000 - default_sight_distance - 2, True, 0), |
| 90 | + # end.1 -> end.2 : stops the signal propagation to the main track |
| 91 | + Signal("s.takeover.end.2", t_takeover, 2_000 - default_sight_distance - 1, True, 0), |
| 92 | + # end.2 -> end of the takeover track : needs to be as long as the default |
| 93 | + # signal sight distance. We must not see the signals on the main track while |
| 94 | + # in the block used for the engineering allowance. |
| 95 | +] |
| 96 | +for offset in range(0, 20_000, 2_000): |
| 97 | + raw_signals.append(Signal(f"s.1.{offset}", t_1, offset, False)) |
| 98 | + raw_signals.append(Signal(f"s.3.{offset}", t_3, offset, False)) |
| 99 | + |
| 100 | +signals = [] |
| 101 | +for raw_signal in raw_signals: |
| 102 | + detector = raw_signal.track.add_detector(label=f"det.{raw_signal.name[2:]}", position=raw_signal.position) |
| 103 | + signal = raw_signal.track.add_signal( |
| 104 | + label=raw_signal.name, |
| 105 | + position=raw_signal.position, |
| 106 | + direction=Direction.START_TO_STOP, |
| 107 | + is_route_delimiter=raw_signal.is_route_delimiter, |
| 108 | + sight_distance=raw_signal.sight_distance, |
| 109 | + ) |
| 110 | + signal.add_logical_signal("BAL", settings={"Nf": "true" if raw_signal.is_route_delimiter else "false"}) |
| 111 | + signals.append(signal) |
| 112 | + |
| 113 | +# Add links |
| 114 | + |
| 115 | +s_1 = builder.add_point_switch(t_1.end(), t_2.begin(), t_takeover.begin(), label="s.1") |
| 116 | +s_2 = builder.add_point_switch(t_3.begin(), t_2.end(), t_takeover.end(), label="s.2") |
| 117 | + |
| 118 | +# Set coordinates |
| 119 | + |
| 120 | +lat_track_1 = 50 |
| 121 | +lat_track_2 = 49.999 |
| 122 | + |
| 123 | +t_1.begin().set_coords(-0.12, lat_track_1) |
| 124 | +t_1.end().set_coords(-0.1, lat_track_1) |
| 125 | +t_2.begin().set_coords(-0.1, lat_track_1) |
| 126 | +t_2.end().set_coords(-0.09, lat_track_1) |
| 127 | +t_3.begin().set_coords(-0.09, lat_track_1) |
| 128 | +t_3.end().set_coords(-0.07, lat_track_1) |
| 129 | + |
| 130 | +t_takeover.begin().set_coords(-0.1, lat_track_2) |
| 131 | +t_takeover.end().set_coords(-0.09, lat_track_2) |
| 132 | + |
| 133 | +# Build infra |
| 134 | +infra = builder.build() |
| 135 | + |
| 136 | +# Save railjson |
| 137 | +infra.save(OUTPUT_DIR / "infra.json") |
0 commit comments