RDG-Networks 0.2.6__py3-none-any.whl → 0.3.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: RDG-Networks
3
- Version: 0.2.6
3
+ Version: 0.3.1
4
4
  Summary: Most of the code from the RDG Networks project
5
5
  Home-page: https://github.com/NiekMooij/RDG_networks
6
6
  Author: Niek Mooij
@@ -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=ixi8dBaMi-M_kCdxGfx8V0o_vOTv_anpAgLnPaBwysc,190
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.1.dist-info/LICENSE.txt,sha256=BHUkX2GsdTr30sKmVZ1MLGR1njnx17EX_oUuuSVZZPE,598
16
+ RDG_Networks-0.3.1.dist-info/METADATA,sha256=UD1uvMqUKZzK6p-v0KdyWB_pQUvUcHR4nkuYV3AxAPU,2422
17
+ RDG_Networks-0.3.1.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
18
+ RDG_Networks-0.3.1.dist-info/entry_points.txt,sha256=6AGpOd8ecHGEsoUhRj0j6VvN_Fg0QNCY8h5rInAJom0,542
19
+ RDG_Networks-0.3.1.dist-info/top_level.txt,sha256=4gUUYafD5Al9V8ZSiViVGYHpRMMCsCBcGgCNodk9Syg,13
20
+ RDG_Networks-0.3.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.3)
2
+ Generator: setuptools (74.1.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -3,5 +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
6
7
  generate_line_segments_static = RDG_networks.generate_line_segments_static:main
7
8
  get_intersection_segments = RDG_networks.get_intersection_segments:main
RDG_networks/__init__.py CHANGED
@@ -7,12 +7,14 @@ from .get_intersection_segments import get_intersection_segments
7
7
  from .generate_line_segments_dynamic import generate_line_segments_dynamic
8
8
  from .generate_line_segments_static import generate_line_segments_static
9
9
  from .draw_segments import draw_segments
10
+ from .thickness.generate_line_segments_dynamic_thickness import generate_line_segments_thickness
10
11
 
11
12
  __all__ = ['generate_line_segments',
12
13
  'generate_line_network',
13
14
  'get_intersection_segments',
14
15
  'generate_line_segments_dynamic',
15
16
  'generate_line_segments_static',
17
+ 'generate_line_segments_thickness',
16
18
  'draw_segments',
17
19
  'sample_in_polygon',
18
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,7 @@
1
+ # __init__.py
2
+
3
+ from .generate_line_segments_dynamic_thickness import generate_line_segments_dynamic_thickness
4
+
5
+ __all__ = [
6
+ 'generate_line_segments_dynamic_thickness',
7
+ ]
@@ -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=f2xUp3Sz3zE222hg62d_cg9mOHDljLp84I0-YcFpgXQ,778
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.6.dist-info/LICENSE.txt,sha256=BHUkX2GsdTr30sKmVZ1MLGR1njnx17EX_oUuuSVZZPE,598
11
- RDG_Networks-0.2.6.dist-info/METADATA,sha256=083T7YyDyXEnsUZqXfaenPNVF2TlD2dpgMYSX3Tkkrs,2422
12
- RDG_Networks-0.2.6.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
13
- RDG_Networks-0.2.6.dist-info/entry_points.txt,sha256=amlCKXKGWk7DfHR7PNc14dLfR2DtUVYczd4FHYmDpCg,430
14
- RDG_Networks-0.2.6.dist-info/top_level.txt,sha256=4gUUYafD5Al9V8ZSiViVGYHpRMMCsCBcGgCNodk9Syg,13
15
- RDG_Networks-0.2.6.dist-info/RECORD,,