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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,,