Skip to content

Commit 1ab283f

Browse files
Merge pull request #47 from simulate-digital-rail/fix-head-left-right-detection-for-switches
rework detection of head, left, right node connected to switches
2 parents 886b7b2 + 0e6f08f commit 1ab283f

File tree

1 file changed

+43
-65
lines changed

1 file changed

+43
-65
lines changed

yaramo/node.py

Lines changed: 43 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import math
1+
import sys
22
from enum import Enum
3+
from itertools import permutations
4+
from math import atan2, cos, sin
35

46
from yaramo.base_element import BaseElement
57
from yaramo.geo_node import GeoNode
@@ -102,70 +104,46 @@ def get_anschluss_of_other(self, other: "Node") -> NodeConnectionDirection:
102104
return None
103105

104106
def calc_anschluss_of_all_nodes(self):
105-
"""Calculates and sets the 'Anschluss' or connection side of the connected_nodes based on their geo-location."""
106-
107-
def get_arc_between_nodes(_node_a: "Node", _node_b: "Node"):
108-
_a = _node_a.geo_node.get_distance_to_other_geo_node(self.geo_node)
109-
_b = self.geo_node.get_distance_to_other_geo_node(_node_b.geo_node)
110-
_c = _node_a.geo_node.get_distance_to_other_geo_node(_node_b.geo_node)
111-
112-
return math.degrees(math.acos((_a * _a + _b * _b - _c * _c) / (2.0 * _a * _b)))
113-
114-
def is_above_line_between_points(
115-
head_point: GeoPoint, branching_point: GeoPoint, comparison_point: GeoPoint
116-
):
117-
return (
118-
(branching_point.x - head_point.x) * (comparison_point.y - head_point.y)
119-
- (branching_point.y - head_point.y) * (comparison_point.x - head_point.x)
120-
) > 0
121-
122-
current_max_arc = 361
123-
other_a: "Node" = None
124-
other_b: "Node" = None
125-
for i in range(len(self.connected_nodes)):
126-
for j in range(len(self.connected_nodes)):
127-
if i != j:
128-
cur_arc = get_arc_between_nodes(
129-
self.connected_nodes[i], self.connected_nodes[j]
130-
)
131-
if cur_arc < current_max_arc:
132-
missing_index = sum(range(len(self.connected_nodes))) - (i + j)
133-
self.connected_on_head = self.connected_nodes[missing_index]
134-
other_a = self.connected_nodes[i]
135-
other_b = self.connected_nodes[j]
136-
current_max_arc = cur_arc
137-
138-
# Check on which side of the line between the head connection and this node the other nodes are
139-
side_a = is_above_line_between_points(
140-
self.connected_on_head.geo_node.geo_point,
141-
self.geo_node.geo_point,
142-
other_a.geo_node.geo_point,
143-
)
144-
side_b = is_above_line_between_points(
145-
self.connected_on_head.geo_node.geo_point,
146-
self.geo_node.geo_point,
147-
other_b.geo_node.geo_point,
148-
)
149-
150-
# If they're on two separate sides we know which is left and right
151-
if side_a != side_b:
152-
if side_a:
153-
self.connected_on_left, self.connected_on_right = other_a, other_b
154-
else:
155-
self.connected_on_right, self.connected_on_left = other_a, other_b
156-
# If they're both above or below that line, we make the node that branches further away the left or right node,
157-
# depending on the side they're on (left if both above)
158-
else:
159-
arc_a = get_arc_between_nodes(self.connected_on_head, other_a)
160-
arc_b = get_arc_between_nodes(self.connected_on_head, other_b)
161-
if arc_a > arc_b:
162-
self.connected_on_right, self.connected_on_left = (
163-
(other_a, other_b) if side_a else (other_b, other_a)
164-
)
165-
else:
166-
self.connected_on_left, self.connected_on_right = (
167-
(other_a, other_b) if side_a else (other_b, other_a)
168-
)
107+
"""Calculates and sets the 'Anschluss' or connection side of
108+
the connected_nodes based on their geo location."""
109+
110+
def get_rad_between_nodes(node_a: GeoPoint, node_b: GeoPoint) -> float:
111+
"""
112+
Returns the angle of an (maybe imaginary) line between
113+
:param:`node_a` and :param:`node_b`.
114+
"""
115+
point_a = node_a.geo_node.geo_point
116+
point_b = node_b.geo_node.geo_point
117+
return atan2(point_b.y - point_a.y, point_b.x - point_a.x)
118+
119+
# Determine which node is head, left, and right by trying the
120+
# permutations and checking for plausibility.
121+
for head, left, right in permutations(self.connected_nodes):
122+
123+
head_angle_abs = get_rad_between_nodes(head, self)
124+
125+
left_angle_abs = get_rad_between_nodes(self, left)
126+
left_angle_rel = left_angle_abs - head_angle_abs
127+
if cos(left_angle_rel) <= sys.float_info.epsilon:
128+
# left turn more than (or almost) 90°
129+
continue
130+
131+
right_angle_abs = get_rad_between_nodes(self, right)
132+
right_angle_rel = right_angle_abs - head_angle_abs
133+
if cos(right_angle_rel) <= sys.float_info.epsilon:
134+
# left turn more than (or almost) 90°
135+
continue
136+
137+
if sin(left_angle_rel) < sin(right_angle_rel):
138+
# Left and right mixed up. Although the permutations do
139+
# contain the correct combination, fixing this right
140+
# away potentially saves cycles:
141+
left, right = right, left
142+
143+
self.connected_on_head = head
144+
self.connected_on_left = left
145+
self.connected_on_right = right
146+
break
169147

170148
def to_serializable(self):
171149
"""See the description in the BaseElement class.

0 commit comments

Comments
 (0)