|
1 | | -import math |
| 1 | +import sys |
2 | 2 | from enum import Enum |
| 3 | +from itertools import permutations |
| 4 | +from math import atan2, cos, sin |
3 | 5 |
|
4 | 6 | from yaramo.base_element import BaseElement |
5 | 7 | from yaramo.geo_node import GeoNode |
@@ -102,70 +104,46 @@ def get_anschluss_of_other(self, other: "Node") -> NodeConnectionDirection: |
102 | 104 | return None |
103 | 105 |
|
104 | 106 | 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 |
169 | 147 |
|
170 | 148 | def to_serializable(self): |
171 | 149 | """See the description in the BaseElement class. |
|
0 commit comments