RDG-Networks 0.2.5__py3-none-any.whl → 0.3.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- {RDG_Networks-0.2.5.dist-info → RDG_Networks-0.3.0.dist-info}/METADATA +1 -1
- RDG_Networks-0.3.0.dist-info/RECORD +20 -0
- {RDG_Networks-0.2.5.dist-info → RDG_Networks-0.3.0.dist-info}/WHEEL +1 -1
- {RDG_Networks-0.2.5.dist-info → RDG_Networks-0.3.0.dist-info}/entry_points.txt +2 -0
- RDG_networks/__init__.py +4 -0
- RDG_networks/thickness/Classes.py +213 -0
- RDG_networks/thickness/__init__.py +7 -0
- RDG_networks/thickness/functions.py +255 -0
- RDG_networks/thickness/generate_line_segments_dynamic_thickness.py +572 -0
- RDG_networks/thickness/sample_in_polygon.py +54 -0
- RDG_Networks-0.2.5.dist-info/RECORD +0 -15
- {RDG_Networks-0.2.5.dist-info → RDG_Networks-0.3.0.dist-info}/LICENSE.txt +0 -0
- {RDG_Networks-0.2.5.dist-info → RDG_Networks-0.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
RDG_networks/Classes.py,sha256=_9X3JPHFAYYlaC8IZ_H9__sfz99G5l9UfPl65lL60_4,7977
|
2
|
+
RDG_networks/__init__.py,sha256=Re-b4Ky4fp0QzUB8R8c4VysUMkHEP1VrfXLeOA8W_2w,922
|
3
|
+
RDG_networks/draw_segments.py,sha256=U53N5GXmQHWKdM1Q1faP_EGKjc6enOu2mcsunzSFpP0,984
|
4
|
+
RDG_networks/generate_line_network.py,sha256=lJ4rhObim3WcEQoebomewRQKWNJC5phFyFYRW7qjXIg,1127
|
5
|
+
RDG_networks/generate_line_segments.py,sha256=QV8_k7q6TD5c7Hcb2Ms_apEdWYw4XdLr7rdJgh49v4Q,9004
|
6
|
+
RDG_networks/generate_line_segments_dynamic.py,sha256=GoIhGXYbcvjqR5BJCnkvAGp8QBpzsE1ZSbl2k9XAOGI,7531
|
7
|
+
RDG_networks/generate_line_segments_static.py,sha256=7KvHZi3krv-tAGydJR_gbMMmHKZ5azzrKcQe3fuWzCE,9265
|
8
|
+
RDG_networks/get_intersection_segments.py,sha256=mXB5qCy1oOps4Vu1mX6flW6v_4Xxc71YK41yOWjJX8o,2797
|
9
|
+
RDG_networks/sample_in_polygon.py,sha256=qpPpW-Da1vK8ZkVWMJ0zBsE8IgyMB619gCdybSkzKSQ,1605
|
10
|
+
RDG_networks/thickness/Classes.py,sha256=kgWNP5NCc11dTVAfujk30r_MsY2Xf4dAOmoalLRHa5E,8063
|
11
|
+
RDG_networks/thickness/__init__.py,sha256=6wL6ViEJvbybgfeKJM9jQJhzdxavdi5nsGtcv94Oh8I,182
|
12
|
+
RDG_networks/thickness/functions.py,sha256=cEUd858EZtf7ZUjq-MS1dctlWHPBxQzAY-B2Vko4DzU,11596
|
13
|
+
RDG_networks/thickness/generate_line_segments_dynamic_thickness.py,sha256=M8jOad-Jg_J14Y263k9RTNoLd0VblOTKTumqwOlCM_I,25598
|
14
|
+
RDG_networks/thickness/sample_in_polygon.py,sha256=nJ-yqfoCCGfC6_EpGL3L1t1LOYdqWZd-7v5bxy6th34,1849
|
15
|
+
RDG_Networks-0.3.0.dist-info/LICENSE.txt,sha256=BHUkX2GsdTr30sKmVZ1MLGR1njnx17EX_oUuuSVZZPE,598
|
16
|
+
RDG_Networks-0.3.0.dist-info/METADATA,sha256=9-zzX1WxWlxkDIzUGhXiQskWzIUx40WfFTpCo8CmqBU,2422
|
17
|
+
RDG_Networks-0.3.0.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
18
|
+
RDG_Networks-0.3.0.dist-info/entry_points.txt,sha256=6AGpOd8ecHGEsoUhRj0j6VvN_Fg0QNCY8h5rInAJom0,542
|
19
|
+
RDG_Networks-0.3.0.dist-info/top_level.txt,sha256=4gUUYafD5Al9V8ZSiViVGYHpRMMCsCBcGgCNodk9Syg,13
|
20
|
+
RDG_Networks-0.3.0.dist-info/RECORD,,
|
@@ -3,4 +3,6 @@ draw_segments = RDG_networks.draw_segments:main
|
|
3
3
|
generate_line_network = RDG_networks.generate_line_network:main
|
4
4
|
generate_line_segments = RDG_networks.generate_line_segments:main
|
5
5
|
generate_line_segments_dynamic = RDG_networks.generate_line_segments_dynamic:main
|
6
|
+
generate_line_segments_dynamic_thickness = RDG_networks.thickness.generate_line_segments_dynamic_thickness:main
|
7
|
+
generate_line_segments_static = RDG_networks.generate_line_segments_static:main
|
6
8
|
get_intersection_segments = RDG_networks.get_intersection_segments:main
|
RDG_networks/__init__.py
CHANGED
@@ -5,12 +5,16 @@ from .generate_line_segments import generate_line_segments
|
|
5
5
|
from .generate_line_network import generate_line_network
|
6
6
|
from .get_intersection_segments import get_intersection_segments
|
7
7
|
from .generate_line_segments_dynamic import generate_line_segments_dynamic
|
8
|
+
from .generate_line_segments_static import generate_line_segments_static
|
8
9
|
from .draw_segments import draw_segments
|
10
|
+
from .thickness.generate_line_segments_dynamic_thickness import generate_line_segments_thickness
|
9
11
|
|
10
12
|
__all__ = ['generate_line_segments',
|
11
13
|
'generate_line_network',
|
12
14
|
'get_intersection_segments',
|
13
15
|
'generate_line_segments_dynamic',
|
16
|
+
'generate_line_segments_static',
|
17
|
+
'generate_line_segments_thickness',
|
14
18
|
'draw_segments',
|
15
19
|
'sample_in_polygon',
|
16
20
|
'Line',
|
@@ -0,0 +1,213 @@
|
|
1
|
+
import copy
|
2
|
+
import matplotlib.axes._axes as axes
|
3
|
+
from matplotlib.patches import Polygon as polgon
|
4
|
+
import numpy as np
|
5
|
+
from typing import List, Tuple, Union, Optional
|
6
|
+
|
7
|
+
class Line:
|
8
|
+
"""
|
9
|
+
Represents a line segment by its location and direction.
|
10
|
+
|
11
|
+
Attributes:
|
12
|
+
- location (Tuple[float, float]): The starting point of the line.
|
13
|
+
- direction (Tuple[float, float]): The direction vector of the line.
|
14
|
+
- id (Optional[Union[str, int]]): Identifier for the line segment.
|
15
|
+
"""
|
16
|
+
|
17
|
+
def __init__(self, location: Tuple[float, float], direction: Tuple[float, float], id: Optional[Union[str, int]] = None, neighbors_initial={}, neighbors={}):
|
18
|
+
self.location = location
|
19
|
+
self.direction = direction
|
20
|
+
self.id = id
|
21
|
+
self.neighbors_initial = neighbors_initial
|
22
|
+
self.neighbors = neighbors
|
23
|
+
|
24
|
+
|
25
|
+
def draw(self, ax: axes.Axes, color: str = 'black', alpha: float = 1.0, label: bool = False, linewidth=1):
|
26
|
+
"""
|
27
|
+
Draw the line segment on a given axes.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
- ax (axes.Axes): Matplotlib axes on which to draw the line segment.
|
31
|
+
- color (str): Color of the line segment (default is 'black').
|
32
|
+
- alpha (float): Alpha (transparency) value (default is 1.0).
|
33
|
+
"""
|
34
|
+
|
35
|
+
x1, y1 = np.array(self.location[0]) - np.array(self.direction) / np.linalg.norm(self.direction) * 100
|
36
|
+
x2, y2 = np.array(self.location[1]) - np.array(self.direction) / np.linalg.norm(self.direction) * 100
|
37
|
+
ax.plot([x1, x2], [y1, y2], color=color, alpha=alpha, linewidth=linewidth)
|
38
|
+
|
39
|
+
if label:
|
40
|
+
ax.text((x1+x2)/2, (y1+y2)/2, self.id, fontsize=12)
|
41
|
+
|
42
|
+
ax.set(xlim=(0, 1), ylim=(0, 1))
|
43
|
+
|
44
|
+
class LineSegment:
|
45
|
+
"""
|
46
|
+
Represents a line segment defined by its start and end points.
|
47
|
+
|
48
|
+
Attributes:
|
49
|
+
- start (Tuple[float, float]): Starting point of the line segment.
|
50
|
+
- end (Tuple[float, float]): Ending point of the line segment.
|
51
|
+
- id (Optional[Union[str, int]]): Identifier for the line segment.
|
52
|
+
"""
|
53
|
+
|
54
|
+
def __init__(self, start: Tuple[float, float], end: Tuple[float, float], id: Optional[Union[str, int]] = None, neighbors_initial={}, neighbors={}):
|
55
|
+
self.start = start
|
56
|
+
self.end = end
|
57
|
+
self.id = id
|
58
|
+
self.neighbors_initial = neighbors_initial
|
59
|
+
self.neighbors = neighbors
|
60
|
+
|
61
|
+
def length(self) -> float:
|
62
|
+
return np.linalg.norm(np.array(self.start) - np.array(self.end))
|
63
|
+
|
64
|
+
def draw(self, ax: axes.Axes, color: str = 'black', alpha: float = 1.0, label: bool = False, linewidth=1):
|
65
|
+
"""
|
66
|
+
Draw the line segment on a given axes.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
- ax (axes.Axes): Matplotlib axes on which to draw the line segment.
|
70
|
+
- color (str): Color of the line segment (default is 'black').
|
71
|
+
- alpha (float): Alpha (transparency) value (default is 1.0).
|
72
|
+
"""
|
73
|
+
|
74
|
+
x1, y1 = self.start
|
75
|
+
x2, y2 = self.end
|
76
|
+
ax.plot([x1, x2], [y1, y2], color=color, alpha=alpha, linewidth=linewidth)
|
77
|
+
|
78
|
+
if label:
|
79
|
+
ax.text((x1+x2)/2, (y1+y2)/2, self.id, fontsize=12)
|
80
|
+
|
81
|
+
def copy(self):
|
82
|
+
"""
|
83
|
+
Create a copy of the LineSegment object.
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
- LineSegment: A new LineSegment object with the same attributes.
|
87
|
+
"""
|
88
|
+
return copy.deepcopy(self)
|
89
|
+
|
90
|
+
class Polygon:
|
91
|
+
"""
|
92
|
+
Represents a polygon defined by a list of vertices.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
vertices (List[Tuple[float, float]]): A list of (x, y) coordinates representing the vertices of the polygon.
|
96
|
+
"""
|
97
|
+
|
98
|
+
def __init__(self, vertices: List[tuple], middle_segment=None):
|
99
|
+
"""
|
100
|
+
Initializes a Polygon instance with the provided vertices.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
vertices (List[Tuple[float, float]]): A list of (x, y) coordinates representing the vertices of the polygon.
|
104
|
+
"""
|
105
|
+
self.vertices = vertices
|
106
|
+
self.middle_line = middle_segment
|
107
|
+
|
108
|
+
def area(self) -> float:
|
109
|
+
"""
|
110
|
+
Calculates the area of the polygon.
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
float: The area of the polygon.
|
114
|
+
|
115
|
+
Raises:
|
116
|
+
ValueError: If the polygon has less than 3 vertices.
|
117
|
+
"""
|
118
|
+
if len(self.vertices) < 3:
|
119
|
+
raise ValueError("A polygon must have at least 3 vertices.")
|
120
|
+
|
121
|
+
area = 0.0
|
122
|
+
|
123
|
+
for i in range(len(self.vertices)):
|
124
|
+
x1, y1 = self.vertices[i]
|
125
|
+
x2, y2 = self.vertices[(i + 1) % len(self.vertices)]
|
126
|
+
area += (x1 * y2) - (x2 * y1)
|
127
|
+
|
128
|
+
area = abs(area) / 2.0
|
129
|
+
|
130
|
+
return area
|
131
|
+
|
132
|
+
def sort_vertices(self) -> List[Tuple[float, float]]:
|
133
|
+
"""
|
134
|
+
Sorts the vertices of the polygon based on their polar angles with respect to a reference point.
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
List[Tuple[float, float]]: The sorted list of vertices.
|
138
|
+
"""
|
139
|
+
def polar_angle(point: Tuple[float, float], reference_point: Tuple[float, float]) -> float:
|
140
|
+
"""
|
141
|
+
Calculates the polar angle of a point with respect to a reference point.
|
142
|
+
|
143
|
+
Args:
|
144
|
+
point (Tuple[float, float]): The coordinates (x, y) of the point for which to calculate the polar angle.
|
145
|
+
reference_point (Tuple[float, float]): The coordinates (x, y) of the reference point.
|
146
|
+
|
147
|
+
Returns:
|
148
|
+
float: The polar angle in radians.
|
149
|
+
"""
|
150
|
+
dx = point[0] - reference_point[0]
|
151
|
+
dy = point[1] - reference_point[1]
|
152
|
+
return np.arctan2(dy, dx)
|
153
|
+
|
154
|
+
reference_point = min(self.vertices, key=lambda point: point[1])
|
155
|
+
return sorted(self.vertices, key=lambda point: polar_angle(point, reference_point))
|
156
|
+
|
157
|
+
def draw(self, ax: axes.Axes, color='purple', alpha=0.8):
|
158
|
+
"""
|
159
|
+
Draws a filled polygon with the given vertices on the specified Matplotlib axes.
|
160
|
+
|
161
|
+
Args:
|
162
|
+
ax (matplotlib.axes.Axes): The Matplotlib axes on which to draw the polygon.
|
163
|
+
|
164
|
+
Note:
|
165
|
+
This method sorts the vertices based on their polar angles with respect to a reference point
|
166
|
+
(vertex with the lowest y-coordinate) before drawing the filled polygon.
|
167
|
+
"""
|
168
|
+
sorted_vertices = self.sort_vertices()
|
169
|
+
polygon = polgon(sorted_vertices, closed=True, alpha=alpha, facecolor=color, edgecolor='black')
|
170
|
+
ax.add_patch(polygon)
|
171
|
+
|
172
|
+
def perimeter(self):
|
173
|
+
perimeter = 0
|
174
|
+
for i in range(len(self.vertices)):
|
175
|
+
x1, y1 = self.vertices[i]
|
176
|
+
x2, y2 = self.vertices[(i + 1) % len(self.vertices)]
|
177
|
+
perimeter += np.sqrt((x1-x2)**2 + (y1-y2)**2)
|
178
|
+
return perimeter
|
179
|
+
|
180
|
+
class Cycle:
|
181
|
+
def __init__(self, vertices, id=None):
|
182
|
+
self.vertices = vertices
|
183
|
+
self.id = id
|
184
|
+
|
185
|
+
def sort_vertices(self) -> List[Tuple[float, float]]:
|
186
|
+
"""
|
187
|
+
Sorts the vertices of the polygon based on their polar angles with respect to a reference point.
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
List[Tuple[float, float]]: The sorted list of vertices.
|
191
|
+
"""
|
192
|
+
def polar_angle(point: Tuple[float, float], reference_point: Tuple[float, float]) -> float:
|
193
|
+
"""
|
194
|
+
Calculates the polar angle of a point with respect to a reference point.
|
195
|
+
|
196
|
+
Args:
|
197
|
+
point (Tuple[float, float]): The coordinates (x, y) of the point for which to calculate the polar angle.
|
198
|
+
reference_point (Tuple[float, float]): The coordinates (x, y) of the reference point.
|
199
|
+
|
200
|
+
Returns:
|
201
|
+
float: The polar angle in radians.
|
202
|
+
"""
|
203
|
+
dx = point[0] - reference_point[0]
|
204
|
+
dy = point[1] - reference_point[1]
|
205
|
+
return np.arctan2(dy, dx)
|
206
|
+
|
207
|
+
reference_point = min(self.vertices, key=lambda point: point[1])
|
208
|
+
return sorted(self.vertices, key=lambda point: polar_angle(point, reference_point))
|
209
|
+
|
210
|
+
def draw(self, ax: axes.Axes, color='purple', alpha=0.8):
|
211
|
+
sorted_vertices = self.sort_vertices()
|
212
|
+
polygon = polgon(sorted_vertices, closed=False, alpha=alpha, color=color)
|
213
|
+
ax.add_patch(polygon)
|
@@ -0,0 +1,255 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import random
|
3
|
+
from typing import List, Dict, Tuple, Union, Optional
|
4
|
+
|
5
|
+
from .Classes import Line, LineSegment
|
6
|
+
from .sample_in_polygon import sample_in_polygon, is_inside_polygon
|
7
|
+
|
8
|
+
def doLinesIntersect(line1: Line, line2: Line) -> Tuple[bool, Union[Tuple[float, float], None]]:
|
9
|
+
"""
|
10
|
+
Check if two lines intersect and return the intersection point.
|
11
|
+
|
12
|
+
Args:
|
13
|
+
- line1 (Line): The first line segment.
|
14
|
+
- line2 (Line): The second line segment.
|
15
|
+
|
16
|
+
Returns:
|
17
|
+
- intersect (bool): True if the lines intersect, False otherwise.
|
18
|
+
- intersection_point (tuple or None): The intersection point (x, y) if lines intersect, None otherwise.
|
19
|
+
"""
|
20
|
+
x1, y1 = line1.location
|
21
|
+
v1, w1 = line1.direction
|
22
|
+
|
23
|
+
x2, y2 = line2.location
|
24
|
+
v2, w2 = line2.direction
|
25
|
+
|
26
|
+
determinant = v1 * w2 - v2 * w1
|
27
|
+
|
28
|
+
if determinant == 0:
|
29
|
+
return False, (None, None)
|
30
|
+
|
31
|
+
t1 = ((x2 - x1) * w2 - (y2 - y1) * v2) / determinant
|
32
|
+
t2 = ((x2 - x1) * w1 - (y2 - y1) * v1) / determinant
|
33
|
+
|
34
|
+
intersect_x = x1 + v1 * t1
|
35
|
+
intersect_y = y2 + w2 * t2
|
36
|
+
|
37
|
+
if -1e-6 < intersect_x < 1 + 1e-6 and -1e-6 < intersect_y < 1 + 1e-6:
|
38
|
+
return True, (intersect_x, intersect_y)
|
39
|
+
else:
|
40
|
+
return False, (None, None)
|
41
|
+
|
42
|
+
def doSegmentsIntersect(
|
43
|
+
segment1: 'LineSegment',
|
44
|
+
segment2: 'LineSegment'
|
45
|
+
) -> Tuple[bool, Tuple[Optional[float], Optional[float]]]:
|
46
|
+
"""
|
47
|
+
Determines if two line segments intersect and returns the intersection point if they do.
|
48
|
+
|
49
|
+
Args:
|
50
|
+
segment1 (LineSegment): The first line segment.
|
51
|
+
segment2 (LineSegment): The second line segment.
|
52
|
+
|
53
|
+
Returns:
|
54
|
+
Tuple[bool, Tuple[Optional[float], Optional[float]]]:
|
55
|
+
- A boolean indicating whether the segments intersect.
|
56
|
+
- A tuple of the x and y coordinates of the intersection point if they intersect,
|
57
|
+
otherwise (None, None).
|
58
|
+
"""
|
59
|
+
|
60
|
+
# Create line equations based on the segments' start and end points
|
61
|
+
line1 = Line(location=segment1.start, direction=np.array(segment1.end) - np.array(segment1.start))
|
62
|
+
line2 = Line(location=segment2.start, direction=np.array(segment2.end) - np.array(segment2.start))
|
63
|
+
|
64
|
+
# Check if the infinite extensions of the two lines intersect
|
65
|
+
intersect, (intersect_x, intersect_y) = doLinesIntersect(line1, line2)
|
66
|
+
|
67
|
+
# If no intersection, return False
|
68
|
+
if not intersect:
|
69
|
+
return False, (None, None)
|
70
|
+
|
71
|
+
# Check if the intersection point is within the bounds of both segments in the x-direction
|
72
|
+
xcheck = (
|
73
|
+
(segment1.end[0] <= intersect_x <= segment1.start[0]
|
74
|
+
or segment1.start[0] <= intersect_x <= segment1.end[0]
|
75
|
+
or abs(intersect_x - segment1.end[0]) < 1e-6
|
76
|
+
or abs(intersect_x - segment1.start[0]) < 1e-6)
|
77
|
+
and
|
78
|
+
(segment2.end[0] <= intersect_x <= segment2.start[0]
|
79
|
+
or segment2.start[0] <= intersect_x <= segment2.end[0]
|
80
|
+
or abs(intersect_x - segment2.end[0]) < 1e-6
|
81
|
+
or abs(intersect_x - segment2.start[0]) < 1e-6)
|
82
|
+
)
|
83
|
+
|
84
|
+
# Check if the intersection point is within the bounds of both segments in the y-direction
|
85
|
+
ycheck = (
|
86
|
+
(segment1.end[1] <= intersect_y <= segment1.start[1]
|
87
|
+
or segment1.start[1] <= intersect_y <= segment1.end[1]
|
88
|
+
or abs(intersect_y - segment1.end[1]) < 1e-6
|
89
|
+
or abs(intersect_y - segment1.start[1]) < 1e-6)
|
90
|
+
and
|
91
|
+
(segment2.end[1] <= intersect_y <= segment2.start[1]
|
92
|
+
or segment2.start[1] <= intersect_y <= segment2.end[1]
|
93
|
+
or abs(intersect_y - segment2.end[1]) < 1e-6
|
94
|
+
or abs(intersect_y - segment2.start[1]) < 1e-6)
|
95
|
+
)
|
96
|
+
|
97
|
+
# If the intersection point lies within the bounds of both segments, return True with the intersection point
|
98
|
+
if xcheck and ycheck:
|
99
|
+
return True, (intersect_x, intersect_y)
|
100
|
+
|
101
|
+
# Otherwise, return False and no intersection point
|
102
|
+
return False, (None, None)
|
103
|
+
|
104
|
+
def pick_item_with_probability(
|
105
|
+
polygon_arr: Dict[str, Dict[str, object]]
|
106
|
+
) -> Tuple[str, Dict[str, object]]:
|
107
|
+
"""
|
108
|
+
Randomly selects an item from the polygon array with a probability proportional to the area of the polygons.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
polygon_arr (Dict[str, Dict[str, object]]):
|
112
|
+
A dictionary where keys are polygon identifiers (e.g., 'p1', 'p2') and values are dictionaries containing polygon properties,
|
113
|
+
including an 'area' key that stores the area of the polygon.
|
114
|
+
|
115
|
+
Returns:
|
116
|
+
Tuple[str, Dict[str, object]]:
|
117
|
+
- The identifier of the selected polygon.
|
118
|
+
- The corresponding polygon data (dictionary) containing its properties.
|
119
|
+
"""
|
120
|
+
|
121
|
+
# Calculate the total weight (sum of areas of all polygons)
|
122
|
+
max_weight = sum(pol['area'] for pol in polygon_arr.values())
|
123
|
+
|
124
|
+
# Generate a random threshold between 0 and the total weight
|
125
|
+
threshold = random.uniform(0, max_weight)
|
126
|
+
cumulative_weight = 0
|
127
|
+
|
128
|
+
# Iterate through the polygons, accumulating weights
|
129
|
+
for item, pol in polygon_arr.items():
|
130
|
+
weight = pol['area']
|
131
|
+
cumulative_weight += weight
|
132
|
+
|
133
|
+
# Return the polygon when the cumulative weight surpasses the threshold
|
134
|
+
if cumulative_weight >= threshold:
|
135
|
+
return item, pol
|
136
|
+
|
137
|
+
def get_location_and_direction(
|
138
|
+
polygon_arr: Dict[str, Dict[str, object]],
|
139
|
+
thickness: float,
|
140
|
+
max_attempts: int = 1000,
|
141
|
+
angles: Union[str, List[float]] = 'uniform'
|
142
|
+
) -> Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
|
143
|
+
"""
|
144
|
+
Attempts to find a valid location and direction within a polygon for placing a new segment. The direction can either be randomly
|
145
|
+
chosen (uniformly) or from a specified list of angles. It ensures that the segment lies within the polygon's bounds given the
|
146
|
+
specified thickness.
|
147
|
+
|
148
|
+
Args:
|
149
|
+
polygon_arr (Dict[str, Dict[str, object]]):
|
150
|
+
A dictionary where the keys are polygon identifiers and the values are dictionaries containing polygon properties, including 'vertices'.
|
151
|
+
thickness (float):
|
152
|
+
The thickness of the segment that needs to fit inside the polygon.
|
153
|
+
max_attempts (int, optional):
|
154
|
+
The maximum number of attempts to find a valid location and direction. Defaults to 1000.
|
155
|
+
angles (Union[str, List[float]], optional):
|
156
|
+
A string ('uniform' for random directions) or a list of angles (in radians) to choose the direction from. Defaults to 'uniform'.
|
157
|
+
|
158
|
+
Returns:
|
159
|
+
Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
|
160
|
+
- If a valid location and direction are found, returns a tuple containing:
|
161
|
+
- The polygon ID (`str`).
|
162
|
+
- The polygon data (`Dict[str, object]`).
|
163
|
+
- The new location as a tuple of floats (`Tuple[float, float]`).
|
164
|
+
- The direction vector as a numpy array (`np.ndarray`).
|
165
|
+
- The perpendicular vector to the direction as a numpy array (`np.ndarray`).
|
166
|
+
- Returns `False` if no valid location and direction are found after the maximum attempts.
|
167
|
+
"""
|
168
|
+
|
169
|
+
# Generate a new direction based on the angles parameter
|
170
|
+
if angles == 'uniform':
|
171
|
+
direction = np.array([random.uniform(-1, 1), random.uniform(-1, 1)])
|
172
|
+
direction = direction / np.linalg.norm(direction) # Normalize the direction vector
|
173
|
+
else:
|
174
|
+
directions = [ (np.cos(angle), np.sin(angle)) for angle in angles ]
|
175
|
+
direction = random.choice(directions)
|
176
|
+
direction = np.array(direction) / np.linalg.norm(direction) # Normalize the chosen direction
|
177
|
+
|
178
|
+
# Try to find a valid location and direction up to max_attempts
|
179
|
+
attempt = 0
|
180
|
+
while attempt < max_attempts:
|
181
|
+
polygon_id, polygon = pick_item_with_probability(polygon_arr)
|
182
|
+
|
183
|
+
# Sample a location within the polygon
|
184
|
+
location_new = sample_in_polygon(polygon['vertices'])
|
185
|
+
|
186
|
+
# Compute the perpendicular vector to the direction
|
187
|
+
perpendicular = np.array([direction[1], -direction[0]])
|
188
|
+
perpendicular = perpendicular / np.linalg.norm(perpendicular)
|
189
|
+
|
190
|
+
# Ensure the perpendicular vector is oriented consistently (y-component is non-negative)
|
191
|
+
if perpendicular[1] < 0:
|
192
|
+
perpendicular = -perpendicular
|
193
|
+
|
194
|
+
# Compute the positions for the segment with thickness, shifted by half-thickness along the perpendicular direction
|
195
|
+
p1 = np.array(location_new) + thickness / 2 * perpendicular
|
196
|
+
p2 = np.array(location_new) - thickness / 2 * perpendicular
|
197
|
+
|
198
|
+
# Check if both endpoints of the segment are inside the polygon
|
199
|
+
if is_inside_polygon(polygon['vertices'], p1) and is_inside_polygon(polygon['vertices'], p2):
|
200
|
+
return polygon_id, polygon, location_new, direction, perpendicular
|
201
|
+
|
202
|
+
attempt += 1
|
203
|
+
|
204
|
+
# If no valid location and direction is found, return False
|
205
|
+
return False
|
206
|
+
|
207
|
+
def get_polygons(polygon_id, polygon_arr, neighbor1_1, neighbor1_2, vertex_begin_1, vertex_end_1, neighbor2_1, neighbor2_2, vertex_begin_2, vertex_end_2, segment_new_id_1, segment_new_id_2):
|
208
|
+
# Extract vertices and cycle (faces) of the original polygon
|
209
|
+
vertices = polygon_arr[polygon_id]['vertices']
|
210
|
+
cycle = polygon_arr[polygon_id]['faces']
|
211
|
+
|
212
|
+
# Get first cycle and vertices
|
213
|
+
index_start_1, index_end_1 = (cycle.index(neighbor1_1), cycle.index(neighbor1_2))
|
214
|
+
if index_start_1 < index_end_1:
|
215
|
+
cycle1 = [segment_new_id_1] + cycle[index_start_1:index_end_1+1]
|
216
|
+
vertices1 = [vertex_begin_1] + vertices[index_start_1:index_end_1] + [vertex_end_1]
|
217
|
+
else:
|
218
|
+
cycle1 = [segment_new_id_1] + cycle[index_start_1:] + cycle[:index_end_1+1]
|
219
|
+
vertices1 = [vertex_begin_1] + vertices[index_start_1:] + vertices[:index_end_1] + [vertex_end_1]
|
220
|
+
|
221
|
+
# Get second cycle and vertices
|
222
|
+
index_start_2, index_end_2 = (cycle.index(neighbor2_2), cycle.index(neighbor2_1))
|
223
|
+
if index_start_2 < index_end_2:
|
224
|
+
cycle2 = [segment_new_id_2] + cycle[index_start_2:index_end_2+1]
|
225
|
+
vertices2 = [vertex_end_2] + vertices[index_start_2:index_end_2] + [vertex_begin_2]
|
226
|
+
else:
|
227
|
+
cycle2 = [segment_new_id_2] + cycle[index_start_2:] + cycle[:index_end_2+1]
|
228
|
+
vertices2 = [vertex_end_2] + vertices[index_start_2:] + vertices[:index_end_2] + [vertex_begin_2]
|
229
|
+
|
230
|
+
# Get middle cycle and vertices
|
231
|
+
cycle0 = [neighbor1_1, segment_new_id_1, neighbor1_2]
|
232
|
+
vertices0 = [vertex_begin_1, vertex_end_1]
|
233
|
+
|
234
|
+
index_start_0, index_end_0 = (cycle.index(neighbor1_2), cycle.index(neighbor2_2))
|
235
|
+
if index_start_0 < index_end_0:
|
236
|
+
cycle0 = cycle0 + cycle[index_start_0:index_end_0+1]
|
237
|
+
vertices0 = vertices0 + vertices[index_start_0:index_end_0]
|
238
|
+
|
239
|
+
elif index_start_0 > index_end_0:
|
240
|
+
cycle0 = cycle0 + cycle[index_start_0:] + cycle[:index_end_0+1]
|
241
|
+
vertices0 = vertices0 + vertices[index_start_0:] + vertices[:index_end_0]
|
242
|
+
|
243
|
+
cycle0 = cycle0 + [segment_new_id_2]
|
244
|
+
vertices0 = vertices0 + [vertex_end_2] + [vertex_begin_2]
|
245
|
+
|
246
|
+
index_start_0, index_end_0 = (cycle.index(neighbor2_1), cycle.index(neighbor1_1))
|
247
|
+
if index_start_0 < index_end_0:
|
248
|
+
cycle0 = cycle0 + cycle[index_start_0:index_end_0+1]
|
249
|
+
vertices0 = vertices0 + vertices[index_start_0:index_end_0]
|
250
|
+
|
251
|
+
elif index_start_0 > index_end_0:
|
252
|
+
cycle0 = cycle0 + cycle[index_start_0:] + cycle[:index_end_0+1]
|
253
|
+
vertices0 = vertices0 + vertices[index_start_0:] + vertices[:index_end_0]
|
254
|
+
|
255
|
+
return cycle0, vertices0, cycle1, vertices1, cycle2, vertices2
|
@@ -0,0 +1,572 @@
|
|
1
|
+
import math
|
2
|
+
import numpy as np
|
3
|
+
from typing import List, Dict, Tuple, Union, Optional
|
4
|
+
|
5
|
+
from .Classes import Line, LineSegment
|
6
|
+
from .sample_in_polygon import sample_in_polygon, is_inside_polygon
|
7
|
+
|
8
|
+
def doLinesIntersect(line1: Line, line2: Line) -> Tuple[bool, Union[Tuple[float, float], None]]:
|
9
|
+
"""
|
10
|
+
Check if two lines intersect and return the intersection point.
|
11
|
+
|
12
|
+
Args:
|
13
|
+
- line1 (Line): The first line segment.
|
14
|
+
- line2 (Line): The second line segment.
|
15
|
+
|
16
|
+
Returns:
|
17
|
+
- intersect (bool): True if the lines intersect, False otherwise.
|
18
|
+
- intersection_point (tuple or None): The intersection point (x, y) if lines intersect, None otherwise.
|
19
|
+
"""
|
20
|
+
x1, y1 = line1.location
|
21
|
+
v1, w1 = line1.direction
|
22
|
+
|
23
|
+
x2, y2 = line2.location
|
24
|
+
v2, w2 = line2.direction
|
25
|
+
|
26
|
+
determinant = v1 * w2 - v2 * w1
|
27
|
+
|
28
|
+
if determinant == 0:
|
29
|
+
return False, (None, None)
|
30
|
+
|
31
|
+
t1 = ((x2 - x1) * w2 - (y2 - y1) * v2) / determinant
|
32
|
+
t2 = ((x2 - x1) * w1 - (y2 - y1) * v1) / determinant
|
33
|
+
|
34
|
+
intersect_x = x1 + v1 * t1
|
35
|
+
intersect_y = y2 + w2 * t2
|
36
|
+
|
37
|
+
if -1e-6 < intersect_x < 1 + 1e-6 and -1e-6 < intersect_y < 1 + 1e-6:
|
38
|
+
return True, (intersect_x, intersect_y)
|
39
|
+
else:
|
40
|
+
return False, (None, None)
|
41
|
+
|
42
|
+
def doSegmentsIntersect(
|
43
|
+
segment1: 'LineSegment',
|
44
|
+
segment2: 'LineSegment'
|
45
|
+
) -> Tuple[bool, Tuple[Optional[float], Optional[float]]]:
|
46
|
+
"""
|
47
|
+
Determines if two line segments intersect and returns the intersection point if they do.
|
48
|
+
|
49
|
+
Args:
|
50
|
+
segment1 (LineSegment): The first line segment.
|
51
|
+
segment2 (LineSegment): The second line segment.
|
52
|
+
|
53
|
+
Returns:
|
54
|
+
Tuple[bool, Tuple[Optional[float], Optional[float]]]:
|
55
|
+
- A boolean indicating whether the segments intersect.
|
56
|
+
- A tuple of the x and y coordinates of the intersection point if they intersect,
|
57
|
+
otherwise (None, None).
|
58
|
+
"""
|
59
|
+
|
60
|
+
# Create line equations based on the segments' start and end points
|
61
|
+
line1 = Line(location=segment1.start, direction=np.array(segment1.end) - np.array(segment1.start))
|
62
|
+
line2 = Line(location=segment2.start, direction=np.array(segment2.end) - np.array(segment2.start))
|
63
|
+
|
64
|
+
# Check if the infinite extensions of the two lines intersect
|
65
|
+
intersect, (intersect_x, intersect_y) = doLinesIntersect(line1, line2)
|
66
|
+
|
67
|
+
# If no intersection, return False
|
68
|
+
if not intersect:
|
69
|
+
return False, (None, None)
|
70
|
+
|
71
|
+
# Check if the intersection point is within the bounds of both segments in the x-direction
|
72
|
+
xcheck = (
|
73
|
+
(segment1.end[0] <= intersect_x <= segment1.start[0]
|
74
|
+
or segment1.start[0] <= intersect_x <= segment1.end[0]
|
75
|
+
or abs(intersect_x - segment1.end[0]) < 1e-6
|
76
|
+
or abs(intersect_x - segment1.start[0]) < 1e-6)
|
77
|
+
and
|
78
|
+
(segment2.end[0] <= intersect_x <= segment2.start[0]
|
79
|
+
or segment2.start[0] <= intersect_x <= segment2.end[0]
|
80
|
+
or abs(intersect_x - segment2.end[0]) < 1e-6
|
81
|
+
or abs(intersect_x - segment2.start[0]) < 1e-6)
|
82
|
+
)
|
83
|
+
|
84
|
+
# Check if the intersection point is within the bounds of both segments in the y-direction
|
85
|
+
ycheck = (
|
86
|
+
(segment1.end[1] <= intersect_y <= segment1.start[1]
|
87
|
+
or segment1.start[1] <= intersect_y <= segment1.end[1]
|
88
|
+
or abs(intersect_y - segment1.end[1]) < 1e-6
|
89
|
+
or abs(intersect_y - segment1.start[1]) < 1e-6)
|
90
|
+
and
|
91
|
+
(segment2.end[1] <= intersect_y <= segment2.start[1]
|
92
|
+
or segment2.start[1] <= intersect_y <= segment2.end[1]
|
93
|
+
or abs(intersect_y - segment2.end[1]) < 1e-6
|
94
|
+
or abs(intersect_y - segment2.start[1]) < 1e-6)
|
95
|
+
)
|
96
|
+
|
97
|
+
# If the intersection point lies within the bounds of both segments, return True with the intersection point
|
98
|
+
if xcheck and ycheck:
|
99
|
+
return True, (intersect_x, intersect_y)
|
100
|
+
|
101
|
+
# Otherwise, return False and no intersection point
|
102
|
+
return False, (None, None)
|
103
|
+
|
104
|
+
def pick_item_with_probability(
|
105
|
+
polygon_arr: Dict[str, Dict[str, object]]
|
106
|
+
) -> Tuple[str, Dict[str, object]]:
|
107
|
+
"""
|
108
|
+
Randomly selects an item from the polygon array with a probability proportional to the area of the polygons.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
polygon_arr (Dict[str, Dict[str, object]]):
|
112
|
+
A dictionary where keys are polygon identifiers (e.g., 'p1', 'p2') and values are dictionaries containing polygon properties,
|
113
|
+
including an 'area' key that stores the area of the polygon.
|
114
|
+
|
115
|
+
Returns:
|
116
|
+
Tuple[str, Dict[str, object]]:
|
117
|
+
- The identifier of the selected polygon.
|
118
|
+
- The corresponding polygon data (dictionary) containing its properties.
|
119
|
+
"""
|
120
|
+
|
121
|
+
# Calculate the total weight (sum of areas of all polygons)
|
122
|
+
max_weight = sum(pol['area'] for pol in polygon_arr.values())
|
123
|
+
|
124
|
+
# Generate a random threshold between 0 and the total weight
|
125
|
+
threshold = random.uniform(0, max_weight)
|
126
|
+
cumulative_weight = 0
|
127
|
+
|
128
|
+
# Iterate through the polygons, accumulating weights
|
129
|
+
for item, pol in polygon_arr.items():
|
130
|
+
weight = pol['area']
|
131
|
+
cumulative_weight += weight
|
132
|
+
|
133
|
+
# Return the polygon when the cumulative weight surpasses the threshold
|
134
|
+
if cumulative_weight >= threshold:
|
135
|
+
return item, pol
|
136
|
+
|
137
|
+
def get_location_and_direction(
|
138
|
+
polygon_arr: Dict[str, Dict[str, object]],
|
139
|
+
thickness: float,
|
140
|
+
max_attempts: int = 1000,
|
141
|
+
angles: Union[str, List[float]] = 'uniform'
|
142
|
+
) -> Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
|
143
|
+
"""
|
144
|
+
Attempts to find a valid location and direction within a polygon for placing a new segment. The direction can either be randomly
|
145
|
+
chosen (uniformly) or from a specified list of angles. It ensures that the segment lies within the polygon's bounds given the
|
146
|
+
specified thickness.
|
147
|
+
|
148
|
+
Args:
|
149
|
+
polygon_arr (Dict[str, Dict[str, object]]):
|
150
|
+
A dictionary where the keys are polygon identifiers and the values are dictionaries containing polygon properties, including 'vertices'.
|
151
|
+
thickness (float):
|
152
|
+
The thickness of the segment that needs to fit inside the polygon.
|
153
|
+
max_attempts (int, optional):
|
154
|
+
The maximum number of attempts to find a valid location and direction. Defaults to 1000.
|
155
|
+
angles (Union[str, List[float]], optional):
|
156
|
+
A string ('uniform' for random directions) or a list of angles (in radians) to choose the direction from. Defaults to 'uniform'.
|
157
|
+
|
158
|
+
Returns:
|
159
|
+
Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
|
160
|
+
- If a valid location and direction are found, returns a tuple containing:
|
161
|
+
- The polygon ID (`str`).
|
162
|
+
- The polygon data (`Dict[str, object]`).
|
163
|
+
- The new location as a tuple of floats (`Tuple[float, float]`).
|
164
|
+
- The direction vector as a numpy array (`np.ndarray`).
|
165
|
+
- The perpendicular vector to the direction as a numpy array (`np.ndarray`).
|
166
|
+
- Returns `False` if no valid location and direction are found after the maximum attempts.
|
167
|
+
"""
|
168
|
+
|
169
|
+
# Generate a new direction based on the angles parameter
|
170
|
+
if angles == 'uniform':
|
171
|
+
direction = np.array([random.uniform(-1, 1), random.uniform(-1, 1)])
|
172
|
+
direction = direction / np.linalg.norm(direction) # Normalize the direction vector
|
173
|
+
else:
|
174
|
+
directions = [ (np.cos(angle), np.sin(angle)) for angle in angles ]
|
175
|
+
direction = random.choice(directions)
|
176
|
+
direction = np.array(direction) / np.linalg.norm(direction) # Normalize the chosen direction
|
177
|
+
|
178
|
+
# Try to find a valid location and direction up to max_attempts
|
179
|
+
attempt = 0
|
180
|
+
while attempt < max_attempts:
|
181
|
+
polygon_id, polygon = pick_item_with_probability(polygon_arr)
|
182
|
+
|
183
|
+
# Sample a location within the polygon
|
184
|
+
location_new = sample_in_polygon(polygon['vertices'])
|
185
|
+
|
186
|
+
# Compute the perpendicular vector to the direction
|
187
|
+
perpendicular = np.array([direction[1], -direction[0]])
|
188
|
+
perpendicular = perpendicular / np.linalg.norm(perpendicular)
|
189
|
+
|
190
|
+
# Ensure the perpendicular vector is oriented consistently (y-component is non-negative)
|
191
|
+
if perpendicular[1] < 0:
|
192
|
+
perpendicular = -perpendicular
|
193
|
+
|
194
|
+
# Compute the positions for the segment with thickness, shifted by half-thickness along the perpendicular direction
|
195
|
+
p1 = np.array(location_new) + thickness / 2 * perpendicular
|
196
|
+
p2 = np.array(location_new) - thickness / 2 * perpendicular
|
197
|
+
|
198
|
+
# Check if both endpoints of the segment are inside the polygon
|
199
|
+
if is_inside_polygon(polygon['vertices'], p1) and is_inside_polygon(polygon['vertices'], p2):
|
200
|
+
return polygon_id, polygon, location_new, direction, perpendicular
|
201
|
+
|
202
|
+
attempt += 1
|
203
|
+
|
204
|
+
# If no valid location and direction is found, return False
|
205
|
+
return False
|
206
|
+
|
207
|
+
def get_polygons(polygon_id, polygon_arr, neighbor1_1, neighbor1_2, vertex_begin_1, vertex_end_1, neighbor2_1, neighbor2_2, vertex_begin_2, vertex_end_2, segment_new_id_1, segment_new_id_2):
|
208
|
+
# Extract vertices and cycle (faces) of the original polygon
|
209
|
+
vertices = polygon_arr[polygon_id]['vertices']
|
210
|
+
cycle = polygon_arr[polygon_id]['faces']
|
211
|
+
|
212
|
+
# Get first cycle and vertices
|
213
|
+
index_start_1, index_end_1 = (cycle.index(neighbor1_1), cycle.index(neighbor1_2))
|
214
|
+
if index_start_1 < index_end_1:
|
215
|
+
cycle1 = [segment_new_id_1] + cycle[index_start_1:index_end_1+1]
|
216
|
+
vertices1 = [vertex_begin_1] + vertices[index_start_1:index_end_1] + [vertex_end_1]
|
217
|
+
else:
|
218
|
+
cycle1 = [segment_new_id_1] + cycle[index_start_1:] + cycle[:index_end_1+1]
|
219
|
+
vertices1 = [vertex_begin_1] + vertices[index_start_1:] + vertices[:index_end_1] + [vertex_end_1]
|
220
|
+
|
221
|
+
# Get second cycle and vertices
|
222
|
+
index_start_2, index_end_2 = (cycle.index(neighbor2_2), cycle.index(neighbor2_1))
|
223
|
+
if index_start_2 < index_end_2:
|
224
|
+
cycle2 = [segment_new_id_2] + cycle[index_start_2:index_end_2+1]
|
225
|
+
vertices2 = [vertex_end_2] + vertices[index_start_2:index_end_2] + [vertex_begin_2]
|
226
|
+
else:
|
227
|
+
cycle2 = [segment_new_id_2] + cycle[index_start_2:] + cycle[:index_end_2+1]
|
228
|
+
vertices2 = [vertex_end_2] + vertices[index_start_2:] + vertices[:index_end_2] + [vertex_begin_2]
|
229
|
+
|
230
|
+
# Get middle cycle and vertices
|
231
|
+
cycle0 = [neighbor1_1, segment_new_id_1, neighbor1_2]
|
232
|
+
vertices0 = [vertex_begin_1, vertex_end_1]
|
233
|
+
|
234
|
+
index_start_0, index_end_0 = (cycle.index(neighbor1_2), cycle.index(neighbor2_2))
|
235
|
+
if index_start_0 < index_end_0:
|
236
|
+
cycle0 = cycle0 + cycle[index_start_0:index_end_0+1]
|
237
|
+
vertices0 = vertices0 + vertices[index_start_0:index_end_0]
|
238
|
+
|
239
|
+
elif index_start_0 > index_end_0:
|
240
|
+
cycle0 = cycle0 + cycle[index_start_0:] + cycle[:index_end_0+1]
|
241
|
+
vertices0 = vertices0 + vertices[index_start_0:] + vertices[:index_end_0]
|
242
|
+
|
243
|
+
cycle0 = cycle0 + [segment_new_id_2]
|
244
|
+
vertices0 = vertices0 + [vertex_end_2] + [vertex_begin_2]
|
245
|
+
|
246
|
+
index_start_0, index_end_0 = (cycle.index(neighbor2_1), cycle.index(neighbor1_1))
|
247
|
+
if index_start_0 < index_end_0:
|
248
|
+
cycle0 = cycle0 + cycle[index_start_0:index_end_0+1]
|
249
|
+
vertices0 = vertices0 + vertices[index_start_0:index_end_0]
|
250
|
+
|
251
|
+
elif index_start_0 > index_end_0:
|
252
|
+
cycle0 = cycle0 + cycle[index_start_0:] + cycle[:index_end_0+1]
|
253
|
+
vertices0 = vertices0 + vertices[index_start_0:] + vertices[:index_end_0]
|
254
|
+
|
255
|
+
return cycle0, vertices0, cycle1, vertices1, cycle2, vertices2
|
256
|
+
|
257
|
+
def get_new_segment(
|
258
|
+
line_segments_to_check: List['LineSegment'],
|
259
|
+
location: Tuple[float, float],
|
260
|
+
direction: Tuple[float, float],
|
261
|
+
id: Optional[int] = None
|
262
|
+
) -> 'LineSegment':
|
263
|
+
"""
|
264
|
+
Creates a new line segment by extending a given location in a specified direction and
|
265
|
+
determines its neighbors by checking intersections with other line segments.
|
266
|
+
|
267
|
+
Args:
|
268
|
+
line_segments_to_check (List[LineSegment]): List of existing line segments to check for intersections.
|
269
|
+
location (Tuple[float, float]): The starting point (x, y) for the new line segment.
|
270
|
+
direction (Tuple[float, float]): The direction vector in which to extend the line segment.
|
271
|
+
id (Optional[int]): Optional ID for the new line segment. If not provided, defaults to None.
|
272
|
+
|
273
|
+
Returns:
|
274
|
+
LineSegment: A new line segment object with its neighbors based on intersections.
|
275
|
+
"""
|
276
|
+
|
277
|
+
# Create a temporary line segment extending from the location in both directions
|
278
|
+
s_temp = LineSegment(start=np.array(location) - 10 * np.array(direction), end=np.array(location) + 10 * np.array(direction))
|
279
|
+
intersection_points: List[Dict[str, Tuple[float, float]]] = []
|
280
|
+
|
281
|
+
# Check for intersections with existing line segments
|
282
|
+
for segment in line_segments_to_check:
|
283
|
+
intersect, (intersect_x, intersect_y) = doSegmentsIntersect(s_temp, segment)
|
284
|
+
|
285
|
+
if intersect:
|
286
|
+
segment_length = math.sqrt(
|
287
|
+
(location[0] - intersect_x) ** 2
|
288
|
+
+ (location[1] - intersect_y) ** 2
|
289
|
+
)
|
290
|
+
intersection_points.append(
|
291
|
+
{"id": segment.id, "point": (intersect_x, intersect_y), "segment_length": segment_length}
|
292
|
+
)
|
293
|
+
|
294
|
+
# Divide intersections into ones behind and in front of the new line
|
295
|
+
intersections_b = [intersection for intersection in intersection_points if intersection["point"][0] < location[0]]
|
296
|
+
intersections_f = [intersection for intersection in intersection_points if intersection["point"][0] > location[0]]
|
297
|
+
|
298
|
+
if not intersections_b or not intersections_f:
|
299
|
+
intersections_b = [intersection for intersection in intersection_points if intersection["point"][1] < location[1]]
|
300
|
+
intersections_f = [intersection for intersection in intersection_points if intersection["point"][1] > location[1]]
|
301
|
+
|
302
|
+
# Determine the closest intersections for segment start and end
|
303
|
+
s_start = min(intersections_b, key=lambda x: x["segment_length"])
|
304
|
+
s_end = min(intersections_f, key=lambda x: x["segment_length"])
|
305
|
+
start, end = s_start['point'], s_end['point']
|
306
|
+
start_id, end_id = s_start['id'], s_end['id']
|
307
|
+
|
308
|
+
# Ensure the start comes before the end
|
309
|
+
if start[0] > end[0]:
|
310
|
+
start, end = end, start
|
311
|
+
start_id, end_id = end_id, start_id
|
312
|
+
|
313
|
+
# Create a new line segment and assign neighbors
|
314
|
+
neighbors_initial = {start_id: start, end_id: end}
|
315
|
+
segment_new = LineSegment(start=start, end=end, id=id, neighbors_initial=neighbors_initial, neighbors=neighbors_initial)
|
316
|
+
|
317
|
+
return segment_new
|
318
|
+
|
319
|
+
def update_data(
|
320
|
+
segments_dict: Dict[int, 'LineSegment'],
|
321
|
+
polygon_arr: Dict[str, Dict[str, object]],
|
322
|
+
polygon_id: str,
|
323
|
+
segment_thickness_dict: Dict[int, 'Polygon'],
|
324
|
+
vertices0: List[Tuple[float, float]],
|
325
|
+
vertices1: List[Tuple[float, float]],
|
326
|
+
vertices2: List[Tuple[float, float]],
|
327
|
+
cycle0: List[int],
|
328
|
+
cycle1: List[int],
|
329
|
+
cycle2: List[int],
|
330
|
+
neighbor1_1: int,
|
331
|
+
neighbor1_2: int,
|
332
|
+
neighbor2_1: int,
|
333
|
+
neighbor2_2: int,
|
334
|
+
vertex_begin_1: Tuple[float, float],
|
335
|
+
vertex_end_1: Tuple[float, float],
|
336
|
+
vertex_begin_2: Tuple[float, float],
|
337
|
+
vertex_end_2: Tuple[float, float],
|
338
|
+
id_1: int,
|
339
|
+
id_2: int
|
340
|
+
) -> Tuple[Dict[int, 'LineSegment'], Dict[str, Dict[str, object]], Dict[int, 'Polygon']]:
|
341
|
+
"""
|
342
|
+
Updates the segments, polygons, and segment thickness dictionaries by adding new data derived
|
343
|
+
from provided vertices and neighbor information.
|
344
|
+
|
345
|
+
Args:
|
346
|
+
segments_dict (Dict[int, LineSegment]): A dictionary of segments with segment ID as the key.
|
347
|
+
polygon_arr (Dict[str, Dict[str, object]]): A dictionary of polygons with polygon ID as the key.
|
348
|
+
polygon_id (str): The ID of the polygon being updated.
|
349
|
+
segment_thickness_dict (Dict[int, Polygon]): A dictionary mapping thickness information to polygon objects.
|
350
|
+
vertices0 (List[Tuple[float, float]]): Vertices of the base polygon.
|
351
|
+
vertices1 (List[Tuple[float, float]]): Vertices of the first new polygon.
|
352
|
+
vertices2 (List[Tuple[float, float]]): Vertices of the second new polygon.
|
353
|
+
cycle0 (List[int]): List of face indices for the base polygon.
|
354
|
+
cycle1 (List[int]): List of face indices for the first new polygon.
|
355
|
+
cycle2 (List[int]): List of face indices for the second new polygon.
|
356
|
+
neighbor1_1 (int): ID of the first neighbor of the first segment.
|
357
|
+
neighbor1_2 (int): ID of the second neighbor of the first segment.
|
358
|
+
neighbor2_1 (int): ID of the first neighbor of the second segment.
|
359
|
+
neighbor2_2 (int): ID of the second neighbor of the second segment.
|
360
|
+
vertex_begin_1 (Tuple[float, float]): Starting vertex of the first segment.
|
361
|
+
vertex_end_1 (Tuple[float, float]): Ending vertex of the first segment.
|
362
|
+
vertex_begin_2 (Tuple[float, float]): Starting vertex of the second segment.
|
363
|
+
vertex_end_2 (Tuple[float, float]): Ending vertex of the second segment.
|
364
|
+
id_1 (int): ID of the first new segment.
|
365
|
+
id_2 (int): ID of the second new segment.
|
366
|
+
|
367
|
+
Returns:
|
368
|
+
Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
|
369
|
+
- Updated dictionary of line segments.
|
370
|
+
- Updated dictionary of polygons.
|
371
|
+
- Updated dictionary of segment thickness.
|
372
|
+
"""
|
373
|
+
|
374
|
+
# Update polygon_arr (a dictionary of polygons)
|
375
|
+
polygon_new_1 = {
|
376
|
+
f'p{len(polygon_arr) + 1}': {
|
377
|
+
'vertices': vertices1,
|
378
|
+
'area': Polygon(vertices=vertices1).area(),
|
379
|
+
'faces': cycle1
|
380
|
+
}
|
381
|
+
}
|
382
|
+
polygon_new_2 = {
|
383
|
+
polygon_id: {
|
384
|
+
'vertices': vertices2,
|
385
|
+
'area': Polygon(vertices=vertices2).area(),
|
386
|
+
'faces': cycle2
|
387
|
+
}
|
388
|
+
}
|
389
|
+
polygon_arr.update(polygon_new_1)
|
390
|
+
polygon_arr.update(polygon_new_2)
|
391
|
+
|
392
|
+
# Update the segments_dict for the first segment
|
393
|
+
neighbors_initial_1 = {
|
394
|
+
neighbor1_1: vertex_begin_1,
|
395
|
+
neighbor1_2: vertex_end_1
|
396
|
+
}
|
397
|
+
segment_new_1 = LineSegment(
|
398
|
+
start=vertex_begin_1,
|
399
|
+
end=vertex_end_1,
|
400
|
+
id=id_1,
|
401
|
+
neighbors_initial=neighbors_initial_1,
|
402
|
+
neighbors=neighbors_initial_1
|
403
|
+
)
|
404
|
+
segments_dict[segment_new_1.id] = segment_new_1
|
405
|
+
segments_dict[neighbor1_1].neighbors[id_1] = vertex_begin_1
|
406
|
+
segments_dict[neighbor1_2].neighbors[id_1] = vertex_end_1
|
407
|
+
|
408
|
+
# Update the segments_dict for the second segment
|
409
|
+
neighbors_initial_2 = {
|
410
|
+
neighbor2_1: vertex_begin_2,
|
411
|
+
neighbor2_2: vertex_end_2
|
412
|
+
}
|
413
|
+
segment_new_2 = LineSegment(
|
414
|
+
start=vertex_begin_2,
|
415
|
+
end=vertex_end_2,
|
416
|
+
id=id_2,
|
417
|
+
neighbors_initial=neighbors_initial_2,
|
418
|
+
neighbors=neighbors_initial_2
|
419
|
+
)
|
420
|
+
segments_dict[segment_new_2.id] = segment_new_2
|
421
|
+
segments_dict[neighbor2_1].neighbors[id_2] = vertex_begin_2
|
422
|
+
segments_dict[neighbor2_2].neighbors[id_2] = vertex_end_2
|
423
|
+
|
424
|
+
# Update the segment_thickness_dict with the base polygon
|
425
|
+
segment_thickness_dict[len(segment_thickness_dict) + 1] = Polygon(vertices=vertices0)
|
426
|
+
|
427
|
+
return segments_dict, polygon_arr, segment_thickness_dict
|
428
|
+
|
429
|
+
def add_line_segment(
|
430
|
+
segments_dict: Dict[int, 'LineSegment'],
|
431
|
+
polygon_arr: Dict[str, Dict[str, object]],
|
432
|
+
segment_thickness_dict: Dict[int, 'Polygon'],
|
433
|
+
thickness: float = 0,
|
434
|
+
angles: str = 'uniform'
|
435
|
+
) -> Union[Tuple[Dict[int, 'LineSegment'], Dict[str, Dict[str, object]], Dict[int, 'Polygon']], bool]:
|
436
|
+
"""
|
437
|
+
Adds a new line segment to the segments and polygon data structures, with a given thickness and angle distribution.
|
438
|
+
|
439
|
+
Args:
|
440
|
+
segments_dict (Dict[int, LineSegment]): A dictionary containing the current line segments.
|
441
|
+
polygon_arr (Dict[str, Dict[str, object]]): A dictionary containing the current polygons and their properties.
|
442
|
+
segment_thickness_dict (Dict[int, Polygon]): A dictionary storing the thickness information mapped to polygons.
|
443
|
+
thickness (float): The thickness of the new segment to be added. Defaults to 0.
|
444
|
+
angles (str): The angle distribution method. Defaults to 'uniform'.
|
445
|
+
|
446
|
+
Returns:
|
447
|
+
Union[Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]], bool]:
|
448
|
+
- A tuple containing the updated segments dictionary, polygon dictionary, and thickness dictionary,
|
449
|
+
or False if no valid location for the new segment is found.
|
450
|
+
"""
|
451
|
+
|
452
|
+
# Get a valid location and direction, or return False if none is found
|
453
|
+
loc = get_location_and_direction(polygon_arr, thickness, max_attempts=1000, angles=angles)
|
454
|
+
if loc:
|
455
|
+
polygon_id, polygon, location_new, direction_new, perpendicular = loc
|
456
|
+
else:
|
457
|
+
print('No valid location found')
|
458
|
+
return False
|
459
|
+
|
460
|
+
# Get the borders of the new segment with the given thickness
|
461
|
+
line_segments_to_check = [segments_dict[segment] for segment in polygon['faces']]
|
462
|
+
middle_segment = get_new_segment(line_segments_to_check, location=location_new, direction=direction_new)
|
463
|
+
s1 = get_new_segment(line_segments_to_check, location=np.array(location_new) + thickness * perpendicular / 2, direction=direction_new)
|
464
|
+
s2 = get_new_segment(line_segments_to_check, location=np.array(location_new) - thickness * perpendicular / 2, direction=direction_new)
|
465
|
+
|
466
|
+
# Extract neighbor information and segment vertices
|
467
|
+
neighbor1_1, neighbor1_2 = list(s1.neighbors.keys())
|
468
|
+
vertex_begin_1, vertex_end_1 = list(s1.neighbors.values())
|
469
|
+
neighbor2_1, neighbor2_2 = list(s2.neighbors.keys())
|
470
|
+
vertex_begin_2, vertex_end_2 = list(s2.neighbors.values())
|
471
|
+
id_1 = str(int((len(segments_dict.keys()) - 2) / 2)) + '_1'
|
472
|
+
id_2 = str(int((len(segments_dict.keys()) - 2) / 2)) + '_2'
|
473
|
+
|
474
|
+
# Get the resulting polygons after splitting
|
475
|
+
cycle0, vertices0, cycle1, vertices1, cycle2, vertices2 = get_polygons(
|
476
|
+
polygon_id,
|
477
|
+
polygon_arr,
|
478
|
+
neighbor1_1,
|
479
|
+
neighbor1_2,
|
480
|
+
vertex_begin_1,
|
481
|
+
vertex_end_1,
|
482
|
+
neighbor2_1,
|
483
|
+
neighbor2_2,
|
484
|
+
vertex_begin_2=vertex_begin_2,
|
485
|
+
vertex_end_2=vertex_end_2,
|
486
|
+
segment_new_id_1=id_1,
|
487
|
+
segment_new_id_2=id_2
|
488
|
+
)
|
489
|
+
|
490
|
+
# Update all relevant data structures
|
491
|
+
segments_dict, polygon_arr, segment_thickness_dict = update_data(
|
492
|
+
segments_dict,
|
493
|
+
polygon_arr,
|
494
|
+
polygon_id,
|
495
|
+
segment_thickness_dict,
|
496
|
+
vertices0,
|
497
|
+
vertices1,
|
498
|
+
vertices2,
|
499
|
+
cycle0,
|
500
|
+
cycle1,
|
501
|
+
cycle2,
|
502
|
+
neighbor1_1,
|
503
|
+
neighbor1_2,
|
504
|
+
neighbor2_1,
|
505
|
+
neighbor2_2,
|
506
|
+
vertex_begin_1,
|
507
|
+
vertex_end_1,
|
508
|
+
vertex_begin_2,
|
509
|
+
vertex_end_2,
|
510
|
+
id_1,
|
511
|
+
id_2
|
512
|
+
)
|
513
|
+
|
514
|
+
# Associate the middle segment with the newly created thickness entry
|
515
|
+
segment_thickness_dict[list(segment_thickness_dict.keys())[-1]].middle_segment = middle_segment
|
516
|
+
|
517
|
+
return segments_dict, polygon_arr, segment_thickness_dict
|
518
|
+
|
519
|
+
def generate_line_segments_dynamic_thickness(
|
520
|
+
size: int,
|
521
|
+
thickness_arr: List[float],
|
522
|
+
angles: str = 'uniform'
|
523
|
+
) -> Tuple[Dict[str, 'LineSegment'], Dict[str, Dict[str, object]], Dict[int, 'Polygon']]:
|
524
|
+
"""
|
525
|
+
Generates a specified number of line segments and updates the polygon and segment thickness dictionaries.
|
526
|
+
|
527
|
+
Args:
|
528
|
+
size (int): The number of line segments to generate.
|
529
|
+
thickness_arr (List[float]): A list containing the thickness values for each segment to be generated.
|
530
|
+
angles (str): The angle distribution method for generating segments. Defaults to 'uniform'.
|
531
|
+
|
532
|
+
Returns:
|
533
|
+
Tuple[Dict[str, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
|
534
|
+
- Updated dictionary of line segments.
|
535
|
+
- Updated dictionary of polygons.
|
536
|
+
- Updated dictionary of segment thicknesses.
|
537
|
+
"""
|
538
|
+
|
539
|
+
# Initialize border segments for a square and its polygon representation
|
540
|
+
borders = [
|
541
|
+
LineSegment((1, 0), (0, 0), id='b1', neighbors_initial={'b2': (0, 0), 'b4': (1, 0)}, neighbors={'b2': (0, 0), 'b4': (1, 0)}),
|
542
|
+
LineSegment((0, 1), (0, 0), id='b2', neighbors_initial={'b1': (0, 0), 'b3': (0, 1)}, neighbors={'b1': (0, 0), 'b3': (0, 1)}),
|
543
|
+
LineSegment((0, 1), (1, 1), id='b3', neighbors_initial={'b2': (0, 1), 'b4': (1, 1)}, neighbors={'b2': (0, 1), 'b4': (1, 1)}),
|
544
|
+
LineSegment((1, 1), (1, 0), id='b4', neighbors_initial={'b1': (1, 0), 'b3': (1, 1)}, neighbors={'b1': (1, 0), 'b3': (1, 1)})
|
545
|
+
]
|
546
|
+
|
547
|
+
polygon_arr = {
|
548
|
+
'p1': {
|
549
|
+
'vertices': [(0, 0), (0, 1), (1, 1), (1, 0)],
|
550
|
+
'area': 1,
|
551
|
+
'faces': ['b1', 'b2', 'b3', 'b4']
|
552
|
+
}
|
553
|
+
}
|
554
|
+
|
555
|
+
segments = borders
|
556
|
+
segments_dict = {segment.id: segment for segment in segments}
|
557
|
+
segment_thickness_dict = {}
|
558
|
+
|
559
|
+
# Generate new line segments based on the given size and thickness array
|
560
|
+
for i in range(size):
|
561
|
+
output = add_line_segment(segments_dict, polygon_arr, segment_thickness_dict, thickness=thickness_arr[i], angles=angles)
|
562
|
+
if output:
|
563
|
+
segments_dict, polygon_arr, segment_thickness_dict = output
|
564
|
+
else:
|
565
|
+
print(f"Stopped at iteration {i}, could not find a valid segment position.")
|
566
|
+
break
|
567
|
+
|
568
|
+
# Uncomment the following line if you want progress feedback
|
569
|
+
percentage = np.round(i / size * 100, 3)
|
570
|
+
print(f'generate_segments: {percentage}% done', end='\r')
|
571
|
+
|
572
|
+
return segments_dict, polygon_arr, segment_thickness_dict
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from shapely.geometry import Polygon, Point
|
3
|
+
|
4
|
+
def sort_vertices(vertices):
|
5
|
+
"""
|
6
|
+
Sorts the vertices of the polygon based on their polar angles with respect to a reference point.
|
7
|
+
|
8
|
+
Returns:
|
9
|
+
List[Tuple[float, float]]: The sorted list of vertices.
|
10
|
+
"""
|
11
|
+
def polar_angle(point, reference_point) -> float:
|
12
|
+
"""
|
13
|
+
Calculates the polar angle of a point with respect to a reference point.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
point (Tuple[float, float]): The coordinates (x, y) of the point for which to calculate the polar angle.
|
17
|
+
reference_point (Tuple[float, float]): The coordinates (x, y) of the reference point.
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
float: The polar angle in radians.
|
21
|
+
"""
|
22
|
+
dx = point[0] - reference_point[0]
|
23
|
+
dy = point[1] - reference_point[1]
|
24
|
+
return np.arctan2(dy, dx)
|
25
|
+
|
26
|
+
reference_point = min(vertices, key=lambda point: point[1])
|
27
|
+
return sorted(vertices, key=lambda point: polar_angle(point, reference_point))
|
28
|
+
|
29
|
+
def sample_in_polygon(vertices):
|
30
|
+
|
31
|
+
vertices = sort_vertices(vertices)
|
32
|
+
|
33
|
+
# Create a Shapely polygon from the given vertices
|
34
|
+
polygon = Polygon(vertices)
|
35
|
+
|
36
|
+
# Find the bounding box of the polygon
|
37
|
+
min_x, min_y, max_x, max_y = polygon.bounds
|
38
|
+
|
39
|
+
# Generate random points within the bounding box
|
40
|
+
while True:
|
41
|
+
random_point = Point(np.random.uniform(min_x, max_x), np.random.uniform(min_y, max_y))
|
42
|
+
|
43
|
+
# Check if the random point is inside the polygon
|
44
|
+
if polygon.contains(random_point):
|
45
|
+
return random_point.x, random_point.y
|
46
|
+
|
47
|
+
def is_inside_polygon(vertices, point):
|
48
|
+
vertices = sort_vertices(vertices)
|
49
|
+
polygon = Polygon(vertices)
|
50
|
+
point = Point(point[0], point[1])
|
51
|
+
if polygon.contains(point):
|
52
|
+
return True
|
53
|
+
else:
|
54
|
+
return False
|
@@ -1,15 +0,0 @@
|
|
1
|
-
RDG_networks/Classes.py,sha256=_9X3JPHFAYYlaC8IZ_H9__sfz99G5l9UfPl65lL60_4,7977
|
2
|
-
RDG_networks/__init__.py,sha256=aCfGs87cJn0Tp5GqTpFXPOEfJPpR4Tnnz819Kk_0t84,661
|
3
|
-
RDG_networks/draw_segments.py,sha256=U53N5GXmQHWKdM1Q1faP_EGKjc6enOu2mcsunzSFpP0,984
|
4
|
-
RDG_networks/generate_line_network.py,sha256=lJ4rhObim3WcEQoebomewRQKWNJC5phFyFYRW7qjXIg,1127
|
5
|
-
RDG_networks/generate_line_segments.py,sha256=QV8_k7q6TD5c7Hcb2Ms_apEdWYw4XdLr7rdJgh49v4Q,9004
|
6
|
-
RDG_networks/generate_line_segments_dynamic.py,sha256=GoIhGXYbcvjqR5BJCnkvAGp8QBpzsE1ZSbl2k9XAOGI,7531
|
7
|
-
RDG_networks/generate_line_segments_static.py,sha256=7KvHZi3krv-tAGydJR_gbMMmHKZ5azzrKcQe3fuWzCE,9265
|
8
|
-
RDG_networks/get_intersection_segments.py,sha256=mXB5qCy1oOps4Vu1mX6flW6v_4Xxc71YK41yOWjJX8o,2797
|
9
|
-
RDG_networks/sample_in_polygon.py,sha256=qpPpW-Da1vK8ZkVWMJ0zBsE8IgyMB619gCdybSkzKSQ,1605
|
10
|
-
RDG_Networks-0.2.5.dist-info/LICENSE.txt,sha256=BHUkX2GsdTr30sKmVZ1MLGR1njnx17EX_oUuuSVZZPE,598
|
11
|
-
RDG_Networks-0.2.5.dist-info/METADATA,sha256=df8uZ3On7aPNlvd59IvUkC2a0IvXktdRO6aaYLa4q0M,2422
|
12
|
-
RDG_Networks-0.2.5.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
|
13
|
-
RDG_Networks-0.2.5.dist-info/entry_points.txt,sha256=Htsh9jRabMCGHwXayUwZoSy-9IFGFrMB1X9hgyyr3_8,350
|
14
|
-
RDG_Networks-0.2.5.dist-info/top_level.txt,sha256=4gUUYafD5Al9V8ZSiViVGYHpRMMCsCBcGgCNodk9Syg,13
|
15
|
-
RDG_Networks-0.2.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|