Skip to content

Commit 2950910

Browse files
Merge branch 'main' into refactor/replace-connected-nodes-edges
2 parents 523061e + 3994b67 commit 2950910

File tree

5 files changed

+80
-114
lines changed

5 files changed

+80
-114
lines changed

.github/workflows/python-app.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
poetry run isort -c .
3535
- name: Lint with pylint
3636
run: |
37-
poetry run pylint orm_importer
37+
poetry run pylint orm_importer --fail-under 8
3838
3939
test:
4040

orm_importer/importer.py

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from collections import defaultdict
2-
from typing import List
2+
from typing import Any, List, Optional
33

44
import networkx as nx
55
import overpy
@@ -16,11 +16,11 @@
1616
get_additional_signals,
1717
get_opposite_edge_pairs,
1818
get_signal_classification_number,
19+
get_signal_direction,
1920
get_signal_function,
2021
get_signal_kind,
2122
get_signal_name,
2223
get_signal_states,
23-
getSignalDirection,
2424
is_end_node,
2525
is_same_edge,
2626
is_signal,
@@ -35,34 +35,41 @@ def __init__(self):
3535
self.top_nodes: list[OverpyNode] = []
3636
self.node_data: dict[str, OverpyNode] = {}
3737
self.ways: dict[str, List[overpy.Way]] = defaultdict(list)
38-
self.paths: dict[str, List[List]] = defaultdict(list)
38+
self.paths: dict[tuple[Optional[Any], Optional[Any]], List[List]] = defaultdict(list)
3939
self.api = overpy.Overpass(url="https://osm.hpi.de/overpass/api/interpreter")
4040
self.topology = Topology()
4141

42-
def _get_track_objects(self, polygon: str):
43-
query = f'(way["railway"="rail"](poly: "{polygon}");node(w)(poly: "{polygon}"););out body;'
42+
def _get_track_objects(self, polygon: str, railway_option_types: list[str]):
43+
query_parts = ""
44+
for _type in railway_option_types:
45+
query_parts = (
46+
query_parts
47+
+ f'way["railway"="{_type}"](poly: "{polygon}");node(w)(poly: "{polygon}");'
48+
)
49+
query = f"({query_parts});out body;"
50+
print(query)
4451
return self._query_api(query)
4552

4653
def _query_api(self, query):
4754
result = self.api.query(query)
4855
return result
4956

5057
def _build_graph(self, track_objects):
51-
G = nx.Graph()
58+
graph = nx.Graph()
5259
for way in track_objects.ways:
5360
previous_node = None
5461
for idx, node_id in enumerate(way._node_ids):
5562
try:
5663
node = track_objects.get_node(node_id)
5764
self.node_data[node_id] = node
5865
self.ways[str(node_id)].append(way)
59-
G.add_node(node.id)
66+
graph.add_node(node.id)
6067
if previous_node:
61-
G.add_edge(previous_node.id, node.id)
68+
graph.add_edge(previous_node.id, node.id)
6269
previous_node = node
6370
except overpy.exception.DataIncomplete:
6471
continue
65-
return G
72+
return graph
6673

