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)