Skip to content

Commit b24641f

Browse files
Merge branch 'operations' into compare-operation
2 parents dea15ce + 64fc72e commit b24641f

File tree

4 files changed

+74
-16
lines changed

4 files changed

+74
-16
lines changed

test/node_test.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from itertools import product
22

3+
from pytest import raises
4+
5+
from yaramo.geo_node import Wgs84GeoNode
36
from yaramo.model import Node
47

58
from .helper import create_edge, create_node
@@ -134,3 +137,17 @@ def test_anschluss():
134137
assert switch.connected_on_right == right, (
135138
"right node " f"{coords_str(switch.connected_on_right)} incorrect"
136139
)
140+
141+
142+
def test_implausible_anschluss():
143+
"""Assert that detection of "Anschluss" (head, left, right) raises
144+
an exception on really implausible geographies."""
145+
head = create_node(0, 0) # layout:
146+
point = create_node(2, 2) # l
147+
left = create_node(1, 3) # s r
148+
right = create_node(3, 2) # h
149+
150+
point.connected_nodes.extend((head, left, right))
151+
152+
with raises(Exception) as exception:
153+
point.calc_anschluss_of_all_nodes()

yaramo/geo_node.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,20 @@ def __init__(self, x, y, **kwargs):
1919

2020
@abstractmethod
2121
def get_distance_to_other_geo_node(self, geo_node_b: "GeoNode"):
22+
"""Returns to distance to the given other GeoNode."""
2223
pass
2324

24-
def to_serializable(self):
25-
return self.__dict__, {}
26-
2725
@abstractmethod
28-
def to_wgs84(self):
26+
def to_wgs84(self) -> "Wgs84GeoNode":
2927
pass
3028

3129
@abstractmethod
32-
def to_dbref(self):
30+
def to_dbref(self) -> "DbrefGeoNode":
3331
pass
3432

33+
def to_serializable(self):
34+
return self.__dict__, {}
35+
3536

3637
class Wgs84GeoNode(GeoNode):
3738
def get_distance_to_other_geo_node(self, geo_node_b: "Wgs84GeoNode"):

yaramo/node.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -155,37 +155,42 @@ def get_anschluss_for_edge(self, edge: "Edge") -> EdgeConnectionDirection:
155155
def calc_anschluss_of_all_edges(self):
156156
"""Calculates and sets the 'Anschluss' or connection side of
157157
the connected_nodes based on their geo location."""
158-
159158
if len(self.connected_edges) == 1:
160159
self.connected_edge_on_head = self.connected_edges[0]
161160
return
161+
almost_zero = sys.float_info.epsilon
162162

163-
def get_arc_between_geo_nodes(geo_node_a: "GeoNode", geo_node_b: "GeoNode") -> float:
163+
def get_rad_between_nodes(geo_node_a: GeoNode, geo_node_b: GeoNode) -> float:
164164
"""
165165
Returns the angle of an (maybe imaginary) line between
166-
:param:`node_a` and :param:`node_b`.
166+
:param:`geo_node_a` and :param:`geo_node_b`.
167167
"""
168168
return atan2(geo_node_b.y - geo_node_a.y, geo_node_b.x - geo_node_a.x)
169169

170170
# Determine which node is head, left, and right by trying the
171171
# permutations and checking for plausibility.
172172
for head, left, right in permutations(self.connected_edges):
173173

174-
head_angle_abs = get_arc_between_geo_nodes(head.get_next_geo_node(self), self.geo_node)
175-
left_angle_abs = get_arc_between_geo_nodes(self.geo_node, left.get_next_geo_node(self))
174+
head_angle_abs = get_rad_between_nodes(head.get_next_geo_node(self), self.geo_node)
175+
left_angle_abs = get_rad_between_nodes(self.geo_node, left.get_next_geo_node(self))
176176
left_angle_rel = left_angle_abs - head_angle_abs
177-
if cos(left_angle_rel) <= sys.float_info.epsilon:
177+
if cos(left_angle_rel) <= almost_zero:
178178
# left turn more than (or almost) 90°
179179
continue
180180

