pygeodesy 24.7.24__py2.py3-none-any.whl → 24.8.24__py2.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.
- {PyGeodesy-24.7.24.dist-info → PyGeodesy-24.8.24.dist-info}/METADATA +20 -19
- {PyGeodesy-24.7.24.dist-info → PyGeodesy-24.8.24.dist-info}/RECORD +57 -57
- {PyGeodesy-24.7.24.dist-info → PyGeodesy-24.8.24.dist-info}/WHEEL +1 -1
- pygeodesy/__init__.py +26 -27
- pygeodesy/auxilats/auxAngle.py +2 -2
- pygeodesy/auxilats/auxDST.py +3 -3
- pygeodesy/azimuthal.py +4 -4
- pygeodesy/basics.py +3 -3
- pygeodesy/cartesianBase.py +6 -6
- pygeodesy/constants.py +11 -11
- pygeodesy/css.py +5 -5
- pygeodesy/ellipsoidalBase.py +18 -15
- pygeodesy/ellipsoidalExact.py +2 -2
- pygeodesy/ellipsoidalGeodSolve.py +2 -2
- pygeodesy/ellipsoidalKarney.py +2 -2
- pygeodesy/ellipsoidalNvector.py +2 -2
- pygeodesy/ellipsoidalVincenty.py +7 -6
- pygeodesy/ellipsoids.py +3 -3
- pygeodesy/epsg.py +3 -3
- pygeodesy/fmath.py +2 -1
- pygeodesy/formy.py +2 -2
- pygeodesy/fsums.py +4 -4
- pygeodesy/gars.py +66 -58
- pygeodesy/geodesici.py +4 -10
- pygeodesy/geodesicx/gx.py +3 -3
- pygeodesy/geodesicx/gxarea.py +3 -3
- pygeodesy/geodsolve.py +3 -3
- pygeodesy/geohash.py +491 -267
- pygeodesy/geoids.py +298 -316
- pygeodesy/heights.py +176 -194
- pygeodesy/internals.py +39 -6
- pygeodesy/interns.py +2 -3
- pygeodesy/karney.py +2 -2
- pygeodesy/latlonBase.py +14 -8
- pygeodesy/lazily.py +22 -21
- pygeodesy/ltp.py +6 -7
- pygeodesy/ltpTuples.py +12 -6
- pygeodesy/named.py +5 -4
- pygeodesy/namedTuples.py +14 -1
- pygeodesy/osgr.py +7 -7
- pygeodesy/points.py +2 -2
- pygeodesy/resections.py +7 -7
- pygeodesy/rhumb/solve.py +3 -3
- pygeodesy/simplify.py +10 -10
- pygeodesy/sphericalBase.py +3 -3
- pygeodesy/sphericalTrigonometry.py +2 -2
- pygeodesy/streprs.py +3 -3
- pygeodesy/triaxials.py +210 -204
- pygeodesy/units.py +36 -19
- pygeodesy/unitsBase.py +4 -4
- pygeodesy/utmupsBase.py +3 -3
- pygeodesy/vector2d.py +158 -51
- pygeodesy/vector3d.py +13 -52
- pygeodesy/vector3dBase.py +81 -63
- pygeodesy/webmercator.py +3 -3
- pygeodesy/wgrs.py +109 -101
- {PyGeodesy-24.7.24.dist-info → PyGeodesy-24.8.24.dist-info}/top_level.txt +0 -0
pygeodesy/geohash.py
CHANGED
|
@@ -1,93 +1,47 @@
|
|
|
1
1
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
|
-
u'''Geohash
|
|
4
|
+
u'''I{Gustavo Niemeyer}’s U{Geohash<https://WikiPedia.org/wiki/Geohash>}.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
Class L{Geohash} and several functions to encode, decode and inspect
|
|
7
|
+
C{geohashes} and optional L{Geohashed} caches.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
and published under the same MIT Licence**, see
|
|
11
|
-
<https://www.Movable-Type.co.UK/scripts/geohash.html>}.
|
|
9
|
+
Originally transcoded from JavaScript originals by I{(C) Chris Veness
|
|
10
|
+
2011-2015} and published under the same MIT Licence**, see
|
|
11
|
+
U{Geohashes<https://www.Movable-Type.co.UK/scripts/geohash.html>}.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
<https://
|
|
15
|
-
<https://
|
|
16
|
-
<https://
|
|
13
|
+
@see: U{Geohash<https://WikiPedia.org/wiki/Geohash>}, I{Karney}'s C++
|
|
14
|
+
U{Geohash<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Geohash.html>},
|
|
15
|
+
U{geohash<https://GitHub.com/vinsci/geohash>},
|
|
16
|
+
U{pygeohash<https://PyPI.org/project/pygeohash>} and
|
|
17
|
+
U{geohash-js<https://GitHub.com/DaveTroy/geohash-js>}.
|
|
17
18
|
'''
|
|
18
19
|
|
|
19
|
-
from pygeodesy.basics import
|
|
20
|
-
from pygeodesy.constants import EPS, R_M,
|
|
21
|
-
|
|
22
|
-
from pygeodesy.dms import parse3llh # parseDMS2
|
|
20
|
+
from pygeodesy.basics import isstr, map2
|
|
21
|
+
from pygeodesy.constants import EPS, R_M, _0_0, _0_5, _180_0, _360_0, \
|
|
22
|
+
_90_0, _N_90_0, _N_180_0 # PYCHOK used!
|
|
23
23
|
from pygeodesy.errors import _ValueError, _xkwds, _xStrError
|
|
24
|
-
from pygeodesy.fmath import favg
|
|
25
24
|
# from pygeodesy import formy as _formy # _MODS
|
|
26
|
-
from pygeodesy.interns import NN, _COMMA_, _DOT_, _E_, _N_, _NE_,
|
|
27
|
-
_S_, _SE_, _SW_, _W_
|
|
28
|
-
|
|
25
|
+
from pygeodesy.interns import NN, _COMMA_, _DOT_, _E_, _height_, _N_, _NE_, \
|
|
26
|
+
_NW_, _radius_, _S_, _SE_, _SPACE_, _SW_, _W_, \
|
|
27
|
+
_width_ # _INV_
|
|
28
|
+
from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
|
|
29
29
|
from pygeodesy.named import _name__, _NamedDict, _NamedTuple, nameof, _xnamed
|
|
30
30
|
from pygeodesy.namedTuples import Bounds2Tuple, Bounds4Tuple, LatLon2Tuple, \
|
|
31
31
|
PhiLam2Tuple
|
|
32
32
|
from pygeodesy.props import deprecated_function, deprecated_method, \
|
|
33
|
-
deprecated_property_RO, Property_RO
|
|
34
|
-
|
|
35
|
-
from pygeodesy.
|
|
33
|
+
deprecated_property_RO, Property_RO, \
|
|
34
|
+
property_RO, property_ROver
|
|
35
|
+
# from pygeodesy.streprs import Fmt, fstr # _MODS
|
|
36
|
+
from pygeodesy.units import Degrees_, Int, Lat_, Lon_, Meter, Precision_, Str
|
|
36
37
|
|
|
37
38
|
from math import fabs, ldexp, log10, radians
|
|
38
39
|
|
|
39
40
|
__all__ = _ALL_LAZY.geohash
|
|
40
|
-
__version__ = '24.
|
|
41
|
+
__version__ = '24.08.05'
|
|
41
42
|
|
|
42
|
-
_formy
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class _GH(object):
|
|
46
|
-
'''(INTERNAL) Lazily defined constants.
|
|
47
|
-
'''
|
|
48
|
-
def _4d(self, n, e, s, w): # helper
|
|
49
|
-
return dict(N=(n, e), S=(s, w),
|
|
50
|
-
E=(e, n), W=(w, s))
|
|
51
|
-
|
|
52
|
-
@Property_RO
|
|
53
|
-
def Borders(self):
|
|
54
|
-
return self._4d('prxz', 'bcfguvyz', '028b', '0145hjnp')
|
|
55
|
-
|
|
56
|
-
Bounds4 = (_N_90_0, _N_180_0, _90_0, _180_0)
|
|
57
|
-
|
|
58
|
-
@Property_RO
|
|
59
|
-
def DecodedBase32(self): # inverse GeohashBase32 map
|
|
60
|
-
return dict((c, i) for i, c in enumerate(self.GeohashBase32))
|
|
61
|
-
|
|
62
|
-
# Geohash-specific base32 map
|
|
63
|
-
GeohashBase32 = '0123456789bcdefghjkmnpqrstuvwxyz' # no a, i, j and o
|
|
64
|
-
|
|
65
|
-
@Property_RO
|
|
66
|
-
def Neighbors(self):
|
|
67
|
-
return self._4d('p0r21436x8zb9dcf5h7kjnmqesgutwvy',
|
|
68
|
-
'bc01fg45238967deuvhjyznpkmstqrwx',
|
|
69
|
-
'14365h7k9dcfesgujnmqp0r2twvyx8zb',
|
|
70
|
-
'238967debc01fg45kmstqrwxuvhjyznp')
|
|
71
|
-
|
|
72
|
-
@Property_RO
|
|
73
|
-
def Sizes(self): # lat-, lon and radial size (in meter)
|
|
74
|
-
# ... where radial = sqrt(latSize * lonWidth / PI)
|
|
75
|
-
_t = _floatuple
|
|
76
|
-
return (_t(20032e3, 20000e3, 11292815.096), # 0
|
|
77
|
-
_t( 5003e3, 5000e3, 2821794.075), # 1
|
|
78
|
-
_t( 650e3, 1225e3, 503442.397), # 2
|
|
79
|
-
_t( 156e3, 156e3, 88013.575), # 3
|
|
80
|
-
_t( 19500, 39100, 15578.683), # 4
|
|
81
|
-
_t( 4890, 4890, 2758.887), # 5
|
|
82
|
-
_t( 610, 1220, 486.710), # 6
|
|
83
|
-
_t( 153, 153, 86.321), # 7
|
|
84
|
-
_t( 19.1, 38.2, 15.239), # 8
|
|
85
|
-
_t( 4.77, 4.77, 2.691), # 9
|
|
86
|
-
_t( 0.596, 1.19, 0.475), # 10
|
|
87
|
-
_t( 0.149, 0.149, 0.084), # 11
|
|
88
|
-
_t( 0.0186, 0.0372, 0.015)) # 12 _MaxPrec
|
|
89
|
-
|
|
90
|
-
_GH = _GH() # PYCHOK singleton
|
|
43
|
+
_formy = _MODS.into(formy=__name__)
|
|
44
|
+
_MASK5 = 16, 8, 4, 2, 1 # PYCHOK used!
|
|
91
45
|
_MaxPrec = 12
|
|
92
46
|
|
|
93
47
|
|
|
@@ -106,16 +60,22 @@ def _2bounds(LatLon, LatLon_kwds, s, w, n, e, **name):
|
|
|
106
60
|
def _2center(bounds):
|
|
107
61
|
'''(INTERNAL) Return the C{bounds} center.
|
|
108
62
|
'''
|
|
109
|
-
return (
|
|
110
|
-
|
|
63
|
+
return (_2mid(bounds.latN, bounds.latS),
|
|
64
|
+
_2mid(bounds.lonE, bounds.lonW))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _2dab(d, a, b):
|
|
68
|
+
'''(INTERNAL) Get delta lat or lon from center.
|
|
69
|
+
'''
|
|
70
|
+
return fabs(d - round(*_2mid_ndigits(a, b)))
|
|
111
71
|
|
|
112
72
|
|
|
113
73
|
def _2fll(lat, lon, *unused):
|
|
114
74
|
'''(INTERNAL) Convert lat, lon to 2-tuple of floats.
|
|
115
75
|
'''
|
|
116
76
|
# lat, lon = parseDMS2(lat, lon)
|
|
117
|
-
return (
|
|
118
|
-
|
|
77
|
+
return (Lat_(lat, Error=GeohashError),
|
|
78
|
+
Lon_(lon, Error=GeohashError))
|
|
119
79
|
|
|
120
80
|
|
|
121
81
|
def _2Geohash(geohash):
|
|
@@ -125,61 +85,226 @@ def _2Geohash(geohash):
|
|
|
125
85
|
Geohash(geohash)
|
|
126
86
|
|
|
127
87
|
|
|
128
|
-
def
|
|
129
|
-
'''(INTERNAL)
|
|
88
|
+
def _2latlon(s, w, n, e, fstr=None):
|
|
89
|
+
'''(INTERNAL) Get the center C{lat, lon}, rounded.
|
|
90
|
+
'''
|
|
91
|
+
lat, a = _2mid_ndigits(n, s)
|
|
92
|
+
lon, b = _2mid_ndigits(e, w)
|
|
93
|
+
return (fstr(lat, prec=a), fstr(lon, prec=b)) if fstr else \
|
|
94
|
+
(round(lat, a), round(lon, b))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _2mid(a, b):
|
|
98
|
+
'''(INTERNAL) Bisect C{a} to C{b}.
|
|
99
|
+
'''
|
|
100
|
+
return (a + b) * _0_5 # favg
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _2mid_ndigits(a, b): # a > b
|
|
104
|
+
'''(INTERNAL) Return 2-tuple C{(_2mid, ndigits)}.
|
|
105
|
+
'''
|
|
106
|
+
# round to near centre without excessive
|
|
107
|
+
# precision to ⌊2-log10(Δ°)⌋ ndigits
|
|
108
|
+
return _2mid(a, b), int(2 - log10(a - b))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _2Precision(p):
|
|
112
|
+
'''(INTERNAL) Get a valid C{Precision}.
|
|
113
|
+
'''
|
|
114
|
+
return Precision_(p, low=1, high=_MaxPrec, Error=GeohashError)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _2res(res, **prec):
|
|
118
|
+
'''(INTERNAL) Get the C{res}olution for a C{prec}ision.
|
|
130
119
|
'''
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
120
|
+
p = max(min(Int(Error=GeohashError, **prec), _MaxPrec), 0) * 5
|
|
121
|
+
x = (p - p // 2) if res > _180_0 else (p // 2)
|
|
122
|
+
return ldexp(res, -x) if x else res # ldexp == res / float(1 << x)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class _GH(object):
|
|
126
|
+
'''(INTERNAL) Lazily defined constants.
|
|
127
|
+
'''
|
|
128
|
+
def _4d(self, s, w, n, e): # helper
|
|
129
|
+
return dict(S=(s, w), W=(w, s),
|
|
130
|
+
N=(n, e), E=(e, n))
|
|
131
|
+
|
|
132
|
+
@property_ROver
|
|
133
|
+
def Borders(self):
|
|
134
|
+
return self._4d('028b', '0145hjnp', 'prxz', 'bcfguvyz')
|
|
135
|
+
|
|
136
|
+
@property_ROver
|
|
137
|
+
def DecodeB32(self): # inverse EncodeB32 map
|
|
138
|
+
return dict((c, i) for i, c in enumerate(self.EncodeB32))
|
|
139
|
+
|
|
140
|
+
def decode2(self, geohash):
|
|
141
|
+
'''Decode C{geohash} to 2-tuple C{(lat, lon)}.
|
|
142
|
+
'''
|
|
143
|
+
swne = self.swne4(geohash)
|
|
144
|
+
return _2latlon(*swne)
|
|
145
|
+
|
|
146
|
+
# Geohash's base32 codes, no a, i, l and o
|
|
147
|
+
EncodeB32 = '0123456789bcdefghjkmnpqrstuvwxyz'
|
|
148
|
+
|
|
149
|
+
def encode(self, *lat_lon_prec_eps):
|
|
150
|
+
'''Encode C{lat, lon} to C{prec}ision or C{eps}.
|
|
151
|
+
'''
|
|
152
|
+
def _encodes(lat, lon, prec, eps=0):
|
|
153
|
+
s, w, n, e = self.SWNE4
|
|
154
|
+
E, d, _mid = self.EncodeB32, True, _2mid
|
|
155
|
+
for _ in range(prec):
|
|
156
|
+
i = 0
|
|
157
|
+
for _ in range(5): # len(_MASK5)
|
|
158
|
+
i += i
|
|
159
|
+
if d: # bisect longitude
|
|
160
|
+
a = _mid(e, w)
|
|
161
|
+
if lon < a:
|
|
162
|
+
e = a
|
|
163
|
+
else:
|
|
164
|
+
w = a
|
|
165
|
+
i += 1
|
|
166
|
+
else: # bisect latitude
|
|
167
|
+
a = _mid(n, s)
|
|
168
|
+
if lat < a:
|
|
169
|
+
n = a
|
|
170
|
+
else:
|
|
171
|
+
s = a
|
|
172
|
+
i += 1
|
|
173
|
+
d = not d
|
|
174
|
+
yield E[i]
|
|
175
|
+
if eps > 0: # infer prec
|
|
176
|
+
if _2dab(lon, e, w) < eps and \
|
|
177
|
+
_2dab(lat, n, s) < eps:
|
|
178
|
+
break
|
|
179
|
+
|
|
180
|
+
return NN.join(_encodes(*lat_lon_prec_eps))
|
|
181
|
+
|
|
182
|
+
def encode2(self, lat, lon, prec, eps):
|
|
183
|
+
'''Return 2-tuple C{geohash, (lat, lon))}.
|
|
184
|
+
'''
|
|
185
|
+
lat, lon = _2fll(lat, lon)
|
|
186
|
+
if prec:
|
|
187
|
+
p, e = _2Precision(prec), 0
|
|
188
|
+
else: # infer precision by refining geohash
|
|
189
|
+
p, e = _MaxPrec, max(eps, EPS)
|
|
190
|
+
return self.encode(lat, lon, p, e), (lat, lon)
|
|
191
|
+
|
|
192
|
+
@property_ROver
|
|
193
|
+
def _LatLon2Tuple(self):
|
|
194
|
+
|
|
195
|
+
class _LatLon2Tuple(_NamedTuple):
|
|
196
|
+
'''DEPRECATED on 2024.07.28, C{(lat, lon)} in B{C{meter}}, use L{Sizes3Tuple}.'''
|
|
197
|
+
_Names_ = LatLon2Tuple._Names_
|
|
198
|
+
_Units_ = Meter, Meter
|
|
199
|
+
|
|
200
|
+
return _LatLon2Tuple
|
|
201
|
+
|
|
202
|
+
@property_ROver
|
|
203
|
+
def Neighbors(self):
|
|
204
|
+
return self._4d('14365h7k9dcfesgujnmqp0r2twvyx8zb',
|
|
205
|
+
'238967debc01fg45kmstqrwxuvhjyznp',
|
|
206
|
+
'p0r21436x8zb9dcf5h7kjnmqesgutwvy',
|
|
207
|
+
'bc01fg45238967deuvhjyznpkmstqrwx')
|
|
208
|
+
|
|
209
|
+
@property_ROver
|
|
210
|
+
def Sizes(self): # height, width and radius (in meter)
|
|
211
|
+
# where radius = sqrt(height * width / PI), the
|
|
212
|
+
# radius of a circle with area (height * width)
|
|
213
|
+
T = Sizes3Tuple
|
|
214
|
+
return (T(20000e3, 20032e3, 11292815.096), # 0
|
|
215
|
+
T( 5000e3, 5003e3, 2821794.075), # 1
|
|
216
|
+
T( 650e3, 1225e3, 503442.397), # 2
|
|
217
|
+
T( 156e3, 156e3, 88013.575), # 3
|
|
218
|
+
T( 19500, 39100, 15578.683), # 4
|
|
219
|
+
T( 4890, 4890, 2758.887), # 5
|
|
220
|
+
T( 610, 1220, 486.710), # 6
|
|
221
|
+
T( 153, 153, 86.321), # 7
|
|
222
|
+
T( 19.1, 38.2, 15.239), # 8
|
|
223
|
+
T( 4.77, 4.77, 2.691), # 9
|
|
224
|
+
T( 0.596, 1.19, 0.475), # 10
|
|
225
|
+
T( 0.149, 0.149, 0.084), # 11
|
|
226
|
+
T( 0.0186, 0.0372, 0.015)) # 12 _MaxPrec
|
|
227
|
+
|
|
228
|
+
SWNE4 = (_N_90_0, _N_180_0, _90_0, _180_0)
|
|
229
|
+
|
|
230
|
+
def swne4(self, geohash, mask5=_MASK5):
|
|
231
|
+
'''Decode C{geohash} into 4-tuple C{(s, w, n, e)}.
|
|
232
|
+
'''
|
|
233
|
+
nc = len(geohash) if isstr(geohash) else 0
|
|
234
|
+
if not (0 < nc <= _MaxPrec): # or geohash.startswith(_INV_)
|
|
235
|
+
raise GeohashError(geohash=geohash, len=nc)
|
|
236
|
+
s, w, n, e = self.SWNE4
|
|
237
|
+
D, d, _mid = self.DecodeB32, True, _2mid
|
|
238
|
+
try:
|
|
239
|
+
for j, c in enumerate(geohash.lower()):
|
|
240
|
+
i = D[c]
|
|
241
|
+
for m in mask5:
|
|
242
|
+
if d: # longitude
|
|
243
|
+
a = _mid(e, w)
|
|
244
|
+
if (i & m):
|
|
245
|
+
w = a
|
|
246
|
+
else:
|
|
247
|
+
e = a
|
|
248
|
+
else: # latitude
|
|
249
|
+
a = _mid(n, s)
|
|
250
|
+
if (i & m):
|
|
251
|
+
s = a
|
|
252
|
+
else:
|
|
253
|
+
n = a
|
|
254
|
+
d = not d
|
|
255
|
+
except KeyError:
|
|
256
|
+
c = _MODS.streprs.Fmt.INDEX(repr(c), j)
|
|
257
|
+
raise GeohashError(geohash=geohash, len=nc, txt=c)
|
|
258
|
+
return s, w, n, e
|
|
259
|
+
|
|
260
|
+
_GH = _GH() # PYCHOK singleton
|
|
141
261
|
|
|
142
262
|
|
|
143
263
|
class Geohash(Str):
|
|
144
264
|
'''Geohash class, a named C{str}.
|
|
145
265
|
'''
|
|
146
266
|
# no str.__init__ in Python 3
|
|
147
|
-
def __new__(cls,
|
|
148
|
-
'''New L{Geohash} from an other L{Geohash} instance or C{str}
|
|
149
|
-
or from a
|
|
150
|
-
|
|
151
|
-
@arg
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
267
|
+
def __new__(cls, lat_ghll, lon=None, precision=None, eps=EPS, **name):
|
|
268
|
+
'''New L{Geohash} from an other L{Geohash} instance or geohash C{str}
|
|
269
|
+
or from a lat- and longitude.
|
|
270
|
+
|
|
271
|
+
@arg lat_ghll: Latitude (C{degrees90}), a geohash (L{Geohash},
|
|
272
|
+
C{str}) or a location (C{LatLon}, C{LatLon*Tuple}).
|
|
273
|
+
@kwarg lon: Logitude (C{degrees180)}, required if B{C{lat_ghll}}
|
|
274
|
+
is C{degrees90}, ignored otherwise.
|
|
275
|
+
@kwarg precision: The desired geohash length (C{int} 1..12) or
|
|
276
|
+
C{None} or C{0}, see L{encode<pygeodesy.geohash.encode>}.
|
|
277
|
+
@kwarg eps: Optional inference tolerance (C{degrees}), see
|
|
278
|
+
L{encode<pygeodesy.geohash.encode>}.
|
|
155
279
|
@kwarg name: Optional C{B{name}=NN} (C{str}).
|
|
156
280
|
|
|
157
281
|
@return: New L{Geohash}.
|
|
158
282
|
|
|
159
|
-
@raise GeohashError:
|
|
283
|
+
@raise GeohashError: Invalid B{C{lat_ghll}}.
|
|
160
284
|
|
|
161
|
-
@raise
|
|
162
|
-
'''
|
|
163
|
-
ll = None
|
|
285
|
+
@raise RangeError: Invalid B{C{lat_gll}} or B{C{lon}}.
|
|
164
286
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
287
|
+
@raise TypeError: Invalid B{C{lat_ghll}}.
|
|
288
|
+
'''
|
|
289
|
+
if lon is None:
|
|
290
|
+
if isinstance(lat_ghll, Geohash):
|
|
291
|
+
gh, ll = str(lat_ghll), lat_ghll.latlon
|
|
292
|
+
elif isstr(lat_ghll): # "lat, lon" or "geohash"
|
|
293
|
+
ll = lat_ghll.replace(_COMMA_, _SPACE_).split()
|
|
294
|
+
if len(ll) > 1:
|
|
295
|
+
gh, ll = _GH.encode2(ll[0], ll[1], precision, eps)
|
|
296
|
+
else:
|
|
297
|
+
gh, ll = lat_ghll.lower(), None
|
|
298
|
+
_ = _GH.swne4(gh, mask5=()) # validate
|
|
299
|
+
else: # assume LatLon
|
|
300
|
+
try:
|
|
301
|
+
gh, ll = _GH.encode2(lat_ghll.lat, lat_ghll.lon, precision, eps)
|
|
302
|
+
except AttributeError:
|
|
303
|
+
raise _xStrError(Geohash, ghll=lat_ghll, Error=GeohashError)
|
|
304
|
+
else:
|
|
305
|
+
gh, ll = _GH.encode2(lat_ghll, lon, precision, eps)
|
|
306
|
+
|
|
307
|
+
self = Str.__new__(cls, gh, name=_name__(name, _or_nameof=lat_ghll))
|
|
183
308
|
self._latlon = ll
|
|
184
309
|
return self
|
|
185
310
|
|
|
@@ -205,7 +330,7 @@ class Geohash(Str):
|
|
|
205
330
|
if D not in _GH.Neighbors:
|
|
206
331
|
raise GeohashError(direction=direction)
|
|
207
332
|
|
|
208
|
-
e = 1
|
|
333
|
+
e = len(self) & 1 # int(isodd(len(self)))
|
|
209
334
|
|
|
210
335
|
c = self[-1:] # last hash char
|
|
211
336
|
i = _GH.Neighbors[D][e].find(c)
|
|
@@ -221,7 +346,7 @@ class Geohash(Str):
|
|
|
221
346
|
if n:
|
|
222
347
|
n = _DOT_(n, D)
|
|
223
348
|
# append letter for direction to parent
|
|
224
|
-
return Geohash(p + _GH.
|
|
349
|
+
return Geohash(p + _GH.EncodeB32[i], name=n)
|
|
225
350
|
|
|
226
351
|
@Property_RO
|
|
227
352
|
def _bounds(self):
|
|
@@ -270,7 +395,7 @@ class Geohash(Str):
|
|
|
270
395
|
for n in range(n):
|
|
271
396
|
if self[n] != other[n]:
|
|
272
397
|
break
|
|
273
|
-
return _GH.Sizes[n]
|
|
398
|
+
return _GH.Sizes[n].radius
|
|
274
399
|
|
|
275
400
|
@deprecated_method
|
|
276
401
|
def distance1To(self, other): # PYCHOK no cover
|
|
@@ -383,13 +508,26 @@ class Geohash(Str):
|
|
|
383
508
|
return len(self)
|
|
384
509
|
|
|
385
510
|
@Property_RO
|
|
511
|
+
def resolution2(self):
|
|
512
|
+
'''Get the I{lon-} and I{latitudinal} resolution of this cell
|
|
513
|
+
in a L{Resolutions2Tuple}C{(res1, res2)}, both in C{degrees}.
|
|
514
|
+
'''
|
|
515
|
+
return resolution2(self.precision, self.precision)
|
|
516
|
+
|
|
517
|
+
@deprecated_property_RO
|
|
386
518
|
def sizes(self):
|
|
387
|
-
'''
|
|
388
|
-
|
|
519
|
+
'''DEPRECATED on 2024.07.28, use property C{Geohash.sizes3}.'''
|
|
520
|
+
t = self.sizes3
|
|
521
|
+
return _GH._LatLon2Tuple(t.height, t.width, name=t.name)
|
|
522
|
+
|
|
523
|
+
@Property_RO
|
|
524
|
+
def sizes3(self):
|
|
525
|
+
'''Get the lat-, longitudinal and radial size of this cell in
|
|
526
|
+
a L{Sizes3Tuple}C{(height, width, radius)}, all in C{meter}.
|
|
389
527
|
'''
|
|
390
528
|
z = _GH.Sizes
|
|
391
|
-
n = min(
|
|
392
|
-
return
|
|
529
|
+
n = min(max(self.precision, 1), len(z) - 1)
|
|
530
|
+
return Sizes3Tuple(z[n], name=self.name)
|
|
393
531
|
|
|
394
532
|
def toLatLon(self, LatLon=None, **LatLon_kwds):
|
|
395
533
|
'''Return (the approximate center of) this geohash cell
|
|
@@ -423,18 +561,6 @@ class Geohash(Str):
|
|
|
423
561
|
'''
|
|
424
562
|
return self._distanceTo(_formy.vincentys, other, **radius_wrap)
|
|
425
563
|
|
|
426
|
-
@Property_RO
|
|
427
|
-
def N(self):
|
|
428
|
-
'''Get the cell North of this (L{Geohash}).
|
|
429
|
-
'''
|
|
430
|
-
return self.adjacent(_N_)
|
|
431
|
-
|
|
432
|
-
@Property_RO
|
|
433
|
-
def S(self):
|
|
434
|
-
'''Get the cell South of this (L{Geohash}).
|
|
435
|
-
'''
|
|
436
|
-
return self.adjacent(_S_)
|
|
437
|
-
|
|
438
564
|
@Property_RO
|
|
439
565
|
def E(self):
|
|
440
566
|
'''Get the cell East of this (L{Geohash}).
|
|
@@ -442,10 +568,10 @@ class Geohash(Str):
|
|
|
442
568
|
return self.adjacent(_E_)
|
|
443
569
|
|
|
444
570
|
@Property_RO
|
|
445
|
-
def
|
|
446
|
-
'''Get the cell
|
|
571
|
+
def N(self):
|
|
572
|
+
'''Get the cell North of this (L{Geohash}).
|
|
447
573
|
'''
|
|
448
|
-
return self.adjacent(
|
|
574
|
+
return self.adjacent(_N_)
|
|
449
575
|
|
|
450
576
|
@Property_RO
|
|
451
577
|
def NE(self):
|
|
@@ -459,6 +585,12 @@ class Geohash(Str):
|
|
|
459
585
|
'''
|
|
460
586
|
return self.N.W
|
|
461
587
|
|
|
588
|
+
@Property_RO
|
|
589
|
+
def S(self):
|
|
590
|
+
'''Get the cell South of this (L{Geohash}).
|
|
591
|
+
'''
|
|
592
|
+
return self.adjacent(_S_)
|
|
593
|
+
|
|
462
594
|
@Property_RO
|
|
463
595
|
def SE(self):
|
|
464
596
|
'''Get the cell SouthEast of this (L{Geohash}).
|
|
@@ -471,6 +603,124 @@ class Geohash(Str):
|
|
|
471
603
|
'''
|
|
472
604
|
return self.S.W
|
|
473
605
|
|
|
606
|
+
@Property_RO
|
|
607
|
+
def W(self):
|
|
608
|
+
'''Get the cell West of this (L{Geohash}).
|
|
609
|
+
'''
|
|
610
|
+
return self.adjacent(_W_)
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
class Geohashed(object):
|
|
614
|
+
'''A cache of en- and decoded geohashes of one precision.
|
|
615
|
+
'''
|
|
616
|
+
_nn = None, # 1-tuple
|
|
617
|
+
|
|
618
|
+
def __init__(self, precision, ndigits=None):
|
|
619
|
+
'''New L{Geohashed} cache.
|
|
620
|
+
|
|
621
|
+
@arg precision: The geohash encoded length (C{int}, 1..12).
|
|
622
|
+
@kwarg ndigits: Optional number of digits to round C{lat}
|
|
623
|
+
and C{lon} to cache keys (C{int}, typically
|
|
624
|
+
C{B{ndigits}=B{precision}}) or C{None} for
|
|
625
|
+
no rounding.
|
|
626
|
+
'''
|
|
627
|
+
self._p = _2Precision(precision)
|
|
628
|
+
if ndigits is None:
|
|
629
|
+
self._ab2 = self._ab2float
|
|
630
|
+
else:
|
|
631
|
+
self._ab2 = self._ab2round
|
|
632
|
+
n = Int(ndigits=ndigits)
|
|
633
|
+
self._nn = n, n
|
|
634
|
+
self.clear()
|
|
635
|
+
|
|
636
|
+
def __len__(self):
|
|
637
|
+
'''Return the number of I{unigue} geohashes (C{int}).
|
|
638
|
+
'''
|
|
639
|
+
d = self._d
|
|
640
|
+
d = set(d.keys())
|
|
641
|
+
n = len(d)
|
|
642
|
+
for e in self._e.values():
|
|
643
|
+
e = set(e.values())
|
|
644
|
+
n += len(e - d)
|
|
645
|
+
return n
|
|
646
|
+
|
|
647
|
+
def _ab2(self, *ll): # overwritten
|
|
648
|
+
'''(INTERNAL) Make encoded keys C{a, b}.
|
|
649
|
+
'''
|
|
650
|
+
return ll
|
|
651
|
+
|
|
652
|
+
def _ab2float(self, *ll):
|
|
653
|
+
'''(INTERNAL) Make encoded keys C{a, b}.
|
|
654
|
+
'''
|
|
655
|
+
return map(float, ll)
|
|
656
|
+
|
|
657
|
+
def _ab2round(self, *ll):
|
|
658
|
+
'''(INTERNAL) Make encoded keys C{a, b}.
|
|
659
|
+
'''
|
|
660
|
+
return map(round, ll, self._nn) # strict=True
|
|
661
|
+
|
|
662
|
+
def clear(self):
|
|
663
|
+
'''Clear the C{en-} and C{decoded} cache.
|
|
664
|
+
'''
|
|
665
|
+
self._e = {}
|
|
666
|
+
self._d = {}
|
|
667
|
+
|
|
668
|
+
def decoded(self, geohash, encoded=False):
|
|
669
|
+
'''Get and cache the C{(lat, lon)} for C{geohash}, see L{decode<pygeodesy.geohash.decode>}.
|
|
670
|
+
|
|
671
|
+
@kwarg encoded: If C{True}, cache the result as C{encoded}.
|
|
672
|
+
|
|
673
|
+
@return: The C{(lat, lon}) pair for C{geohash}.
|
|
674
|
+
'''
|
|
675
|
+
try:
|
|
676
|
+
ll = self._d[geohash]
|
|
677
|
+
except KeyError:
|
|
678
|
+
self._d[geohash] = ll = _GH.decode2(geohash)
|
|
679
|
+
if encoded:
|
|
680
|
+
a, b = self._ab2(*ll)
|
|
681
|
+
try:
|
|
682
|
+
_ = self._e[b][a]
|
|
683
|
+
except KeyError:
|
|
684
|
+
self._e.setdefault(b, {})[a] = geohash
|
|
685
|
+
return ll
|
|
686
|
+
|
|
687
|
+
def encoded(self, lat, lon, decoded=False):
|
|
688
|
+
'''Get and cache the C{geohash} for C{(lat, lon)}, see L{encode<pygeodesy.geohash.encode>}.
|
|
689
|
+
|
|
690
|
+
@kwarg decoded: If C{True}, cache the result as C{decoded}.
|
|
691
|
+
|
|
692
|
+
@return: The C{geohash} for pair C{(lat, lon}).
|
|
693
|
+
'''
|
|
694
|
+
lat, lon = ll = _2fll(lat, lon)
|
|
695
|
+
a, b = self._ab2(*ll)
|
|
696
|
+
try:
|
|
697
|
+
gh = self._e[b][a]
|
|
698
|
+
except KeyError:
|
|
699
|
+
gh = _GH.encode(lat, lon, self._p, 0)
|
|
700
|
+
self._e.setdefault(b, {})[a] = gh
|
|
701
|
+
if decoded and gh not in self._d:
|
|
702
|
+
self._d[gh] = ll
|
|
703
|
+
return gh
|
|
704
|
+
|
|
705
|
+
@property_RO
|
|
706
|
+
def len2(self):
|
|
707
|
+
'''Return 2-tuple C{(lencoded, ldecoded)} with the C{len}gths of the
|
|
708
|
+
C{en-} and C{decoded} cache.
|
|
709
|
+
'''
|
|
710
|
+
return sum(len(e) for e in self._e.values()), len(self._d)
|
|
711
|
+
|
|
712
|
+
@Property_RO
|
|
713
|
+
def ndigits(self):
|
|
714
|
+
'''Get the rounding (C{int} or C{None}).
|
|
715
|
+
'''
|
|
716
|
+
return self._nn[0]
|
|
717
|
+
|
|
718
|
+
@Property_RO
|
|
719
|
+
def precision(self):
|
|
720
|
+
'''Get the C{precision} (C{int}).
|
|
721
|
+
'''
|
|
722
|
+
return self._p
|
|
723
|
+
|
|
474
724
|
|
|
475
725
|
class GeohashError(_ValueError):
|
|
476
726
|
'''Geohash encode, decode or other L{Geohash} issue.
|
|
@@ -493,6 +743,34 @@ _Neighbors8Defaults = dict(zip(Neighbors8Dict._Keys_, (None,) *
|
|
|
493
743
|
len(Neighbors8Dict._Keys_))) # XXX frozendict
|
|
494
744
|
|
|
495
745
|
|
|
746
|
+
class Resolutions2Tuple(_NamedTuple):
|
|
747
|
+
'''2-Tuple C{(res1, res2)} with the primary I{(longitudinal)} and
|
|
748
|
+
secondary I{(latitudinal)} resolution, both in C{degrees}.
|
|
749
|
+
'''
|
|
750
|
+
_Names_ = ('res1', 'res2')
|
|
751
|
+
_Units_ = ( Degrees_, Degrees_)
|
|
752
|
+
|
|
753
|
+
@property_RO
|
|
754
|
+
def lat(self):
|
|
755
|
+
'''Get the secondary, latitudinal resolution (C{degrees}).
|
|
756
|
+
'''
|
|
757
|
+
return self[1]
|
|
758
|
+
|
|
759
|
+
@property_RO
|
|
760
|
+
def lon(self):
|
|
761
|
+
'''Get the primary, longitudinal resolution (C{degrees}).
|
|
762
|
+
'''
|
|
763
|
+
return self[0]
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
class Sizes3Tuple(_NamedTuple):
|
|
767
|
+
'''3-Tuple C{(height, width, radius)} with latitudinal C{height},
|
|
768
|
+
longitudinal C{width} and area C{radius}, all in C{meter}.
|
|
769
|
+
'''
|
|
770
|
+
_Names_ = (_height_, _width_, _radius_)
|
|
771
|
+
_Units_ = ( Meter, Meter, Meter)
|
|
772
|
+
|
|
773
|
+
|
|
496
774
|
def bounds(geohash, LatLon=None, **LatLon_kwds):
|
|
497
775
|
'''Returns the lower-left SW and upper-right NE corners of a geohash.
|
|
498
776
|
|
|
@@ -510,43 +788,11 @@ def bounds(geohash, LatLon=None, **LatLon_kwds):
|
|
|
510
788
|
|
|
511
789
|
@raise GeohashError: Invalid or C{null} B{C{geohash}}.
|
|
512
790
|
'''
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
raise GeohashError(geohash=geohash)
|
|
516
|
-
|
|
517
|
-
s, w, n, e = _GH.Bounds4
|
|
518
|
-
try:
|
|
519
|
-
d, _avg = True, favg
|
|
520
|
-
for c in gh.lower():
|
|
521
|
-
i = _GH.DecodedBase32[c]
|
|
522
|
-
for m in (16, 8, 4, 2, 1):
|
|
523
|
-
if d: # longitude
|
|
524
|
-
a = _avg(w, e)
|
|
525
|
-
if (i & m):
|
|
526
|
-
w = a
|
|
527
|
-
else:
|
|
528
|
-
e = a
|
|
529
|
-
else: # latitude
|
|
530
|
-
a = _avg(s, n)
|
|
531
|
-
if (i & m):
|
|
532
|
-
s = a
|
|
533
|
-
else:
|
|
534
|
-
n = a
|
|
535
|
-
d = not d
|
|
536
|
-
except KeyError:
|
|
537
|
-
raise GeohashError(geohash=geohash)
|
|
538
|
-
|
|
539
|
-
return _2bounds(LatLon, LatLon_kwds, s, w, n, e,
|
|
791
|
+
swne = _GH.swne4(geohash)
|
|
792
|
+
return _2bounds(LatLon, LatLon_kwds, *swne,
|
|
540
793
|
name=nameof(geohash)) # _or_nameof=geohash
|
|
541
794
|
|
|
542
795
|
|
|
543
|
-
def _bounds3(geohash):
|
|
544
|
-
'''(INTERNAL) Return 3-tuple C{(bounds, height, width)}.
|
|
545
|
-
'''
|
|
546
|
-
b = bounds(geohash)
|
|
547
|
-
return b, (b.latN - b.latS), (b.lonE - b.lonW)
|
|
548
|
-
|
|
549
|
-
|
|
550
796
|
def decode(geohash):
|
|
551
797
|
'''Decode a geohash to lat-/longitude of the (approximate
|
|
552
798
|
centre of) geohash cell to reasonable precision.
|
|
@@ -560,13 +806,10 @@ def decode(geohash):
|
|
|
560
806
|
|
|
561
807
|
@raise GeohashError: Invalid or null B{C{geohash}}.
|
|
562
808
|
'''
|
|
563
|
-
b, h, w = _bounds3(geohash)
|
|
564
|
-
lat, lon = _2center(b)
|
|
565
|
-
|
|
566
809
|
# round to near centre without excessive precision to
|
|
567
810
|
# ⌊2-log10(Δ°)⌋ decimal places, strip trailing zeros
|
|
568
|
-
|
|
569
|
-
|
|
811
|
+
swne = _GH.swne4(geohash)
|
|
812
|
+
return _2latlon(*swne, fstr=_MODS.streprs.fstr)
|
|
570
813
|
|
|
571
814
|
|
|
572
815
|
def decode2(geohash, LatLon=None, **LatLon_kwds):
|
|
@@ -587,14 +830,20 @@ def decode2(geohash, LatLon=None, **LatLon_kwds):
|
|
|
587
830
|
|
|
588
831
|
@raise GeohashError: Invalid or null B{C{geohash}}.
|
|
589
832
|
'''
|
|
590
|
-
|
|
591
|
-
r
|
|
833
|
+
ll = _GH.decode2(geohash)
|
|
834
|
+
r = LatLon2Tuple(ll) if LatLon is None else \
|
|
835
|
+
LatLon( *ll, **LatLon_kwds)
|
|
592
836
|
return _xnamed(r, name__=decode2)
|
|
593
837
|
|
|
594
838
|
|
|
839
|
+
@deprecated_function
|
|
595
840
|
def decode_error(geohash):
|
|
596
|
-
'''
|
|
597
|
-
|
|
841
|
+
'''DEPRECATED on 2024.07.28, use L{geohash.decode_error2}.'''
|
|
842
|
+
return decode_error2(geohash)
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
def decode_error2(geohash):
|
|
846
|
+
'''Return the lat- and longitude decoding error for a geohash.
|
|
598
847
|
|
|
599
848
|
@arg geohash: To be decoded (L{Geohash}).
|
|
600
849
|
|
|
@@ -606,9 +855,9 @@ def decode_error(geohash):
|
|
|
606
855
|
|
|
607
856
|
@raise GeohashError: Invalid or null B{C{geohash}}.
|
|
608
857
|
'''
|
|
609
|
-
|
|
610
|
-
return LatLon2Tuple(
|
|
611
|
-
w * _0_5) #
|
|
858
|
+
s, w, n, e = _GH.swne4(geohash)
|
|
859
|
+
return LatLon2Tuple((n - s) * _0_5, # lat error
|
|
860
|
+
(e - w) * _0_5) # lon error
|
|
612
861
|
|
|
613
862
|
|
|
614
863
|
def distance_(geohash1, geohash2):
|
|
@@ -643,67 +892,23 @@ def distance3(geohash1, geohash2):
|
|
|
643
892
|
return haversine_(geohash1, geohash2)
|
|
644
893
|
|
|
645
894
|
|
|
646
|
-
def encode(lat, lon, precision=None):
|
|
895
|
+
def encode(lat, lon, precision=None, eps=EPS):
|
|
647
896
|
'''Encode a lat-/longitude as a C{geohash}, either to the specified
|
|
648
|
-
precision or if not provided, to an
|
|
649
|
-
precision.
|
|
897
|
+
precision or if not provided, to an inferred precision.
|
|
650
898
|
|
|
651
|
-
@arg lat: Latitude (C{
|
|
652
|
-
@arg lon: Longitude (C{
|
|
653
|
-
@kwarg precision:
|
|
654
|
-
|
|
899
|
+
@arg lat: Latitude (C{degrees90}).
|
|
900
|
+
@arg lon: Longitude (C{degrees180}).
|
|
901
|
+
@kwarg precision: The desired geohash length (C{int} 1..12) or
|
|
902
|
+
C{None} or C{0} for inferred.
|
|
903
|
+
@kwarg eps: Optional inference tolerance (C{degrees}), ignored
|
|
904
|
+
if B{C{precision}} is not C{None} or C{0}.
|
|
655
905
|
|
|
656
906
|
@return: The C{geohash} (C{str}).
|
|
657
907
|
|
|
658
908
|
@raise GeohashError: Invalid B{C{lat}}, B{C{lon}} or B{C{precision}}.
|
|
659
909
|
'''
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
if precision is None:
|
|
663
|
-
# Infer precision by refining geohash until
|
|
664
|
-
# it matches precision of supplied lat/lon.
|
|
665
|
-
for p in range(1, _MaxPrec + 1):
|
|
666
|
-
gh = encode(lat, lon, p)
|
|
667
|
-
ll = map2(float, decode(gh))
|
|
668
|
-
if fabs(lat - ll[0]) < EPS and \
|
|
669
|
-
fabs(lon - ll[1]) < EPS:
|
|
670
|
-
return gh
|
|
671
|
-
p = _MaxPrec
|
|
672
|
-
else:
|
|
673
|
-
p = Precision_(precision, Error=GeohashError, low=1, high=_MaxPrec)
|
|
674
|
-
|
|
675
|
-
b = i = 0
|
|
676
|
-
d, gh = True, []
|
|
677
|
-
s, w, n, e = _GH.Bounds4
|
|
678
|
-
|
|
679
|
-
_avg = favg
|
|
680
|
-
while p > 0:
|
|
681
|
-
i += i
|
|
682
|
-
if d: # bisect longitude
|
|
683
|
-
m = _avg(e, w)
|
|
684
|
-
if lon < m:
|
|
685
|
-
e = m
|
|
686
|
-
else:
|
|
687
|
-
w = m
|
|
688
|
-
i += 1
|
|
689
|
-
else: # bisect latitude
|
|
690
|
-
m = _avg(n, s)
|
|
691
|
-
if lat < m:
|
|
692
|
-
n = m
|
|
693
|
-
else:
|
|
694
|
-
s = m
|
|
695
|
-
i += 1
|
|
696
|
-
d = not d
|
|
697
|
-
|
|
698
|
-
b += 1
|
|
699
|
-
if b == 5:
|
|
700
|
-
# 5 bits gives a character:
|
|
701
|
-
# append it and start over
|
|
702
|
-
gh.append(_GH.GeohashBase32[i])
|
|
703
|
-
b = i = 0
|
|
704
|
-
p -= 1
|
|
705
|
-
|
|
706
|
-
return NN.join(gh)
|
|
910
|
+
gh, _ = _GH.encode2(lat, lon, precision, eps)
|
|
911
|
+
return gh
|
|
707
912
|
|
|
708
913
|
|
|
709
914
|
def equirectangular4(geohash1, geohash2, radius=R_M):
|
|
@@ -799,14 +1004,6 @@ def precision(res1, res2=None):
|
|
|
799
1004
|
return _MaxPrec
|
|
800
1005
|
|
|
801
1006
|
|
|
802
|
-
class Resolutions2Tuple(_NamedTuple):
|
|
803
|
-
'''2-Tuple C{(res1, res2)} with the primary I{(longitudinal)} and
|
|
804
|
-
secondary I{(latitudinal)} resolution, both in C{degrees}.
|
|
805
|
-
'''
|
|
806
|
-
_Names_ = ('res1', 'res2')
|
|
807
|
-
_Units_ = ( Degrees_, Degrees_)
|
|
808
|
-
|
|
809
|
-
|
|
810
1007
|
def resolution2(prec1, prec2=None):
|
|
811
1008
|
'''Determine the (geographic) resolutions of given L{Geohash}
|
|
812
1009
|
precisions.
|
|
@@ -817,38 +1014,37 @@ def resolution2(prec1, prec2=None):
|
|
|
817
1014
|
(C{int} 1..12).
|
|
818
1015
|
|
|
819
1016
|
@return: L{Resolutions2Tuple}C{(res1, res2)} with the
|
|
820
|
-
(geographic) resolutions C{degrees}, where
|
|
821
|
-
B{
|
|
1017
|
+
(geographic) resolutions in C{degrees}, where
|
|
1018
|
+
C{res2 B{is} res1} if no B{C{prec2}} is given.
|
|
822
1019
|
|
|
823
1020
|
@raise GeohashError: Invalid B{C{prec1}} or B{C{prec2}}.
|
|
824
1021
|
|
|
825
1022
|
@see: I{Karney}'s C++ class U{Geohash
|
|
826
1023
|
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Geohash.html>}.
|
|
827
1024
|
'''
|
|
828
|
-
|
|
1025
|
+
lon = _2res(_360_0, prec1=prec1)
|
|
1026
|
+
lat = lon if prec2 is None else \
|
|
1027
|
+
_2res(_180_0, prec2=prec2)
|
|
1028
|
+
return Resolutions2Tuple(lon, lat)
|
|
829
1029
|
|
|
830
|
-
if prec1:
|
|
831
|
-
p = 5 * max(0, min(Int(prec1=prec1, Error=GeohashError), _MaxPrec))
|
|
832
|
-
res1 = res2 = ldexp(res1, -(p - p // 2))
|
|
833
1030
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1031
|
+
@deprecated_function
|
|
1032
|
+
def sizes(geohash):
|
|
1033
|
+
'''DEPRECATED on 2024.07.28, use function L{pygeodesy.geohash.sizes3}.'''
|
|
1034
|
+
t = sizes3(geohash)
|
|
1035
|
+
return _GH._LatLon2Tuple(t.height, t.width, name=t.name)
|
|
837
1036
|
|
|
838
|
-
return Resolutions2Tuple(res1, res2)
|
|
839
1037
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
'''Return the lat- and longitudinal size of this L{Geohash} cell.
|
|
1038
|
+
def sizes3(geohash):
|
|
1039
|
+
'''Return the lat-, longitudinal and radial size of this L{Geohash} cell.
|
|
843
1040
|
|
|
844
1041
|
@arg geohash: Cell for which size are required (L{Geohash} or C{str}).
|
|
845
1042
|
|
|
846
|
-
@return: A L{
|
|
847
|
-
longitudinal width in (C{meter}).
|
|
1043
|
+
@return: A L{Sizes3Tuple}C{(height, width, radius)}, all C{meter}.
|
|
848
1044
|
|
|
849
1045
|
@raise TypeError: The B{C{geohash}} is not a L{Geohash}, C{LatLon} or C{str}.
|
|
850
1046
|
'''
|
|
851
|
-
return _2Geohash(geohash).
|
|
1047
|
+
return _2Geohash(geohash).sizes3
|
|
852
1048
|
|
|
853
1049
|
|
|
854
1050
|
def vincentys_(geohash1, geohash2, **radius_wrap):
|
|
@@ -868,10 +1064,38 @@ def vincentys_(geohash1, geohash2, **radius_wrap):
|
|
|
868
1064
|
return _2Geohash(geohash1).vincentysTo(geohash2, **radius_wrap)
|
|
869
1065
|
|
|
870
1066
|
|
|
871
|
-
__all__ +=
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1067
|
+
__all__ += _ALL_DOCS(bounds, # functions
|
|
1068
|
+
decode, decode2, decode_error2, distance_,
|
|
1069
|
+
encode, equirectangular4, euclidean_, haversine_,
|
|
1070
|
+
neighbors, precision, resolution2, sizes3, vincentys_,
|
|
1071
|
+
decode_error, sizes) # DEPRECATED
|
|
1072
|
+
|
|
1073
|
+
if __name__ == '__main__':
|
|
1074
|
+
|
|
1075
|
+
from pygeodesy.internals import printf, _versions
|
|
1076
|
+
from timeit import timeit
|
|
1077
|
+
|
|
1078
|
+
for f, p in (('encode', _MaxPrec), ('infer', None)):
|
|
1079
|
+
|
|
1080
|
+
def _t(prec=p):
|
|
1081
|
+
i = 0
|
|
1082
|
+
for lat in range(-90, 90, 3):
|
|
1083
|
+
for lon in range(-180, 180, 7):
|
|
1084
|
+
_ = encode(lat, lon, prec)
|
|
1085
|
+
i += 1
|
|
1086
|
+
return i
|
|
1087
|
+
|
|
1088
|
+
i = _t() # prime
|
|
1089
|
+
n = 10
|
|
1090
|
+
t = timeit(_t, number=n) / (i * n)
|
|
1091
|
+
printf('%s %.3f usec, %s', f, t * 1e6, _versions())
|
|
1092
|
+
|
|
1093
|
+
# % python3.12 -m pygeodesy.geohash
|
|
1094
|
+
# encode 10.145 usec, pygeodesy 24.8.4 Python 3.12.4 64bit arm64 macOS 14.5
|
|
1095
|
+
# infer 14.780 usec, pygeodesy 24.8.4 Python 3.12.4 64bit arm64 macOS 14.5
|
|
1096
|
+
# or about 6.56 and 74.12 times faster than pygeodesy 24.7.24 and older:
|
|
1097
|
+
# encode 66.524 usec, pygeodesy 24.7.24 Python 3.12.4 64bit arm64 macOS 14.5
|
|
1098
|
+
# infer 1095.386 usec, pygeodesy 24.7.24 Python 3.12.4 64bit arm64 macOS 14.5
|
|
875
1099
|
|
|
876
1100
|
# **) MIT License
|
|
877
1101
|
#
|