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.