181-
right_angle_abs = get_arc_between_geo_nodes(
182-
self.geo_node, right.get_next_geo_node(self)
183-
)
181+
right_angle_abs = get_rad_between_nodes(self.geo_node, right.get_next_geo_node(self))
184182
right_angle_rel = right_angle_abs - head_angle_abs
185-
if cos(right_angle_rel) <= sys.float_info.epsilon:
186-
# left turn more than (or almost) 90°
183+
if cos(right_angle_rel) <= almost_zero:
184+
# right turn more than (or almost) 90°
187185
continue
188186

187+
if cos(left_angle_abs - right_angle_abs) <= almost_zero:
188+
# turn from left to right less than (or almost) 90°
189+
raise Exception(
190+
f"Node {self} has three connected nodes but "
191+
"geometrically, it does not look like a switch."
192+
)
193+
189194
if sin(left_angle_rel) < sin(right_angle_rel):
190195
# Left and right mixed up. Although the permutations do
191196
# contain the correct combination, fixing this right

yaramo/topology.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
from collections import defaultdict
12
from datetime import datetime
3+
from enum import Enum
24
from typing import List
35

46
import networkx as nx
@@ -13,10 +15,28 @@
1315
from yaramo.vacancy_section import VacancySection
1416

1517

18+
class PlanningState(Enum):
19+
erstellt = 1
20+
qualitaetsgeprueft = 2
21+
plangeprueft = 3
22+
freigegeben = 4
23+
genehmigt = 5
24+
abgenommen = 6
25+
uebernommen = 7
26+
gleichgestellt = 8
27+
sonstige = 9
28+
29+
def __str__(self):
30+
return self.name
31+
32+
1633
class Topology(BaseElement):
1734
"""The Topology is a collection of all track elements comprising that topology.
1835
1936
Elements like Signals, Nodes, Edges, Routes and Vacancy Sections can be accessed by their uuid in their respective dictionary.
37+
38+
The status_information provides additional information for each PlanningState, with the keys of the inner dict
39+
being based on the PlanPro Akteur_Zuordnung class.
2040
"""
2141

2242
def __init__(self, **kwargs):
@@ -26,6 +46,8 @@ def __init__(self, **kwargs):
2646
self.signals: dict[str, Signal] = {}
2747
self.routes: dict[str, Route] = {}
2848
self.vacancy_sections: dict[str, VacancySection] = {}
49+
self.current_status: PlanningState = PlanningState.erstellt
50+
self.status_information: dict[PlanningState, dict[str, str]] = defaultdict(dict)
2951

3052
self.created_at: datetime = datetime.now()
3153
self.created_with: str = "unknown"
@@ -82,6 +104,15 @@ def update_edge_lengths(self):
82104
for edge in self.edges.values():
83105
edge.update_length()
84106

107+
def get_route_by_signal_names(self, start_signal_name, end_signal_name):
108+
for route_uuid in self.routes:
109+
route = self.routes[route_uuid]
110+
if (
111+
route.start_signal.name == start_signal_name
112+
and route.end_signal.name == end_signal_name
113+
):
114+
return route
115+
85116
def to_serializable(self):
86117
"""See the description in the BaseElement class.
87118
@@ -110,12 +141,16 @@ def to_serializable(self):
110141
"routes": routes,
111142
"objects": objects,
112143
"vacany_sections": vacancy_sections,
144+
"current_status": self.current_status.value,
145+
"status_information": self.status_information,
113146
}, {}
114147

115148
@classmethod
116149
def from_json(cls, json_str: str):
117150
obj = json.loads(json_str)
118151
topology = cls()
152+
topology.current_status = PlanningState(obj.get("current_status", PlanningState.erstellt))
153+
topology.status_information = obj.get("status_information", defaultdict(dict))
119154
for node in obj["nodes"]:
120155
node_obj = Node(**node)
121156
topology.add_node(node_obj)

0 commit comments

Comments
 (0)