that's too much!
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
import numpy as np
|
||||
|
||||
|
||||
def _hilbert_distance(geoms, total_bounds=None, level=16):
|
||||
"""
|
||||
Calculate the distance along a Hilbert curve.
|
||||
|
||||
The distances are calculated for the midpoints of the geometries in the
|
||||
GeoDataFrame.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
geoms : GeometryArray
|
||||
total_bounds : 4-element array
|
||||
Total bounds of geometries - array
|
||||
level : int (1 - 16), default 16
|
||||
Determines the precision of the curve (points on the curve will
|
||||
have coordinates in the range [0, 2^level - 1]).
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
Array containing distances along the Hilbert curve
|
||||
|
||||
"""
|
||||
if geoms.is_empty.any() | geoms.isna().any():
|
||||
raise ValueError(
|
||||
"Hilbert distance cannot be computed on a GeoSeries with empty or "
|
||||
"missing geometries.",
|
||||
)
|
||||
# Calculate bounds as numpy array
|
||||
bounds = geoms.bounds
|
||||
|
||||
# Calculate discrete coords based on total bounds and bounds
|
||||
x, y = _continuous_to_discrete_coords(bounds, level, total_bounds)
|
||||
# Compute distance along hilbert curve
|
||||
distances = _encode(level, x, y)
|
||||
|
||||
return distances
|
||||
|
||||
|
||||
def _continuous_to_discrete_coords(bounds, level, total_bounds):
|
||||
"""
|
||||
Calculates mid points & ranges of geoms and returns
|
||||
as discrete coords
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
bounds : Bounds of each geometry - array
|
||||
|
||||
p : The number of iterations used in constructing the Hilbert curve
|
||||
|
||||
total_bounds : Total bounds of geometries - array
|
||||
|
||||
Returns
|
||||
-------
|
||||
Discrete two-dimensional numpy array
|
||||
Two-dimensional array Array of hilbert distances for each geom
|
||||
|
||||
"""
|
||||
# Hilbert Side length
|
||||
side_length = (2**level) - 1
|
||||
|
||||
# Calculate mid points for x and y bound coords - returns array
|
||||
x_mids = (bounds[:, 0] + bounds[:, 2]) / 2.0
|
||||
y_mids = (bounds[:, 1] + bounds[:, 3]) / 2.0
|
||||
|
||||
# Calculate x and y range of total bound coords - returns array
|
||||
if total_bounds is None:
|
||||
total_bounds = (
|
||||
np.nanmin(x_mids),
|
||||
np.nanmin(y_mids),
|
||||
np.nanmax(x_mids),
|
||||
np.nanmax(y_mids),
|
||||
)
|
||||
|
||||
xmin, ymin, xmax, ymax = total_bounds
|
||||
|
||||
# Transform continuous value to discrete integer for each dimension
|
||||
x_int = _continuous_to_discrete(x_mids, (xmin, xmax), side_length)
|
||||
y_int = _continuous_to_discrete(y_mids, (ymin, ymax), side_length)
|
||||
|
||||
return x_int, y_int
|
||||
|
||||
|
||||
def _continuous_to_discrete(vals, val_range, n):
|
||||
"""
|
||||
Convert a continuous one-dimensional array to discrete integer values
|
||||
based their ranges
|
||||
|
||||
Parameters
|
||||
----------
|
||||
vals : Array of continuous values
|
||||
|
||||
val_range : Tuple containing range of continuous values
|
||||
|
||||
n : Number of discrete values
|
||||
|
||||
Returns
|
||||
-------
|
||||
One-dimensional array of discrete ints
|
||||
|
||||
"""
|
||||
width = val_range[1] - val_range[0]
|
||||
if width == 0:
|
||||
return np.zeros_like(vals, dtype=np.uint32)
|
||||
res = (vals - val_range[0]) * (n / width)
|
||||
|
||||
np.clip(res, 0, n, out=res)
|
||||
return res.astype(np.uint32)
|
||||
|
||||
|
||||
# Fast Hilbert curve algorithm by http://threadlocalmutex.com/
|
||||
# From C++ https://github.com/rawrunprotected/hilbert_curves
|
||||
# (public domain)
|
||||
|
||||
|
||||
MAX_LEVEL = 16
|
||||
|
||||
|
||||
def _interleave(x):
|
||||
x = (x | (x << 8)) & 0x00FF00FF
|
||||
x = (x | (x << 4)) & 0x0F0F0F0F
|
||||
x = (x | (x << 2)) & 0x33333333
|
||||
x = (x | (x << 1)) & 0x55555555
|
||||
return x
|
||||
|
||||
|
||||
def _encode(level, x, y):
|
||||
x = np.asarray(x, dtype="uint32")
|
||||
y = np.asarray(y, dtype="uint32")
|
||||
|
||||
if level > MAX_LEVEL:
|
||||
raise ValueError("Level out of range")
|
||||
|
||||
x = x << (16 - level)
|
||||
y = y << (16 - level)
|
||||
|
||||
# Initial prefix scan round, prime with x and y
|
||||
a = x ^ y
|
||||
b = 0xFFFF ^ a
|
||||
c = 0xFFFF ^ (x | y)
|
||||
d = x & (y ^ 0xFFFF)
|
||||
|
||||
A = a | (b >> 1)
|
||||
B = (a >> 1) ^ a
|
||||
C = ((c >> 1) ^ (b & (d >> 1))) ^ c
|
||||
D = ((a & (c >> 1)) ^ (d >> 1)) ^ d
|
||||
|
||||
a = A.copy()
|
||||
b = B.copy()
|
||||
c = C.copy()
|
||||
d = D.copy()
|
||||
|
||||
A = (a & (a >> 2)) ^ (b & (b >> 2))
|
||||
B = (a & (b >> 2)) ^ (b & ((a ^ b) >> 2))
|
||||
C ^= (a & (c >> 2)) ^ (b & (d >> 2))
|
||||
D ^= (b & (c >> 2)) ^ ((a ^ b) & (d >> 2))
|
||||
|
||||
a = A.copy()
|
||||
b = B.copy()
|
||||
c = C.copy()
|
||||
d = D.copy()
|
||||
|
||||
A = (a & (a >> 4)) ^ (b & (b >> 4))
|
||||
B = (a & (b >> 4)) ^ (b & ((a ^ b) >> 4))
|
||||
C ^= (a & (c >> 4)) ^ (b & (d >> 4))
|
||||
D ^= (b & (c >> 4)) ^ ((a ^ b) & (d >> 4))
|
||||
|
||||
# Final round and projection
|
||||
a = A.copy()
|
||||
b = B.copy()
|
||||
c = C.copy()
|
||||
d = D.copy()
|
||||
|
||||
C ^= (a & (c >> 8)) ^ (b & (d >> 8))
|
||||
D ^= (b & (c >> 8)) ^ ((a ^ b) & (d >> 8))
|
||||
|
||||
# Undo transformation prefix scan
|
||||
a = C ^ (C >> 1)
|
||||
b = D ^ (D >> 1)
|
||||
|
||||
# Recover index bits
|
||||
i0 = x ^ y
|
||||
i1 = b | (0xFFFF ^ (i0 | a))
|
||||
|
||||
return ((_interleave(i1) << 1) | _interleave(i0)) >> (32 - 2 * level)
|
||||
Reference in New Issue
Block a user