geokdtree.core
1from operator import itemgetter 2 3_GETTERS = [itemgetter(i) for i in range(16)] 4 5 6def kdtree(points, depth, axis_count): 7 """ 8 Function: 9 10 - Build a KDTree from a list of points. 11 12 Required Arguments: 13 14 - `points` 15 - Type: list of tuples 16 - What: A list of points to build the KDTree from 17 18 Optional Arguments: 19 20 - `depth` 21 - Type: int 22 - What: The current depth in the tree (used for axis selection) 23 - `axis_count` 24 - Type: int 25 - What: The number of dimensions in the points (default is 2 for 2D points) 26 27 Returns: 28 29 - The constructed KDTree as a tuple in the format (point, axis, left, right). 30 - Where left and right are subtrees. 31 """ 32 if not points: 33 return 0 34 axis = depth % axis_count 35 getter = _GETTERS[axis] if axis < 16 else itemgetter(axis) 36 points.sort(key=getter) 37 median = len(points) // 2 38 return ( 39 points[median], 40 axis, 41 kdtree(points=points[:median], depth=depth + 1, axis_count=axis_count), 42 kdtree( 43 points=points[median + 1 :], depth=depth + 1, axis_count=axis_count 44 ), 45 ) 46 47 48def squared_distance(p1, p2, axis_count=2): 49 """ 50 Function: 51 52 - Calculate the squared distance between two points. 53 54 Required Arguments: 55 56 - `p1` 57 - Type: tuple 58 - What: The first point 59 - `p2` 60 - Type: tuple 61 - What: The second point 62 63 Optional Arguments: 64 65 - `axis_count` 66 - Type: int 67 - What: The number of dimensions in the points 68 - Default: 2 (for 2D points) 69 70 Returns: 71 72 - The squared distance between the two points. 73 """ 74 if axis_count == 2: 75 dx = p1[0] - p2[0] 76 dy = p1[1] - p2[1] 77 return dx * dx + dy * dy 78 elif axis_count == 3: 79 dx = p1[0] - p2[0] 80 dy = p1[1] - p2[1] 81 dz = p1[2] - p2[2] 82 return dx * dx + dy * dy + dz * dz 83 return sum((p1[i] - p2[i]) ** 2 for i in range(axis_count)) 84 85 86def closest_point(node, point, best=None, axis_count=2, best_dist=float("inf")): 87 """ 88 Function: 89 90 - Find the closest point in the KDTree to a given point. 91 92 Required Arguments: 93 94 - `node` 95 - Type: tuple 96 - What: The node of the KDTree 97 - `point` 98 - Type: tuple 99 - What: The point to find the closest point to 100 101 Optional Arguments: 102 103 - `best` 104 - Type: tuple or None 105 - What: The best point found so far (default is None) 106 - `axis_count` 107 - Type: int 108 - What: The number of dimensions in the points (default is 2 for 2D points) 109 - `best_dist` 110 - Type: float 111 - What: The best distance found so far (default is infinity) 112 113 Returns: 114 115 - The closest point found in the KDTree to the given point. 116 """ 117 if node == 0: 118 return best, best_dist 119 # Get the median node and its distance 120 median_node = node[0] 121 median_node_dist = squared_distance( 122 point, median_node, axis_count=axis_count 123 ) 124 # Update the best point and distance if necessary 125 if best is None or median_node_dist < best_dist: 126 best = median_node 127 best_dist = median_node_dist 128 # Calculate the difference for node selection given the current axis 129 axis = node[1] 130 diff = point[axis] - median_node[axis] 131 # Choose side to search 132 close, away = (node[2], node[3]) if diff < 0 else (node[3], node[2]) 133 # Search the close side first 134 best, best_dist = closest_point( 135 close, 136 point, 137 best, 138 axis_count=axis_count, 139 best_dist=best_dist, 140 ) 141 # Check the other side if needed 142 if diff**2 < best_dist: 143 best, best_dist = closest_point( 144 away, 145 point, 146 best, 147 axis_count=axis_count, 148 best_dist=best_dist, 149 ) 150 return best, best_dist 151 152 153def closest_point_per_quadrant_2d(node, point): 154 """ 155 Function: 156 157 - Find the closest point in each of the 4 quadrants (ne, nw, se, sw) 158 for 2D Cartesian points. 159 160 Required Arguments: 161 162 - `node` 163 - Type: tuple 164 - What: The root node of the KDTree 165 - `point` 166 - Type: tuple 167 - What: The 2D query point (x, y) 168 169 Returns: 170 171 - A dictionary mapping 'ne', 'nw', 'se', 'sw' to their closest point tuple or None. 172 """ 173 bests = [None, None, None, None] 174 best_dists = [float("inf"), float("inf"), float("inf"), float("inf")] 175 176 px, py = point[0], point[1] 177 178 def _search(curr, active_mask): 179 if curr == 0 or curr is None or active_mask == 0: 180 return 181 182 median_node = curr[0] 183 mx, my = median_node[0], median_node[1] 184 dist = (px - mx) * (px - mx) + (py - my) * (py - my) 185 186 is_east = mx >= px 187 is_west = mx <= px 188 is_north = my >= py 189 is_south = my <= py 190 191 if is_north and is_east and (active_mask & 1): 192 if dist < best_dists[0]: 193 bests[0] = median_node 194 best_dists[0] = dist 195 196 if is_north and is_west and (active_mask & 2): 197 if dist < best_dists[1]: 198 bests[1] = median_node 199 best_dists[1] = dist 200 201 if is_south and is_east and (active_mask & 4): 202 if dist < best_dists[2]: 203 bests[2] = median_node 204 best_dists[2] = dist 205 206 if is_south and is_west and (active_mask & 8): 207 if dist < best_dists[3]: 208 bests[3] = median_node 209 best_dists[3] = dist 210 211 axis = curr[1] 212 diff = point[axis] - median_node[axis] 213 214 if diff < 0: 215 close, away = curr[2], curr[3] 216 close_mask = active_mask 217 away_mask = ( 218 active_mask & (1 | 4) if axis == 0 else active_mask & (1 | 2) 219 ) 220 else: 221 close, away = curr[3], curr[2] 222 close_mask = active_mask 223 away_mask = ( 224 active_mask & (2 | 8) if axis == 0 else active_mask & (4 | 8) 225 ) 226 227 _search(close, close_mask) 228 229 if away_mask: 230 diff_sq = diff * diff 231 max_away = 0.0 232 if (away_mask & 1) and best_dists[0] > max_away: 233 max_away = best_dists[0] 234 if (away_mask & 2) and best_dists[1] > max_away: 235 max_away = best_dists[1] 236 if (away_mask & 4) and best_dists[2] > max_away: 237 max_away = best_dists[2] 238 if (away_mask & 8) and best_dists[3] > max_away: 239 max_away = best_dists[3] 240 241 if diff_sq < max_away: 242 _search(away, away_mask) 243 244 _search(node, 15) 245 return {"ne": bests[0], "nw": bests[1], "se": bests[2], "sw": bests[3]}
def
kdtree(points, depth, axis_count):
7def kdtree(points, depth, axis_count): 8 """ 9 Function: 10 11 - Build a KDTree from a list of points. 12 13 Required Arguments: 14 15 - `points` 16 - Type: list of tuples 17 - What: A list of points to build the KDTree from 18 19 Optional Arguments: 20 21 - `depth` 22 - Type: int 23 - What: The current depth in the tree (used for axis selection) 24 - `axis_count` 25 - Type: int 26 - What: The number of dimensions in the points (default is 2 for 2D points) 27 28 Returns: 29 30 - The constructed KDTree as a tuple in the format (point, axis, left, right). 31 - Where left and right are subtrees. 32 """ 33 if not points: 34 return 0 35 axis = depth % axis_count 36 getter = _GETTERS[axis] if axis < 16 else itemgetter(axis) 37 points.sort(key=getter) 38 median = len(points) // 2 39 return ( 40 points[median], 41 axis, 42 kdtree(points=points[:median], depth=depth + 1, axis_count=axis_count), 43 kdtree( 44 points=points[median + 1 :], depth=depth + 1, axis_count=axis_count 45 ), 46 )
Function:
- Build a KDTree from a list of points.
Required Arguments:
points- Type: list of tuples
- What: A list of points to build the KDTree from
Optional Arguments:
depth- Type: int
- What: The current depth in the tree (used for axis selection)
axis_count- Type: int
- What: The number of dimensions in the points (default is 2 for 2D points)
Returns:
- The constructed KDTree as a tuple in the format (point, axis, left, right).
- Where left and right are subtrees.
def
squared_distance(p1, p2, axis_count=2):
49def squared_distance(p1, p2, axis_count=2): 50 """ 51 Function: 52 53 - Calculate the squared distance between two points. 54 55 Required Arguments: 56 57 - `p1` 58 - Type: tuple 59 - What: The first point 60 - `p2` 61 - Type: tuple 62 - What: The second point 63 64 Optional Arguments: 65 66 - `axis_count` 67 - Type: int 68 - What: The number of dimensions in the points 69 - Default: 2 (for 2D points) 70 71 Returns: 72 73 - The squared distance between the two points. 74 """ 75 if axis_count == 2: 76 dx = p1[0] - p2[0] 77 dy = p1[1] - p2[1] 78 return dx * dx + dy * dy 79 elif axis_count == 3: 80 dx = p1[0] - p2[0] 81 dy = p1[1] - p2[1] 82 dz = p1[2] - p2[2] 83 return dx * dx + dy * dy + dz * dz 84 return sum((p1[i] - p2[i]) ** 2 for i in range(axis_count))
Function:
- Calculate the squared distance between two points.
Required Arguments:
p1- Type: tuple
- What: The first point
p2- Type: tuple
- What: The second point
Optional Arguments:
axis_count- Type: int
- What: The number of dimensions in the points
- Default: 2 (for 2D points)
Returns:
- The squared distance between the two points.
def
closest_point(node, point, best=None, axis_count=2, best_dist=inf):
87def closest_point(node, point, best=None, axis_count=2, best_dist=float("inf")): 88 """ 89 Function: 90 91 - Find the closest point in the KDTree to a given point. 92 93 Required Arguments: 94 95 - `node` 96 - Type: tuple 97 - What: The node of the KDTree 98 - `point` 99 - Type: tuple 100 - What: The point to find the closest point to 101 102 Optional Arguments: 103 104 - `best` 105 - Type: tuple or None 106 - What: The best point found so far (default is None) 107 - `axis_count` 108 - Type: int 109 - What: The number of dimensions in the points (default is 2 for 2D points) 110 - `best_dist` 111 - Type: float 112 - What: The best distance found so far (default is infinity) 113 114 Returns: 115 116 - The closest point found in the KDTree to the given point. 117 """ 118 if node == 0: 119 return best, best_dist 120 # Get the median node and its distance 121 median_node = node[0] 122 median_node_dist = squared_distance( 123 point, median_node, axis_count=axis_count 124 ) 125 # Update the best point and distance if necessary 126 if best is None or median_node_dist < best_dist: 127 best = median_node 128 best_dist = median_node_dist 129 # Calculate the difference for node selection given the current axis 130 axis = node[1] 131 diff = point[axis] - median_node[axis] 132 # Choose side to search 133 close, away = (node[2], node[3]) if diff < 0 else (node[3], node[2]) 134 # Search the close side first 135 best, best_dist = closest_point( 136 close, 137 point, 138 best, 139 axis_count=axis_count, 140 best_dist=best_dist, 141 ) 142 # Check the other side if needed 143 if diff**2 < best_dist: 144 best, best_dist = closest_point( 145 away, 146 point, 147 best, 148 axis_count=axis_count, 149 best_dist=best_dist, 150 ) 151 return best, best_dist
Function:
- Find the closest point in the KDTree to a given point.
Required Arguments:
node- Type: tuple
- What: The node of the KDTree
point- Type: tuple
- What: The point to find the closest point to
Optional Arguments:
best- Type: tuple or None
- What: The best point found so far (default is None)
axis_count- Type: int
- What: The number of dimensions in the points (default is 2 for 2D points)
best_dist- Type: float
- What: The best distance found so far (default is infinity)
Returns:
- The closest point found in the KDTree to the given point.
def
closest_point_per_quadrant_2d(node, point):
154def closest_point_per_quadrant_2d(node, point): 155 """ 156 Function: 157 158 - Find the closest point in each of the 4 quadrants (ne, nw, se, sw) 159 for 2D Cartesian points. 160 161 Required Arguments: 162 163 - `node` 164 - Type: tuple 165 - What: The root node of the KDTree 166 - `point` 167 - Type: tuple 168 - What: The 2D query point (x, y) 169 170 Returns: 171 172 - A dictionary mapping 'ne', 'nw', 'se', 'sw' to their closest point tuple or None. 173 """ 174 bests = [None, None, None, None] 175 best_dists = [float("inf"), float("inf"), float("inf"), float("inf")] 176 177 px, py = point[0], point[1] 178 179 def _search(curr, active_mask): 180 if curr == 0 or curr is None or active_mask == 0: 181 return 182 183 median_node = curr[0] 184 mx, my = median_node[0], median_node[1] 185 dist = (px - mx) * (px - mx) + (py - my) * (py - my) 186 187 is_east = mx >= px 188 is_west = mx <= px 189 is_north = my >= py 190 is_south = my <= py 191 192 if is_north and is_east and (active_mask & 1): 193 if dist < best_dists[0]: 194 bests[0] = median_node 195 best_dists[0] = dist 196 197 if is_north and is_west and (active_mask & 2): 198 if dist < best_dists[1]: 199 bests[1] = median_node 200 best_dists[1] = dist 201 202 if is_south and is_east and (active_mask & 4): 203 if dist < best_dists[2]: 204 bests[2] = median_node 205 best_dists[2] = dist 206 207 if is_south and is_west and (active_mask & 8): 208 if dist < best_dists[3]: 209 bests[3] = median_node 210 best_dists[3] = dist 211 212 axis = curr[1] 213 diff = point[axis] - median_node[axis] 214 215 if diff < 0: 216 close, away = curr[2], curr[3] 217 close_mask = active_mask 218 away_mask = ( 219 active_mask & (1 | 4) if axis == 0 else active_mask & (1 | 2) 220 ) 221 else: 222 close, away = curr[3], curr[2] 223 close_mask = active_mask 224 away_mask = ( 225 active_mask & (2 | 8) if axis == 0 else active_mask & (4 | 8) 226 ) 227 228 _search(close, close_mask) 229 230 if away_mask: 231 diff_sq = diff * diff 232 max_away = 0.0 233 if (away_mask & 1) and best_dists[0] > max_away: 234 max_away = best_dists[0] 235 if (away_mask & 2) and best_dists[1] > max_away: 236 max_away = best_dists[1] 237 if (away_mask & 4) and best_dists[2] > max_away: 238 max_away = best_dists[2] 239 if (away_mask & 8) and best_dists[3] > max_away: 240 max_away = best_dists[3] 241 242 if diff_sq < max_away: 243 _search(away, away_mask) 244 245 _search(node, 15) 246 return {"ne": bests[0], "nw": bests[1], "se": bests[2], "sw": bests[3]}
Function:
- Find the closest point in each of the 4 quadrants (ne, nw, se, sw) for 2D Cartesian points.
Required Arguments:
node- Type: tuple
- What: The root node of the KDTree
point- Type: tuple
- What: The 2D query point (x, y)
Returns:
- A dictionary mapping 'ne', 'nw', 'se', 'sw' to their closest point tuple or None.