z7py 0.1.0__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.
- z7py/__init__.py +61 -0
- z7py/latitudes.py +152 -0
- z7py/z7.py +704 -0
- z7py-0.1.0.dist-info/METADATA +479 -0
- z7py-0.1.0.dist-info/RECORD +8 -0
- z7py-0.1.0.dist-info/WHEEL +4 -0
- z7py-0.1.0.dist-info/licenses/LICENSE +201 -0
- z7py-0.1.0.dist-info/licenses/NOTICE +24 -0
z7py/__init__.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""z7py — Z7 (IGEO7/ISEA7H) discrete global grid indexing for Python.
|
|
2
|
+
|
|
3
|
+
Bit-packed 64-bit Z7 indices with Numba-accelerated neighbour traversal,
|
|
4
|
+
parent/child navigation and monotonic range arithmetic. Pure NumPy/Numba:
|
|
5
|
+
no DGGRID binary and no geospatial stack required.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
|
|
10
|
+
from .z7 import (
|
|
11
|
+
# Resolution stats
|
|
12
|
+
RESOLUTION_STATS,
|
|
13
|
+
get_resolution_stats,
|
|
14
|
+
get_num_cells,
|
|
15
|
+
get_cell_area_m2,
|
|
16
|
+
get_cell_area_km2,
|
|
17
|
+
get_cls_m,
|
|
18
|
+
get_cls_km,
|
|
19
|
+
find_resolution_by_value,
|
|
20
|
+
find_resolution_by_cls_m,
|
|
21
|
+
find_resolution_by_area_m2,
|
|
22
|
+
find_resolution_by_num_cells,
|
|
23
|
+
# Hex / string / int conversion
|
|
24
|
+
decode_z7hex_index,
|
|
25
|
+
encode_z7hex_index,
|
|
26
|
+
z7hex_to_z7string,
|
|
27
|
+
z7hex_to_z7int,
|
|
28
|
+
z7int_to_z7hex,
|
|
29
|
+
decode_z7int,
|
|
30
|
+
encode_z7int,
|
|
31
|
+
get_z7hex_resolution,
|
|
32
|
+
get_z7hex_local_pos,
|
|
33
|
+
get_z7string_resolution,
|
|
34
|
+
get_z7string_local_pos,
|
|
35
|
+
z7string_to_index,
|
|
36
|
+
index_to_z7string,
|
|
37
|
+
# Index ops (operate on raw uint64)
|
|
38
|
+
get_base_cell,
|
|
39
|
+
get_digit,
|
|
40
|
+
get_digits,
|
|
41
|
+
get_resolution,
|
|
42
|
+
get_parent,
|
|
43
|
+
# Neighbour tables / helpers
|
|
44
|
+
get_base_cell_neighbours,
|
|
45
|
+
get_base_cell_neighbour,
|
|
46
|
+
get_neighbours,
|
|
47
|
+
get_neighbour,
|
|
48
|
+
first_non_zero,
|
|
49
|
+
neighbour_addition_cw,
|
|
50
|
+
neighbour_addition_ccw,
|
|
51
|
+
neighbour_addition_ccw_mod,
|
|
52
|
+
z7_to_monotonic_int,
|
|
53
|
+
monotonic_int_to_z7,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
from .latitudes import (
|
|
57
|
+
geodetic_to_authalic,
|
|
58
|
+
authalic_to_geodetic,
|
|
59
|
+
WGS84_A,
|
|
60
|
+
WGS84_F,
|
|
61
|
+
)
|
z7py/latitudes.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Authalic ↔ Geodetic latitude conversion for WGS84 ellipsoid.
|
|
3
|
+
Translated from z7jl/AuthalicLatitude.jl.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import numba as nb
|
|
8
|
+
|
|
9
|
+
# The order of the series is a global constant
|
|
10
|
+
POLYNOMIAL_ORDER = 6
|
|
11
|
+
|
|
12
|
+
# WGS84 ellipsoid parameters
|
|
13
|
+
WGS84_A = 6378137.0 # Semi-major axis (meters)
|
|
14
|
+
WGS84_F = 1.0 / 298.257223563 # Flattening
|
|
15
|
+
|
|
16
|
+
# Geodetic to authalic: Coefficients for converting ϕ to ξ.
|
|
17
|
+
# Eq. A19 in Karney (2022)
|
|
18
|
+
# These are the same as in the Julia code, 6x6 matrix.
|
|
19
|
+
AUTHALIC_FWD = np.array([
|
|
20
|
+
[-4/3, -4/45, 88/315, 538/4725, 20824/467775, -44732/2837835],
|
|
21
|
+
[0, 34/45, 8/105, -2482/14175, -37192/467775, -12467764/212837625],
|
|
22
|
+
[0, 0, -1532/2835, -898/14175, 54968/467775, 100320856/1915538625],
|
|
23
|
+
[0, 0, 0, 6007/14175, 24496/467775, -5884124/70945875],
|
|
24
|
+
[0, 0, 0, 0, -23356/66825, -839792/19348875],
|
|
25
|
+
[0, 0, 0, 0, 0, 570284222/1915538625]
|
|
26
|
+
], dtype=np.float64)
|
|
27
|
+
|
|
28
|
+
# Authalic to geodetic: Coefficients for converting ξ to ϕ.
|
|
29
|
+
# Eq. A20 in Karney (2022)
|
|
30
|
+
AUTHALIC_INV = np.array([
|
|
31
|
+
[4/3, 4/45, -16/35, -2582/14175, 60136/467775, 28112932/212837625],
|
|
32
|
+
[0, 46/45, 152/945, -11966/14175, -21016/51975, 251310128/638512875],
|
|
33
|
+
[0, 0, 3044/2835, 3802/14175, -94388/66825, -8797648/10945935],
|
|
34
|
+
[0, 0, 0, 6059/4725, 41072/93555, -1472637812/638512875],
|
|
35
|
+
[0, 0, 0, 0, 768272/467775, 455935736/638512875],
|
|
36
|
+
[0, 0, 0, 0, 0, 4210684958/1915538625]
|
|
37
|
+
], dtype=np.float64)
|
|
38
|
+
|
|
39
|
+
# Coefficients for expansion of the normalized meridian arc unit in terms
|
|
40
|
+
# of *n²*, the square of the third flattening.
|
|
41
|
+
# See [Karney 2010](crate::Bibliography::Kar10) eq. (29)
|
|
42
|
+
MERIDIAN_ARC_COEFFICIENTS = np.array([
|
|
43
|
+
1.0,
|
|
44
|
+
1.0/4.0,
|
|
45
|
+
1.0/64.0,
|
|
46
|
+
1.0/256.0,
|
|
47
|
+
25.0/16384.0,
|
|
48
|
+
49.0/65536.0,
|
|
49
|
+
441.0/1048576.0
|
|
50
|
+
], dtype=np.float64)
|
|
51
|
+
|
|
52
|
+
@nb.njit(cache=True)
|
|
53
|
+
def horner(arg, coeffs):
|
|
54
|
+
"""
|
|
55
|
+
Evaluate a polynomial Σ cᵢ ⋅ xⁱ using the efficient Horner's scheme.
|
|
56
|
+
`coeffs` should be ordered from the lowest power (c₀, c₁, ...) to the highest.
|
|
57
|
+
"""
|
|
58
|
+
val = coeffs[-1]
|
|
59
|
+
for i in range(len(coeffs) - 2, -1, -1):
|
|
60
|
+
val = val * arg + coeffs[i]
|
|
61
|
+
return val
|
|
62
|
+
|
|
63
|
+
@nb.njit(cache=True)
|
|
64
|
+
def fourier_sin(angle, coeffs):
|
|
65
|
+
"""
|
|
66
|
+
Evaluate a Fourier sine series: Σ cᵢ * sin(i * angle).
|
|
67
|
+
"""
|
|
68
|
+
val = 0.0
|
|
69
|
+
for i in range(len(coeffs)):
|
|
70
|
+
val += coeffs[i] * np.sin((i + 1) * angle)
|
|
71
|
+
return val
|
|
72
|
+
|
|
73
|
+
@nb.njit(cache=True)
|
|
74
|
+
def third_flattening(f):
|
|
75
|
+
"""
|
|
76
|
+
The third flattening, n = f / (2 - f).
|
|
77
|
+
"""
|
|
78
|
+
return f / (2.0 - f)
|
|
79
|
+
|
|
80
|
+
@nb.njit(cache=True)
|
|
81
|
+
def normalized_meridian_arc_unit(f):
|
|
82
|
+
"""
|
|
83
|
+
The Normalized Meridian Arc Unit, Qn.
|
|
84
|
+
"""
|
|
85
|
+
n = third_flattening(f)
|
|
86
|
+
return horner(n * n, MERIDIAN_ARC_COEFFICIENTS) / (1.0 + n)
|
|
87
|
+
|
|
88
|
+
@nb.njit(cache=True)
|
|
89
|
+
def compute_fourier_coeffs(f, poly_fwd, poly_inv):
|
|
90
|
+
"""
|
|
91
|
+
Compute Fourier coefficients by evaluating their corresponding Taylor polynomials.
|
|
92
|
+
"""
|
|
93
|
+
n = third_flattening(f)
|
|
94
|
+
fwd = np.empty(POLYNOMIAL_ORDER, dtype=np.float64)
|
|
95
|
+
inv = np.empty(POLYNOMIAL_ORDER, dtype=np.float64)
|
|
96
|
+
for i in range(POLYNOMIAL_ORDER):
|
|
97
|
+
fwd[i] = n * horner(n, poly_fwd[i])
|
|
98
|
+
inv[i] = n * horner(n, poly_inv[i])
|
|
99
|
+
|
|
100
|
+
# etc[0] in Julia corresponds to normalized_meridian_arc_unit
|
|
101
|
+
qn = normalized_meridian_arc_unit(f)
|
|
102
|
+
return fwd, inv, qn
|
|
103
|
+
|
|
104
|
+
@nb.njit(cache=True)
|
|
105
|
+
def _geodetic_to_authalic_rad(phi_rad, fwd_coeffs):
|
|
106
|
+
return phi_rad + fourier_sin(2.0 * phi_rad, fwd_coeffs)
|
|
107
|
+
|
|
108
|
+
@nb.njit(cache=True)
|
|
109
|
+
def _authalic_to_geodetic_rad(xi_rad, inv_coeffs):
|
|
110
|
+
return xi_rad + fourier_sin(2.0 * xi_rad, inv_coeffs)
|
|
111
|
+
|
|
112
|
+
# Precomputed coefficients for WGS84 for performance
|
|
113
|
+
WGS84_FWD, WGS84_INV, WGS84_QN = compute_fourier_coeffs(WGS84_F, AUTHALIC_FWD, AUTHALIC_INV)
|
|
114
|
+
|
|
115
|
+
@nb.njit(cache=True)
|
|
116
|
+
def geodetic_to_authalic(phi, degrees=True):
|
|
117
|
+
"""
|
|
118
|
+
Convert geodetic latitude (ϕ) to authalic latitude (ξ) for WGS84.
|
|
119
|
+
"""
|
|
120
|
+
phi_rad = np.radians(phi) if degrees else phi
|
|
121
|
+
xi_rad = _geodetic_to_authalic_rad(phi_rad, WGS84_FWD)
|
|
122
|
+
return np.degrees(xi_rad) if degrees else xi_rad
|
|
123
|
+
|
|
124
|
+
@nb.njit(cache=True)
|
|
125
|
+
def authalic_to_geodetic(xi, degrees=True):
|
|
126
|
+
"""
|
|
127
|
+
Convert authalic latitude (ξ) to geodetic latitude (ϕ) for WGS84.
|
|
128
|
+
"""
|
|
129
|
+
xi_rad = np.radians(xi) if degrees else xi
|
|
130
|
+
phi_rad = _authalic_to_geodetic_rad(xi_rad, WGS84_INV)
|
|
131
|
+
return np.degrees(phi_rad) if degrees else phi_rad
|
|
132
|
+
|
|
133
|
+
# Generic versions that take ellipsoid parameters
|
|
134
|
+
@nb.njit(cache=True)
|
|
135
|
+
def geodetic_to_authalic_custom(phi, f, poly_fwd, degrees=True):
|
|
136
|
+
phi_rad = np.radians(phi) if degrees else phi
|
|
137
|
+
n = third_flattening(f)
|
|
138
|
+
fwd = np.empty(POLYNOMIAL_ORDER, dtype=np.float64)
|
|
139
|
+
for i in range(POLYNOMIAL_ORDER):
|
|
140
|
+
fwd[i] = n * horner(n, poly_fwd[i])
|
|
141
|
+
xi_rad = _geodetic_to_authalic_rad(phi_rad, fwd)
|
|
142
|
+
return np.degrees(xi_rad) if degrees else xi_rad
|
|
143
|
+
|
|
144
|
+
@nb.njit(cache=True)
|
|
145
|
+
def authalic_to_geodetic_custom(xi, f, poly_inv, degrees=True):
|
|
146
|
+
xi_rad = np.radians(xi) if degrees else xi
|
|
147
|
+
n = third_flattening(f)
|
|
148
|
+
inv = np.empty(POLYNOMIAL_ORDER, dtype=np.float64)
|
|
149
|
+
for i in range(POLYNOMIAL_ORDER):
|
|
150
|
+
inv[i] = n * horner(n, poly_inv[i])
|
|
151
|
+
phi_rad = _authalic_to_geodetic_rad(xi_rad, inv)
|
|
152
|
+
return np.degrees(phi_rad) if degrees else phi_rad
|