6774
def _get_next_top_node(self, node, edge: "tuple[str, str]", path):
6875
node_to_id = edge[1]
@@ -111,7 +118,7 @@ def _add_signals(self, path, edge: model.Edge, node_before, node_after):
111118
signal_geo_point
112119
),
113120
side_distance=dist_edge(node_before, node_after, node),
114-
direction=getSignalDirection(
121+
direction=get_signal_direction(
115122
edge, self.ways, path, node.tags["railway:signal:direction"]
116123
),
117124
function=get_signal_function(node),
@@ -143,15 +150,18 @@ def _should_add_edge(self, node_a: model.Node, node_b: model.Node, path: list[in
143150
present_paths = self.paths[(node_a, node_b)] + self.paths[(node_b, node_a)]
144151
return path not in present_paths and reversed_path not in present_paths
145152

146-
def run(self, polygon):
147-
track_objects = self._get_track_objects(polygon)
153+
def run(self, polygon, railway_option_types: list[str] = None):
154+
if railway_option_types is None:
155+
railway_option_types = ["rail"]
156+
track_objects = self._get_track_objects(polygon, railway_option_types)
148157
self.graph = self._build_graph(track_objects)
149158

150-
# ToDo: Check whether all edges really link to each other in ORM or if there might be edges missing for nodes that are just a few cm from each other
159+
# ToDo: Check whether all edges really link to each other in ORM or if there might be
160+
# edges missing for nodes that are just a few cm from each other
151161
# Only nodes with max 1 edge or that are a switch can be top nodes
152162
for node_id in self.graph.nodes:
153163
node = self.node_data[node_id]
154-
if is_end_node(node, self.graph) or is_switch(node):
164+
if is_end_node(node, self.graph) or is_switch(node, self.graph):
155165
self.top_nodes.append(node)
156166

157167
for node in self.top_nodes:
@@ -201,7 +211,8 @@ def run(self, polygon):
201211
e for e in self.topology.edges.values() if e.node_a == node or e.node_b == node
202212
]
203213

204-
# merge edges, this means removing the switch and allowing only one path for each origin
214+
# merge edges, this means removing the switch and
215+
# allowing only one path for each origin
205216
edge_pair_1, edge_pair_2 = get_opposite_edge_pairs(connected_edges, node)
206217
new_edge_1 = merge_edges(*edge_pair_1, node)
207218
new_edge_2 = merge_edges(*edge_pair_2, node)
@@ -226,8 +237,10 @@ def run(self, polygon):
226237
except DataIncomplete:
227238
nodes = way.get_nodes(resolve_missing=True)
228239
for candidate in nodes:
229-
# we are only interested in nodes outside the bounding box as every node
230-
# that has been previously known was already visited as part of the graph
240+
# we are only interested in nodes outside the
241+
# bounding box as every node that has been
242+
# previously known was already visited as
243+
# part of the graph
231244
if (
232245
candidate.id != int(node.name)
233246
and candidate.id not in self.node_data.keys()
@@ -247,9 +260,9 @@ def run(self, polygon):
247260
break
248261
if not substitute_found:
249262
# if no substitute was found, the third node seems to be inside the bounding box
250-
# this can happen when a node is connected to the same node twice (e.g. station on
251-
# lines with only one track). WARNING: this produced weird results in the past.
252-
# It should be okay to do it after the check above.
263+
# this can happen when a node is connected to the same node twice (e.g.
264+
# station on lines with only one track). WARNING: this produced weird
265+
# results in the past. It should be okay to do it after the check above.
253266
connected_edges = [
254267
e
255268
for e in self.topology.edges.values()

orm_importer/utils.py

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
def dist_edge(node_before, node_after, signal):
2222
# Calculate distance from point(signal) to edge between node before and after
2323
# TODO: Validate that this is really correct!
24-
return 3.95
2524
p1 = np.array((node_before.lat, node_before.lon))
2625
p2 = np.array((node_after.lat, node_after.lon))
2726
p3 = np.array((signal.lat, signal.lon))
@@ -41,12 +40,13 @@ def is_end_node(node, graph):
4140

4241
def is_signal(node):
4342
# we cannot use railway=signal as a condition, as buffer stops violate this assumption.
44-
# Instead, we check for the signal direction as we cannot create a signal without a direction anyway
43+
# Instead, we check for the signal direction as we cannot create a
44+
# signal without a direction anyway
4545
return "railway:signal:direction" in node.tags.keys()
4646

4747

48-
def is_switch(node):
49-
return is_x(node, "switch")
48+
def is_switch(node, graph):
49+
return is_x(node, "switch") and graph.degree(node.id) == 3
5050

5151

5252
def is_x(node, x: str):
@@ -61,7 +61,7 @@ def is_same_edge(e1: tuple, e2: tuple):
6161
return False
6262

6363

64-
def getSignalDirection(edge: Edge, ways: dict[str, List[Way]], path, signal_direction_tag: str):
64+
def get_signal_direction(edge: Edge, ways: dict[str, List[Way]], path, signal_direction_tag: str):
6565
edge_is_forward = None
6666
for way in ways[edge.node_a.name]:
6767
node_a = int(edge.node_a.name)
@@ -90,12 +90,12 @@ def getSignalDirection(edge: Edge, ways: dict[str, List[Way]], path, signal_dire
9090
not edge_is_forward and signal_direction_tag == "backward"
9191
):
9292
return "in"
93-
else:
94-
return "gegen"
93+
return "gegen"
9594

9695

9796
def get_signal_states(signal_tags: dict):
98-
# Sh0 is tagged as Hp0 in OSM since a few years, but not all tags have been replaced so we convert them
97+
# Sh0 is tagged as Hp0 in OSM since a few years, but not all tags
98+
# have been replaced so we convert them
9999
raw_states = []
100100
raw_states += signal_tags.get("railway:signal:main:states", "").split(";")
101101
raw_states += signal_tags.get("railway:signal:combined:states", "").split(";")
@@ -161,12 +161,11 @@ def get_signal_function(signal: Node) -> str:
161161
tag = next(t for t in signal.tags.keys() if t.endswith(":function"))
162162
if signal.tags[tag] == "entry":
163163
return "Einfahr_Signal"
164-
elif signal.tags[tag] == "exit":
164+
if signal.tags[tag] == "exit":
165165
return "Ausfahr_Signal"
166-
elif signal.tags[tag] == "block":
166+
if signal.tags[tag] == "block":
167167
return "Block_Signal"
168-
else:
169-
return "andere"
168+
return "andere"
170169
except StopIteration:
171170
return "andere"
172171

@@ -177,36 +176,33 @@ def get_signal_kind(signal: Node) -> str:
177176
# ORM Reference: https://wiki.openstreetmap.org/wiki/OpenRailwayMap/Tagging/Signal
178177
if "railway:signal:main" in signal.tags.keys():
179178
return "Hauptsignal"
180-
elif "railway:signal:distant" in signal.tags.keys():
179+
if "railway:signal:distant" in signal.tags.keys():
181180
return "Vorsignal"
182-
elif "railway:signal:combined" in signal.tags.keys():
181+
if "railway:signal:combined" in signal.tags.keys():
183182
return "Mehrabschnittssignal"
184-
elif "railway:signal:shunting" in signal.tags.keys() or (
183+
if "railway:signal:shunting" in signal.tags.keys() or (
185184
"railway:signal:minor" in signal.tags.keys()
186185
and (
187186
signal.tags["railway:signal:minor"] == "DE-ESO:sh0"
188187
or signal.tags["railway:signal:minor"] == "DE-ESO:sh2"
189188
)
190189
):
191190
return "Sperrsignal"
192-
elif (
193-
"railway:signal:main" in signal.tags.keys() and "railway:signal:minor" in signal.tags.keys()
194-
):
191+
if "railway:signal:main" in signal.tags.keys() and "railway:signal:minor" in signal.tags.keys():
195192
return "Hauptsperrsignal"
196193
# Names in comment are not yet supported by PlanPro generator
197-
elif "railway:signal:main_repeated" in signal.tags.keys():
194+
if "railway:signal:main_repeated" in signal.tags.keys():
198195
return "andere" # 'Vorsignalwiederholer'
199-
elif "railway:signal:minor" in signal.tags.keys():
196+
if "railway:signal:minor" in signal.tags.keys():
200197
return "andere" # 'Zugdeckungssignal'
201-
elif "railway:signal:crossing" in signal.tags.keys():
198+
if "railway:signal:crossing" in signal.tags.keys():
202199
return "andere" # 'Überwachungssignal'
203-
elif (
200+
if (
204201
"railway:signal:combined" in signal.tags.keys()
205202
and "railway:signal:minor" in signal.tags.keys()
206203
):
207204
return "andere" # 'Mehrabschnittssperrsignal'
208-
else:
209-
return "andere"
205+
return "andere"
210206

211207

212208
def get_signal_name(node: Node):

0 commit comments

Comments
 (0)