pygeodesy 24.3.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.3.24.dist-info/METADATA +272 -0
- PyGeodesy-24.3.24.dist-info/RECORD +115 -0
- PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
- PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
- pygeodesy/LICENSE +21 -0
- pygeodesy/__init__.py +615 -0
- pygeodesy/__main__.py +103 -0
- pygeodesy/albers.py +867 -0
- pygeodesy/auxilats/_CX_4.py +218 -0
- pygeodesy/auxilats/_CX_6.py +314 -0
- pygeodesy/auxilats/_CX_8.py +475 -0
- pygeodesy/auxilats/__init__.py +54 -0
- pygeodesy/auxilats/__main__.py +86 -0
- pygeodesy/auxilats/auxAngle.py +548 -0
- pygeodesy/auxilats/auxDLat.py +302 -0
- pygeodesy/auxilats/auxDST.py +296 -0
- pygeodesy/auxilats/auxLat.py +848 -0
- pygeodesy/auxilats/auxily.py +272 -0
- pygeodesy/azimuthal.py +1150 -0
- pygeodesy/basics.py +892 -0
- pygeodesy/booleans.py +2031 -0
- pygeodesy/cartesianBase.py +1062 -0
- pygeodesy/clipy.py +704 -0
- pygeodesy/constants.py +516 -0
- pygeodesy/css.py +660 -0
- pygeodesy/datums.py +752 -0
- pygeodesy/deprecated/__init__.py +61 -0
- pygeodesy/deprecated/bases.py +40 -0
- pygeodesy/deprecated/classes.py +262 -0
- pygeodesy/deprecated/consterns.py +54 -0
- pygeodesy/deprecated/datum.py +40 -0
- pygeodesy/deprecated/functions.py +375 -0
- pygeodesy/deprecated/nvector.py +48 -0
- pygeodesy/deprecated/rhumbBase.py +32 -0
- pygeodesy/deprecated/rhumbaux.py +33 -0
- pygeodesy/deprecated/rhumbsolve.py +33 -0
- pygeodesy/deprecated/rhumbx.py +33 -0
- pygeodesy/dms.py +986 -0
- pygeodesy/ecef.py +1348 -0
- pygeodesy/elevations.py +279 -0
- pygeodesy/ellipsoidalBase.py +1224 -0
- pygeodesy/ellipsoidalBaseDI.py +913 -0
- pygeodesy/ellipsoidalExact.py +343 -0
- pygeodesy/ellipsoidalGeodSolve.py +343 -0
- pygeodesy/ellipsoidalKarney.py +403 -0
- pygeodesy/ellipsoidalNvector.py +685 -0
- pygeodesy/ellipsoidalVincenty.py +590 -0
- pygeodesy/ellipsoids.py +2476 -0
- pygeodesy/elliptic.py +1198 -0
- pygeodesy/epsg.py +243 -0
- pygeodesy/errors.py +804 -0
- pygeodesy/etm.py +1190 -0
- pygeodesy/fmath.py +1013 -0
- pygeodesy/formy.py +1818 -0
- pygeodesy/frechet.py +865 -0
- pygeodesy/fstats.py +760 -0
- pygeodesy/fsums.py +1898 -0
- pygeodesy/gars.py +358 -0
- pygeodesy/geodesicw.py +581 -0
- pygeodesy/geodesicx/_C4_24.py +1699 -0
- pygeodesy/geodesicx/_C4_27.py +2395 -0
- pygeodesy/geodesicx/_C4_30.py +3301 -0
- pygeodesy/geodesicx/__init__.py +48 -0
- pygeodesy/geodesicx/__main__.py +91 -0
- pygeodesy/geodesicx/gx.py +1382 -0
- pygeodesy/geodesicx/gxarea.py +535 -0
- pygeodesy/geodesicx/gxbases.py +154 -0
- pygeodesy/geodesicx/gxline.py +669 -0
- pygeodesy/geodsolve.py +426 -0
- pygeodesy/geohash.py +914 -0
- pygeodesy/geoids.py +1884 -0
- pygeodesy/hausdorff.py +892 -0
- pygeodesy/heights.py +1155 -0
- pygeodesy/interns.py +687 -0
- pygeodesy/iters.py +545 -0
- pygeodesy/karney.py +919 -0
- pygeodesy/ktm.py +633 -0
- pygeodesy/latlonBase.py +1766 -0
- pygeodesy/lazily.py +960 -0
- pygeodesy/lcc.py +684 -0
- pygeodesy/ltp.py +1107 -0
- pygeodesy/ltpTuples.py +1563 -0
- pygeodesy/mgrs.py +721 -0
- pygeodesy/named.py +1324 -0
- pygeodesy/namedTuples.py +683 -0
- pygeodesy/nvectorBase.py +695 -0
- pygeodesy/osgr.py +781 -0
- pygeodesy/points.py +1686 -0
- pygeodesy/props.py +628 -0
- pygeodesy/resections.py +1048 -0
- pygeodesy/rhumb/__init__.py +46 -0
- pygeodesy/rhumb/aux_.py +397 -0
- pygeodesy/rhumb/bases.py +1148 -0
- pygeodesy/rhumb/ekx.py +563 -0
- pygeodesy/rhumb/solve.py +572 -0
- pygeodesy/simplify.py +647 -0
- pygeodesy/solveBase.py +472 -0
- pygeodesy/sphericalBase.py +724 -0
- pygeodesy/sphericalNvector.py +1264 -0
- pygeodesy/sphericalTrigonometry.py +1447 -0
- pygeodesy/streprs.py +627 -0
- pygeodesy/trf.py +2079 -0
- pygeodesy/triaxials.py +1484 -0
- pygeodesy/units.py +969 -0
- pygeodesy/unitsBase.py +349 -0
- pygeodesy/ups.py +538 -0
- pygeodesy/utily.py +1231 -0
- pygeodesy/utm.py +762 -0
- pygeodesy/utmups.py +318 -0
- pygeodesy/utmupsBase.py +517 -0
- pygeodesy/vector2d.py +785 -0
- pygeodesy/vector3d.py +968 -0
- pygeodesy/vector3dBase.py +1049 -0
- pygeodesy/webmercator.py +383 -0
- pygeodesy/wgrs.py +439 -0
pygeodesy/geohash.py
ADDED
|
@@ -0,0 +1,914 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''Geohash en-/decoding.
|
|
5
|
+
|
|
6
|
+
Classes L{Geohash} and L{GeohashError} and several functions to encode,
|
|
7
|
+
decode and inspect I{geohashes}.
|
|
8
|
+
|
|
9
|
+
Transcoded from JavaScript originals by I{(C) Chris Veness 2011-2015}
|
|
10
|
+
and published under the same MIT Licence**, see U{Geohashes
|
|
11
|
+
<https://www.Movable-Type.co.UK/scripts/geohash.html>}.
|
|
12
|
+
|
|
13
|
+
See also U{Geohash<https://WikiPedia.org/wiki/Geohash>}, U{Geohash
|
|
14
|
+
<https://GitHub.com/vinsci/geohash>}, U{PyGeohash
|
|
15
|
+
<https://PyPI.org/project/pygeohash>} and U{Geohash-Javascript
|
|
16
|
+
<https://GitHub.com/DaveTroy/geohash-js>}.
|
|
17
|
+
'''
|
|
18
|
+
|
|
19
|
+
from pygeodesy.basics import isodd, isstr, map2
|
|
20
|
+
from pygeodesy.constants import EPS, R_M, _floatuple, _0_0, _0_5, _180_0, \
|
|
21
|
+
_360_0, _90_0, _N_90_0, _N_180_0 # PYCHOK used!
|
|
22
|
+
from pygeodesy.dms import parse3llh # parseDMS2
|
|
23
|
+
from pygeodesy.errors import _ValueError, _xkwds
|
|
24
|
+
from pygeodesy.fmath import favg
|
|
25
|
+
# from pygeodesy import formy as _formy # _MODS
|
|
26
|
+
from pygeodesy.interns import NN, _COMMA_, _DOT_, _E_, _N_, _NE_, _NW_, \
|
|
27
|
+
_S_, _SE_, _SW_, _W_
|
|
28
|
+
from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _ALL_OTHER
|
|
29
|
+
from pygeodesy.named import _NamedDict, _NamedTuple, nameof, _xnamed
|
|
30
|
+
from pygeodesy.namedTuples import Bounds2Tuple, Bounds4Tuple, \
|
|
31
|
+
LatLon2Tuple, PhiLam2Tuple
|
|
32
|
+
from pygeodesy.props import deprecated_function, deprecated_method, \
|
|
33
|
+
deprecated_property_RO, Property_RO, property_RO
|
|
34
|
+
from pygeodesy.streprs import fstr
|
|
35
|
+
from pygeodesy.units import Degrees_, Int, Lat, Lon, Precision_, Str, \
|
|
36
|
+
_xStrError
|
|
37
|
+
|
|
38
|
+
from math import fabs, ldexp, log10, radians
|
|
39
|
+
|
|
40
|
+
__all__ = _ALL_LAZY.geohash
|
|
41
|
+
__version__ = '23.12.18'
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class _GH(object):
|
|
45
|
+
'''(INTERNAL) Lazily defined constants.
|
|
46
|
+
'''
|
|
47
|
+
def _4d(self, n, e, s, w): # helper
|
|
48
|
+
return dict(N=(n, e), S=(s, w),
|
|
49
|
+
E=(e, n), W=(w, s))
|
|
50
|
+
|
|
51
|
+
@Property_RO
|
|
52
|
+
def Borders(self):
|
|
53
|
+
return self._4d('prxz', 'bcfguvyz', '028b', '0145hjnp')
|
|
54
|
+
|
|
55
|
+
Bounds4 = (_N_90_0, _N_180_0, _90_0, _180_0)
|
|
56
|
+
|
|
57
|
+
@Property_RO
|
|
58
|
+
def DecodedBase32(self): # inverse GeohashBase32 map
|
|
59
|
+
return dict((c, i) for i, c in enumerate(self.GeohashBase32))
|
|
60
|
+
|
|
61
|
+
# Geohash-specific base32 map
|
|
62
|
+
GeohashBase32 = '0123456789bcdefghjkmnpqrstuvwxyz' # no a, i, j and o
|
|
63
|
+
|
|
64
|
+
@Property_RO
|
|
65
|
+
def Neighbors(self):
|
|
66
|
+
return self._4d('p0r21436x8zb9dcf5h7kjnmqesgutwvy',
|
|
67
|
+
'bc01fg45238967deuvhjyznpkmstqrwx',
|
|
68
|
+
'14365h7k9dcfesgujnmqp0r2twvyx8zb',
|
|
69
|
+
'238967debc01fg45kmstqrwxuvhjyznp')
|
|
70
|
+
|
|
71
|
+
@Property_RO
|
|
72
|
+
def Sizes(self): # lat-, lon and radial size (in meter)
|
|
73
|
+
# ... where radial = sqrt(latSize * lonWidth / PI)
|
|
74
|
+
return (_floatuple(20032e3, 20000e3, 11292815.096), # 0
|
|
75
|
+
_floatuple( 5003e3, 5000e3, 2821794.075), # 1
|
|
76
|
+
_floatuple( 650e3, 1225e3, 503442.397), # 2
|
|
77
|
+
_floatuple( 156e3, 156e3, 88013.575), # 3
|
|
78
|
+
_floatuple( 19500, 39100, 15578.683), # 4
|
|
79
|
+
_floatuple( 4890, 4890, 2758.887), # 5
|
|
80
|
+
_floatuple( 610, 1220, 486.710), # 6
|
|
81
|
+
_floatuple( 153, 153, 86.321), # 7
|
|
82
|
+
_floatuple( 19.1, 38.2, 15.239), # 8
|
|
83
|
+
_floatuple( 4.77, 4.77, 2.691), # 9
|
|
84
|
+
_floatuple( 0.596, 1.19, 0.475), # 10
|
|
85
|
+
_floatuple( 0.149, 0.149, 0.084), # 11
|
|
86
|
+
_floatuple( 0.0186, 0.0372, 0.015)) # 12 _MaxPrec
|
|
87
|
+
|
|
88
|
+
_GH = _GH() # PYCHOK singleton
|
|
89
|
+
_MaxPrec = 12
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _2bounds(LatLon, LatLon_kwds, s, w, n, e, name=NN):
|
|
93
|
+
'''(INTERNAL) Return SW and NE bounds.
|
|
94
|
+
'''
|
|
95
|
+
if LatLon is None:
|
|
96
|
+
r = Bounds4Tuple(s, w, n, e, name=name)
|
|
97
|
+
else:
|
|
98
|
+
sw = _xnamed(LatLon(s, w, **LatLon_kwds), name)
|
|
99
|
+
ne = _xnamed(LatLon(n, e, **LatLon_kwds), name)
|
|
100
|
+
r = Bounds2Tuple(sw, ne, name=name)
|
|
101
|
+
return r # _xnamed(r, name)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _2center(bounds):
|
|
105
|
+
'''(INTERNAL) Return the C{bounds} center.
|
|
106
|
+
'''
|
|
107
|
+
return (favg(bounds.latN, bounds.latS),
|
|
108
|
+
favg(bounds.lonE, bounds.lonW))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _2fll(lat, lon, *unused):
|
|
112
|
+
'''(INTERNAL) Convert lat, lon to 2-tuple of floats.
|
|
113
|
+
'''
|
|
114
|
+
# lat, lon = parseDMS2(lat, lon)
|
|
115
|
+
return (Lat(lat, Error=GeohashError),
|
|
116
|
+
Lon(lon, Error=GeohashError))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _2Geohash(geohash):
|
|
120
|
+
'''(INTERNAL) Check or create a Geohash instance.
|
|
121
|
+
'''
|
|
122
|
+
return geohash if isinstance(geohash, Geohash) else \
|
|
123
|
+
Geohash(geohash)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _2geostr(geohash):
|
|
127
|
+
'''(INTERNAL) Check a geohash string.
|
|
128
|
+
'''
|
|
129
|
+
try:
|
|
130
|
+
if not (0 < len(geohash) <= _MaxPrec):
|
|
131
|
+
raise ValueError
|
|
132
|
+
geostr = geohash.lower()
|
|
133
|
+
for c in geostr:
|
|
134
|
+
if c not in _GH.DecodedBase32:
|
|
135
|
+
raise ValueError
|
|
136
|
+
return geostr
|
|
137
|
+
except (AttributeError, TypeError, ValueError) as x:
|
|
138
|
+
raise GeohashError(Geohash.__name__, geohash, cause=x)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class Geohash(Str):
|
|
142
|
+
'''Geohash class, a named C{str}.
|
|
143
|
+
'''
|
|
144
|
+
# no str.__init__ in Python 3
|
|
145
|
+
def __new__(cls, cll, precision=None, name=NN):
|
|
146
|
+
'''New L{Geohash} from an other L{Geohash} instance or C{str}
|
|
147
|
+
or from a C{LatLon} instance or C{str}.
|
|
148
|
+
|
|
149
|
+
@arg cll: Cell or location (L{Geohash}, C{LatLon} or C{str}).
|
|
150
|
+
@kwarg precision: Optional, the desired geohash length (C{int}
|
|
151
|
+
1..12), see function L{geohash.encode} for
|
|
152
|
+
some examples.
|
|
153
|
+
@kwarg name: Optional name (C{str}).
|
|
154
|
+
|
|
155
|
+
@return: New L{Geohash}.
|
|
156
|
+
|
|
157
|
+
@raise GeohashError: INValid or non-alphanumeric B{C{cll}}.
|
|
158
|
+
|
|
159
|
+
@raise TypeError: Invalid B{C{cll}}.
|
|
160
|
+
'''
|
|
161
|
+
ll = None
|
|
162
|
+
|
|
163
|
+
if isinstance(cll, Geohash):
|
|
164
|
+
gh = _2geostr(str(cll))
|
|
165
|
+
|
|
166
|
+
elif isstr(cll):
|
|
167
|
+
if _COMMA_ in cll:
|
|
168
|
+
ll = _2fll(*parse3llh(cll))
|
|
169
|
+
gh = encode(*ll, precision=precision)
|
|
170
|
+
else:
|
|
171
|
+
gh = _2geostr(cll)
|
|
172
|
+
|
|
173
|
+
else: # assume LatLon
|
|
174
|
+
try:
|
|
175
|
+
ll = _2fll(cll.lat, cll.lon)
|
|
176
|
+
gh = encode(*ll, precision=precision)
|
|
177
|
+
except AttributeError:
|
|
178
|
+
raise _xStrError(Geohash, cll=cll, Error=GeohashError)
|
|
179
|
+
|
|
180
|
+
self = Str.__new__(cls, gh, name=name or nameof(cll))
|
|
181
|
+
self._latlon = ll
|
|
182
|
+
return self
|
|
183
|
+
|
|
184
|
+
@deprecated_property_RO
|
|
185
|
+
def ab(self):
|
|
186
|
+
'''DEPRECATED, use property C{philam}.'''
|
|
187
|
+
return self.philam
|
|
188
|
+
|
|
189
|
+
def adjacent(self, direction, name=NN):
|
|
190
|
+
'''Determine the adjacent cell in the given compass direction.
|
|
191
|
+
|
|
192
|
+
@arg direction: Compass direction ('N', 'S', 'E' or 'W').
|
|
193
|
+
@kwarg name: Optional name (C{str}), otherwise the name
|
|
194
|
+
of this cell plus C{.D}irection.
|
|
195
|
+
|
|
196
|
+
@return: Geohash of adjacent cell (L{Geohash}).
|
|
197
|
+
|
|
198
|
+
@raise GeohashError: Invalid geohash or B{C{direction}}.
|
|
199
|
+
'''
|
|
200
|
+
# based on <https://GitHub.com/DaveTroy/geohash-js>
|
|
201
|
+
|
|
202
|
+
D = direction[:1].upper()
|
|
203
|
+
if D not in _GH.Neighbors:
|
|
204
|
+
raise GeohashError(direction=direction)
|
|
205
|
+
|
|
206
|
+
e = 1 if isodd(len(self)) else 0
|
|
207
|
+
|
|
208
|
+
c = self[-1:] # last hash char
|
|
209
|
+
i = _GH.Neighbors[D][e].find(c)
|
|
210
|
+
if i < 0:
|
|
211
|
+
raise GeohashError(geohash=self)
|
|
212
|
+
|
|
213
|
+
p = self[:-1] # hash without last char
|
|
214
|
+
# check for edge-cases which don't share common prefix
|
|
215
|
+
if p and (c in _GH.Borders[D][e]):
|
|
216
|
+
p = Geohash(p).adjacent(D)
|
|
217
|
+
|
|
218
|
+
n = name or self.name
|
|
219
|
+
if n:
|
|
220
|
+
n = _DOT_(n, D)
|
|
221
|
+
# append letter for direction to parent
|
|
222
|
+
return Geohash(p + _GH.GeohashBase32[i], name=n)
|
|
223
|
+
|
|
224
|
+
@Property_RO
|
|
225
|
+
def _bounds(self):
|
|
226
|
+
'''(INTERNAL) Cache for L{bounds}.
|
|
227
|
+
'''
|
|
228
|
+
return bounds(self)
|
|
229
|
+
|
|
230
|
+
def bounds(self, LatLon=None, **LatLon_kwds):
|
|
231
|
+
'''Return the lower-left SW and upper-right NE bounds of this
|
|
232
|
+
geohash cell.
|
|
233
|
+
|
|
234
|
+
@kwarg LatLon: Optional class to return I{bounds} (C{LatLon})
|
|
235
|
+
or C{None}.
|
|
236
|
+
@kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
|
|
237
|
+
arguments, ignored if B{C{LatLon}} is C{None}.
|
|
238
|
+
|
|
239
|
+
@return: A L{Bounds2Tuple}C{(latlonSW, latlonNE)} of B{C{LatLon}}s
|
|
240
|
+
or a L{Bounds4Tuple}C{(latS, lonW, latN, lonE)} if
|
|
241
|
+
C{B{LatLon} is None},
|
|
242
|
+
'''
|
|
243
|
+
r = self._bounds
|
|
244
|
+
return r if LatLon is None else \
|
|
245
|
+
_2bounds(LatLon, LatLon_kwds, *r, name=self.name)
|
|
246
|
+
|
|
247
|
+
def _distanceTo(self, func_, other, **kwds):
|
|
248
|
+
'''(INTERNAL) Helper for distances, see C{.formy._distanceTo*}.
|
|
249
|
+
'''
|
|
250
|
+
lls = self.latlon + _2Geohash(other).latlon
|
|
251
|
+
return func_(*lls, **kwds)
|
|
252
|
+
|
|
253
|
+
def distanceTo(self, other):
|
|
254
|
+
'''Estimate the distance between this and an other geohash
|
|
255
|
+
based the cell sizes.
|
|
256
|
+
|
|
257
|
+
@arg other: The other geohash (L{Geohash}, C{LatLon} or C{str}).
|
|
258
|
+
|
|
259
|
+
@return: Approximate distance (C{meter}).
|
|
260
|
+
|
|
261
|
+
@raise TypeError: The B{C{other}} is not a L{Geohash},
|
|
262
|
+
C{LatLon} or C{str}.
|
|
263
|
+
'''
|
|
264
|
+
other = _2Geohash(other)
|
|
265
|
+
|
|
266
|
+
n = min(len(self), len(other), len(_GH.Sizes))
|
|
267
|
+
if n:
|
|
268
|
+
for n in range(n):
|
|
269
|
+
if self[n] != other[n]:
|
|
270
|
+
break
|
|
271
|
+
return _GH.Sizes[n][2]
|
|
272
|
+
|
|
273
|
+
@deprecated_method
|
|
274
|
+
def distance1To(self, other): # PYCHOK no cover
|
|
275
|
+
'''DEPRECATED, use method L{distanceTo}.'''
|
|
276
|
+
return self.distanceTo(other)
|
|
277
|
+
|
|
278
|
+
distance1 = distance1To
|
|
279
|
+
|
|
280
|
+
@deprecated_method
|
|
281
|
+
def distance2To(self, other, radius=R_M, adjust=False, wrap=False): # PYCHOK no cover
|
|
282
|
+
'''DEPRECATED, use method L{equirectangularTo}.'''
|
|
283
|
+
return self.equirectangularTo(other, radius=radius, adjust=adjust, wrap=wrap)
|
|
284
|
+
|
|
285
|
+
distance2 = distance2To
|
|
286
|
+
|
|
287
|
+
@deprecated_method
|
|
288
|
+
def distance3To(self, other, radius=R_M, wrap=False): # PYCHOK no cover
|
|
289
|
+
'''DEPRECATED, use method L{haversineTo}.'''
|
|
290
|
+
return self.haversineTo(other, radius=radius, wrap=wrap)
|
|
291
|
+
|
|
292
|
+
distance3 = distance3To
|
|
293
|
+
|
|
294
|
+
def equirectangularTo(self, other, radius=R_M, **adjust_limit_wrap):
|
|
295
|
+
'''Approximate the distance between this and an other geohash
|
|
296
|
+
using function L{pygeodesy.equirectangular}.
|
|
297
|
+
|
|
298
|
+
@arg other: The other geohash (L{Geohash}, C{LatLon} or C{str}).
|
|
299
|
+
@kwarg radius: Mean earth radius, ellipsoid or datum
|
|
300
|
+
(C{meter}, L{Ellipsoid}, L{Ellipsoid2},
|
|
301
|
+
L{Datum} or L{a_f2Tuple}) or C{None}.
|
|
302
|
+
@kwarg adjust_limit_wrap: Optional keyword arguments for
|
|
303
|
+
function L{pygeodesy.equirectangular_},
|
|
304
|
+
overriding defaults C{B{adjust}=False,
|
|
305
|
+
B{limit}=None} and C{B{wrap}=False}.
|
|
306
|
+
|
|
307
|
+
@return: Distance (C{meter}, same units as B{C{radius}} or the
|
|
308
|
+
ellipsoid or datum axes or C{radians I{squared}} if
|
|
309
|
+
B{C{radius}} is C{None} or C{0}).
|
|
310
|
+
|
|
311
|
+
@raise TypeError: The B{C{other}} is not a L{Geohash}, C{LatLon}
|
|
312
|
+
or C{str} or invalid B{C{radius}}.
|
|
313
|
+
|
|
314
|
+
@see: U{Local, flat earth approximation
|
|
315
|
+
<https://www.EdWilliams.org/avform.htm#flat>}, functions
|
|
316
|
+
'''
|
|
317
|
+
lls = self.latlon + _2Geohash(other).latlon
|
|
318
|
+
kwds = _xkwds(adjust_limit_wrap, adjust=False, limit=None, wrap=False)
|
|
319
|
+
m = self._formy
|
|
320
|
+
return m.equirectangular( *lls, radius=radius, **kwds) if radius else \
|
|
321
|
+
m.equirectangular_(*lls, **kwds).distance2
|
|
322
|
+
|
|
323
|
+
def euclideanTo(self, other, **radius_adjust_wrap):
|
|
324
|
+
'''Approximate the distance between this and an other geohash using
|
|
325
|
+
function L{pygeodesy.euclidean}.
|
|
326
|
+
|
|
327
|
+
@arg other: The other geohash (L{Geohash}, C{LatLon} or C{str}).
|
|
328
|
+
@kwarg radius_adjust_wrap: Optional keyword arguments for function
|
|
329
|
+
L{pygeodesy.euclidean}.
|
|
330
|
+
|
|
331
|
+
@return: Distance (C{meter}, same units as B{C{radius}} or the
|
|
332
|
+
ellipsoid or datum axes).
|
|
333
|
+
|
|
334
|
+
@raise TypeError: The B{C{other}} is not a L{Geohash}, C{LatLon}
|
|
335
|
+
or C{str} or invalid B{C{radius}}.
|
|
336
|
+
'''
|
|
337
|
+
return self._distanceTo(self._formy.euclidean, other, **radius_adjust_wrap)
|
|
338
|
+
|
|
339
|
+
@property_RO
|
|
340
|
+
def _formy(self):
|
|
341
|
+
'''(INTERNAL) Get the C{.formy} module, I{once}.
|
|
342
|
+
'''
|
|
343
|
+
Geohash._formy = f = _MODS.formy # overwrite property_RO
|
|
344
|
+
return f
|
|
345
|
+
|
|
346
|
+
def haversineTo(self, other, **radius_wrap):
|
|
347
|
+
'''Compute the distance between this and an other geohash using
|
|
348
|
+
the L{pygeodesy.haversine} formula.
|
|
349
|
+
|
|
350
|
+
@arg other: The other geohash (L{Geohash}, C{LatLon} or C{str}).
|
|
351
|
+
@kwarg radius_wrap: Optional keyword arguments for function
|
|
352
|
+
L{pygeodesy.haversine}.
|
|
353
|
+
|
|
354
|
+
@return: Distance (C{meter}, same units as B{C{radius}} or the
|
|
355
|
+
ellipsoid or datum axes).
|
|
356
|
+
|
|
357
|
+
@raise TypeError: The B{C{other}} is not a L{Geohash}, C{LatLon}
|
|
358
|
+
or C{str} or invalid B{C{radius}}.
|
|
359
|
+
'''
|
|
360
|
+
return self._distanceTo(self._formy.haversine, other, **radius_wrap)
|
|
361
|
+
|
|
362
|
+
@Property_RO
|
|
363
|
+
def latlon(self):
|
|
364
|
+
'''Get the lat- and longitude of (the approximate center of)
|
|
365
|
+
this geohash as a L{LatLon2Tuple}C{(lat, lon)} in C{degrees}.
|
|
366
|
+
'''
|
|
367
|
+
lat, lon = self._latlon or _2center(self.bounds())
|
|
368
|
+
return LatLon2Tuple(lat, lon, name=self.name)
|
|
369
|
+
|
|
370
|
+
@Property_RO
|
|
371
|
+
def neighbors(self):
|
|
372
|
+
'''Get all 8 adjacent cells as a L{Neighbors8Dict}C{(N, NE,
|
|
373
|
+
E, SE, S, SW, W, NW)} of L{Geohash}es.
|
|
374
|
+
'''
|
|
375
|
+
return Neighbors8Dict(N=self.N, NE=self.NE, E=self.E, SE=self.SE,
|
|
376
|
+
S=self.S, SW=self.SW, W=self.W, NW=self.NW,
|
|
377
|
+
name=self.name)
|
|
378
|
+
|
|
379
|
+
@Property_RO
|
|
380
|
+
def philam(self):
|
|
381
|
+
'''Get the lat- and longitude of (the approximate center of)
|
|
382
|
+
this geohash as a L{PhiLam2Tuple}C{(phi, lam)} in C{radians}.
|
|
383
|
+
'''
|
|
384
|
+
return PhiLam2Tuple(map2(radians, self.latlon), name=self.name) # *map2
|
|
385
|
+
|
|
386
|
+
@Property_RO
|
|
387
|
+
def precision(self):
|
|
388
|
+
'''Get this geohash's precision (C{int}).
|
|
389
|
+
'''
|
|
390
|
+
return len(self)
|
|
391
|
+
|
|
392
|
+
@Property_RO
|
|
393
|
+
def sizes(self):
|
|
394
|
+
'''Get the lat- and longitudinal size of this cell as
|
|
395
|
+
a L{LatLon2Tuple}C{(lat, lon)} in (C{meter}).
|
|
396
|
+
'''
|
|
397
|
+
z = _GH.Sizes
|
|
398
|
+
n = min(len(z) - 1, max(self.precision, 1))
|
|
399
|
+
return LatLon2Tuple(z[n][:2], name=self.name) # *z XXX Height, Width?
|
|
400
|
+
|
|
401
|
+
def toLatLon(self, LatLon=None, **LatLon_kwds):
|
|
402
|
+
'''Return (the approximate center of) this geohash cell
|
|
403
|
+
as an instance of the supplied C{LatLon} class.
|
|
404
|
+
|
|
405
|
+
@arg LatLon: Class to use (C{LatLon}) or C{None}.
|
|
406
|
+
@kwarg LatLon_kwds: Optional, additional B{C{LatLon}}
|
|
407
|
+
keyword arguments, ignored if
|
|
408
|
+
C{B{LatLon} is None}.
|
|
409
|
+
|
|
410
|
+
@return: This geohash location (B{C{LatLon}}) or a
|
|
411
|
+
L{LatLon2Tuple}C{(lat, lon)} if B{C{LatLon}}
|
|
412
|
+
is C{None}.
|
|
413
|
+
|
|
414
|
+
@raise TypeError: Invalid B{C{LatLon}} or B{C{LatLon_kwds}}.
|
|
415
|
+
'''
|
|
416
|
+
return self.latlon if LatLon is None else _xnamed(LatLon(
|
|
417
|
+
*self.latlon, **LatLon_kwds), self.name)
|
|
418
|
+
|
|
419
|
+
def vincentysTo(self, other, **radius_wrap):
|
|
420
|
+
'''Compute the distance between this and an other geohash using
|
|
421
|
+
the L{pygeodesy.vincentys} formula.
|
|
422
|
+
|
|
423
|
+
@arg other: The other geohash (L{Geohash}, C{LatLon} or C{str}).
|
|
424
|
+
@kwarg radius_wrap: Optional keyword arguments for function
|
|
425
|
+
L{pygeodesy.vincentys}.
|
|
426
|
+
|
|
427
|
+
@return: Distance (C{meter}, same units as B{C{radius}} or the
|
|
428
|
+
ellipsoid or datum axes).
|
|
429
|
+
|
|
430
|
+
@raise TypeError: The B{C{other}} is not a L{Geohash}, C{LatLon}
|
|
431
|
+
or C{str} or invalid B{C{radius}}.
|
|
432
|
+
'''
|
|
433
|
+
return self._distanceTo(self._formy.vincentys, other, **radius_wrap)
|
|
434
|
+
|
|
435
|
+
@Property_RO
|
|
436
|
+
def N(self):
|
|
437
|
+
'''Get the cell North of this (L{Geohash}).
|
|
438
|
+
'''
|
|
439
|
+
return self.adjacent(_N_)
|
|
440
|
+
|
|
441
|
+
@Property_RO
|
|
442
|
+
def S(self):
|
|
443
|
+
'''Get the cell South of this (L{Geohash}).
|
|
444
|
+
'''
|
|
445
|
+
return self.adjacent(_S_)
|
|
446
|
+
|
|
447
|
+
@Property_RO
|
|
448
|
+
def E(self):
|
|
449
|
+
'''Get the cell East of this (L{Geohash}).
|
|
450
|
+
'''
|
|
451
|
+
return self.adjacent(_E_)
|
|
452
|
+
|
|
453
|
+
@Property_RO
|
|
454
|
+
def W(self):
|
|
455
|
+
'''Get the cell West of this (L{Geohash}).
|
|
456
|
+
'''
|
|
457
|
+
return self.adjacent(_W_)
|
|
458
|
+
|
|
459
|
+
@Property_RO
|
|
460
|
+
def NE(self):
|
|
461
|
+
'''Get the cell NorthEast of this (L{Geohash}).
|
|
462
|
+
'''
|
|
463
|
+
return self.N.E
|
|
464
|
+
|
|
465
|
+
@Property_RO
|
|
466
|
+
def NW(self):
|
|
467
|
+
'''Get the cell NorthWest of this (L{Geohash}).
|
|
468
|
+
'''
|
|
469
|
+
return self.N.W
|
|
470
|
+
|
|
471
|
+
@Property_RO
|
|
472
|
+
def SE(self):
|
|
473
|
+
'''Get the cell SouthEast of this (L{Geohash}).
|
|
474
|
+
'''
|
|
475
|
+
return self.S.E
|
|
476
|
+
|
|
477
|
+
@Property_RO
|
|
478
|
+
def SW(self):
|
|
479
|
+
'''Get the cell SouthWest of this (L{Geohash}).
|
|
480
|
+
'''
|
|
481
|
+
return self.S.W
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
class GeohashError(_ValueError):
|
|
485
|
+
'''Geohash encode, decode or other L{Geohash} issue.
|
|
486
|
+
'''
|
|
487
|
+
pass
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
class Neighbors8Dict(_NamedDict):
|
|
491
|
+
'''8-Dict C{(N, NE, E, SE, S, SW, W, NW)} of L{Geohash}es,
|
|
492
|
+
providing key I{and} attribute access to the items.
|
|
493
|
+
'''
|
|
494
|
+
_Keys_ = (_N_, _NE_, _E_, _SE_, _S_, _SW_, _W_, _NW_)
|
|
495
|
+
|
|
496
|
+
def __init__(self, **kwds): # PYCHOK no *args
|
|
497
|
+
kwds = _xkwds(kwds, **_Neighbors8Defaults)
|
|
498
|
+
_NamedDict.__init__(self, **kwds) # name=...
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
_Neighbors8Defaults = dict(zip(Neighbors8Dict._Keys_, (None,) *
|
|
502
|
+
len(Neighbors8Dict._Keys_))) # XXX frozendict
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def bounds(geohash, LatLon=None, **LatLon_kwds):
|
|
506
|
+
'''Returns the lower-left SW and upper-right NE corners of a geohash.
|
|
507
|
+
|
|
508
|
+
@arg geohash: To be bound (L{Geohash}).
|
|
509
|
+
@kwarg LatLon: Optional class to return the bounds (C{LatLon})
|
|
510
|
+
or C{None}.
|
|
511
|
+
@kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
|
|
512
|
+
arguments, ignored if C{B{LatLon} is None}.
|
|
513
|
+
|
|
514
|
+
@return: A L{Bounds2Tuple}C{(latlonSW, latlonNE)} of B{C{LatLon}}s
|
|
515
|
+
or if B{C{LatLon}} is C{None}, a L{Bounds4Tuple}C{(latS,
|
|
516
|
+
lonW, latN, lonE)}.
|
|
517
|
+
|
|
518
|
+
@raise TypeError: The B{C{geohash}} is not a L{Geohash}, C{LatLon}
|
|
519
|
+
or C{str} or invalid B{C{LatLon}} or invalid
|
|
520
|
+
B{C{LatLon_kwds}}.
|
|
521
|
+
|
|
522
|
+
@raise GeohashError: Invalid or C{null} B{C{geohash}}.
|
|
523
|
+
'''
|
|
524
|
+
gh = _2Geohash(geohash)
|
|
525
|
+
if len(gh) < 1:
|
|
526
|
+
raise GeohashError(geohash=geohash)
|
|
527
|
+
|
|
528
|
+
s, w, n, e = _GH.Bounds4
|
|
529
|
+
try:
|
|
530
|
+
d, _avg = True, favg
|
|
531
|
+
for c in gh.lower():
|
|
532
|
+
i = _GH.DecodedBase32[c]
|
|
533
|
+
for m in (16, 8, 4, 2, 1):
|
|
534
|
+
if d: # longitude
|
|
535
|
+
if i & m:
|
|
536
|
+
w = _avg(w, e)
|
|
537
|
+
else:
|
|
538
|
+
e = _avg(w, e)
|
|
539
|
+
else: # latitude
|
|
540
|
+
if i & m:
|
|
541
|
+
s = _avg(s, n)
|
|
542
|
+
else:
|
|
543
|
+
n = _avg(s, n)
|
|
544
|
+
d = not d
|
|
545
|
+
except KeyError:
|
|
546
|
+
raise GeohashError(geohash=geohash)
|
|
547
|
+
|
|
548
|
+
return _2bounds(LatLon, LatLon_kwds, s, w, n, e,
|
|
549
|
+
name=nameof(geohash))
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def _bounds3(geohash):
|
|
553
|
+
'''(INTERNAL) Return 3-tuple C{(bounds, height, width)}.
|
|
554
|
+
'''
|
|
555
|
+
b = bounds(geohash)
|
|
556
|
+
return b, (b.latN - b.latS), (b.lonE - b.lonW)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def decode(geohash):
|
|
560
|
+
'''Decode a geohash to lat-/longitude of the (approximate
|
|
561
|
+
centre of) geohash cell to reasonable precision.
|
|
562
|
+
|
|
563
|
+
@arg geohash: To be decoded (L{Geohash}).
|
|
564
|
+
|
|
565
|
+
@return: 2-Tuple C{(latStr, lonStr)}, both C{str}.
|
|
566
|
+
|
|
567
|
+
@raise TypeError: The B{C{geohash}} is not a L{Geohash},
|
|
568
|
+
C{LatLon} or C{str}.
|
|
569
|
+
|
|
570
|
+
@raise GeohashError: Invalid or null B{C{geohash}}.
|
|
571
|
+
'''
|
|
572
|
+
b, h, w = _bounds3(geohash)
|
|
573
|
+
lat, lon = _2center(b)
|
|
574
|
+
|
|
575
|
+
# round to near centre without excessive precision to
|
|
576
|
+
# ⌊2-log10(Δ°)⌋ decimal places, strip trailing zeros
|
|
577
|
+
return (fstr(lat, prec=int(2 - log10(h))),
|
|
578
|
+
fstr(lon, prec=int(2 - log10(w)))) # strs!
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def decode2(geohash, LatLon=None, **LatLon_kwds):
|
|
582
|
+
'''Decode a geohash to lat-/longitude of the (approximate center
|
|
583
|
+
of) geohash cell to reasonable precision.
|
|
584
|
+
|
|
585
|
+
@arg geohash: To be decoded (L{Geohash}).
|
|
586
|
+
@kwarg LatLon: Optional class to return the location (C{LatLon})
|
|
587
|
+
or C{None}.
|
|
588
|
+
@kwarg LatLon_kwds: Optional, addtional B{C{LatLon}} keyword
|
|
589
|
+
arguments, ignored if C{B{LatLon} is None}.
|
|
590
|
+
|
|
591
|
+
@return: L{LatLon2Tuple}C{(lat, lon)}, both C{degrees} if
|
|
592
|
+
C{B{LatLon} is None}, otherwise a B{C{LatLon}} instance.
|
|
593
|
+
|
|
594
|
+
@raise TypeError: The B{C{geohash}} is not a L{Geohash},
|
|
595
|
+
C{LatLon} or C{str}.
|
|
596
|
+
|
|
597
|
+
@raise GeohashError: Invalid or null B{C{geohash}}.
|
|
598
|
+
'''
|
|
599
|
+
t = map2(float, decode(geohash))
|
|
600
|
+
r = LatLon2Tuple(t) if LatLon is None else LatLon(*t, **LatLon_kwds) # *t
|
|
601
|
+
return _xnamed(r, decode2.__name__)
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def decode_error(geohash):
|
|
605
|
+
'''Return the relative lat-/longitude decoding errors for
|
|
606
|
+
this geohash.
|
|
607
|
+
|
|
608
|
+
@arg geohash: To be decoded (L{Geohash}).
|
|
609
|
+
|
|
610
|
+
@return: A L{LatLon2Tuple}C{(lat, lon)} with the lat- and
|
|
611
|
+
longitudinal errors in (C{degrees}).
|
|
612
|
+
|
|
613
|
+
@raise TypeError: The B{C{geohash}} is not a L{Geohash},
|
|
614
|
+
C{LatLon} or C{str}.
|
|
615
|
+
|
|
616
|
+
@raise GeohashError: Invalid or null B{C{geohash}}.
|
|
617
|
+
'''
|
|
618
|
+
_, h, w = _bounds3(geohash)
|
|
619
|
+
return LatLon2Tuple(h * _0_5, # Height error
|
|
620
|
+
w * _0_5) # Width error
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def distance_(geohash1, geohash2):
|
|
624
|
+
'''Estimate the distance between two geohash (from the cell sizes).
|
|
625
|
+
|
|
626
|
+
@arg geohash1: First geohash (L{Geohash}, C{LatLon} or C{str}).
|
|
627
|
+
@arg geohash2: Second geohash (L{Geohash}, C{LatLon} or C{str}).
|
|
628
|
+
|
|
629
|
+
@return: Approximate distance (C{meter}).
|
|
630
|
+
|
|
631
|
+
@raise TypeError: If B{C{geohash1}} or B{C{geohash2}} is not a
|
|
632
|
+
L{Geohash}, C{LatLon} or C{str}.
|
|
633
|
+
'''
|
|
634
|
+
return _2Geohash(geohash1).distanceTo(geohash2)
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
@deprecated_function
|
|
638
|
+
def distance1(geohash1, geohash2):
|
|
639
|
+
'''DEPRECATED, used L{geohash.distance_}.'''
|
|
640
|
+
return distance_(geohash1, geohash2)
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
@deprecated_function
|
|
644
|
+
def distance2(geohash1, geohash2):
|
|
645
|
+
'''DEPRECATED, used L{geohash.equirectangular_}.'''
|
|
646
|
+
return equirectangular_(geohash1, geohash2)
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
@deprecated_function
|
|
650
|
+
def distance3(geohash1, geohash2):
|
|
651
|
+
'''DEPRECATED, used L{geohash.haversine_}.'''
|
|
652
|
+
return haversine_(geohash1, geohash2)
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def encode(lat, lon, precision=None):
|
|
656
|
+
'''Encode a lat-/longitude as a C{geohash}, either to the specified
|
|
657
|
+
precision or if not provided, to an automatically evaluated
|
|
658
|
+
precision.
|
|
659
|
+
|
|
660
|
+
@arg lat: Latitude (C{degrees}).
|
|
661
|
+
@arg lon: Longitude (C{degrees}).
|
|
662
|
+
@kwarg precision: Optional, the desired geohash length (C{int}
|
|
663
|
+
1..12).
|
|
664
|
+
|
|
665
|
+
@return: The C{geohash} (C{str}).
|
|
666
|
+
|
|
667
|
+
@raise GeohashError: Invalid B{C{lat}}, B{C{lon}} or B{C{precision}}.
|
|
668
|
+
'''
|
|
669
|
+
lat, lon = _2fll(lat, lon)
|
|
670
|
+
|
|
671
|
+
if precision is None:
|
|
672
|
+
# Infer precision by refining geohash until
|
|
673
|
+
# it matches precision of supplied lat/lon.
|
|
674
|
+
for p in range(1, _MaxPrec + 1):
|
|
675
|
+
gh = encode(lat, lon, p)
|
|
676
|
+
ll = map2(float, decode(gh))
|
|
677
|
+
if fabs(lat - ll[0]) < EPS and \
|
|
678
|
+
fabs(lon - ll[1]) < EPS:
|
|
679
|
+
return gh
|
|
680
|
+
p = _MaxPrec
|
|
681
|
+
else:
|
|
682
|
+
p = Precision_(precision, Error=GeohashError, low=1, high=_MaxPrec)
|
|
683
|
+
|
|
684
|
+
b = i = 0
|
|
685
|
+
d, gh = True, []
|
|
686
|
+
s, w, n, e = _GH.Bounds4
|
|
687
|
+
|
|
688
|
+
_avg = favg
|
|
689
|
+
while p > 0:
|
|
690
|
+
i += i
|
|
691
|
+
if d: # bisect longitude
|
|
692
|
+
m = _avg(e, w)
|
|
693
|
+
if lon < m:
|
|
694
|
+
e = m
|
|
695
|
+
else:
|
|
696
|
+
w = m
|
|
697
|
+
i += 1
|
|
698
|
+
else: # bisect latitude
|
|
699
|
+
m = _avg(n, s)
|
|
700
|
+
if lat < m:
|
|
701
|
+
n = m
|
|
702
|
+
else:
|
|
703
|
+
s = m
|
|
704
|
+
i += 1
|
|
705
|
+
d = not d
|
|
706
|
+
|
|
707
|
+
b += 1
|
|
708
|
+
if b == 5:
|
|
709
|
+
# 5 bits gives a character:
|
|
710
|
+
# append it and start over
|
|
711
|
+
gh.append(_GH.GeohashBase32[i])
|
|
712
|
+
b = i = 0
|
|
713
|
+
p -= 1
|
|
714
|
+
|
|
715
|
+
return NN.join(gh)
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def equirectangular_(geohash1, geohash2, radius=R_M):
|
|
719
|
+
'''Approximate the distance between two geohashes using the
|
|
720
|
+
L{pygeodesy.equirectangular} formula.
|
|
721
|
+
|
|
722
|
+
@arg geohash1: First geohash (L{Geohash}, C{LatLon} or C{str}).
|
|
723
|
+
@arg geohash2: Second geohash (L{Geohash}, C{LatLon} or C{str}).
|
|
724
|
+
@kwarg radius: Mean earth radius (C{meter}) or C{None}, see method
|
|
725
|
+
L{Geohash.equirectangularTo}.
|
|
726
|
+
|
|
727
|
+
@return: Approximate distance (C{meter}, same units as B{C{radius}}),
|
|
728
|
+
see method L{Geohash.equirectangularTo}.
|
|
729
|
+
|
|
730
|
+
@raise TypeError: If B{C{geohash1}} or B{C{geohash2}} is not a
|
|
731
|
+
L{Geohash}, C{LatLon} or C{str}.
|
|
732
|
+
'''
|
|
733
|
+
return _2Geohash(geohash1).equirectangularTo(geohash2, radius=radius)
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
def euclidean_(geohash1, geohash2, **radius_adjust_wrap):
|
|
737
|
+
'''Approximate the distance between two geohashes using the
|
|
738
|
+
L{pygeodesy.euclidean} formula.
|
|
739
|
+
|
|
740
|
+
@arg geohash1: First geohash (L{Geohash}, C{LatLon} or C{str}).
|
|
741
|
+
@arg geohash2: Second geohash (L{Geohash}, C{LatLon} or C{str}).
|
|
742
|
+
@kwarg radius_adjust_wrap: Optional keyword arguments for function
|
|
743
|
+
L{pygeodesy.euclidean}.
|
|
744
|
+
|
|
745
|
+
@return: Approximate distance (C{meter}, same units as B{C{radius}}).
|
|
746
|
+
|
|
747
|
+
@raise TypeError: If B{C{geohash1}} or B{C{geohash2}} is not a
|
|
748
|
+
L{Geohash}, C{LatLon} or C{str}.
|
|
749
|
+
'''
|
|
750
|
+
return _2Geohash(geohash1).euclideanTo(geohash2, **radius_adjust_wrap)
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
def haversine_(geohash1, geohash2, **radius_wrap):
|
|
754
|
+
'''Compute the great-circle distance between two geohashes
|
|
755
|
+
using the L{pygeodesy.haversine} formula.
|
|
756
|
+
|
|
757
|
+
@arg geohash1: First geohash (L{Geohash}, C{LatLon} or C{str}).
|
|
758
|
+
@arg geohash2: Second geohash (L{Geohash}, C{LatLon} or C{str}).
|
|
759
|
+
@kwarg radius_wrap: Optional keyword arguments for function
|
|
760
|
+
L{pygeodesy.haversine}.
|
|
761
|
+
|
|
762
|
+
@return: Great-circle distance (C{meter}, same units as
|
|
763
|
+
B{C{radius}}).
|
|
764
|
+
|
|
765
|
+
@raise TypeError: If B{C{geohash1}} or B{C{geohash2}} is
|
|
766
|
+
not a L{Geohash}, C{LatLon} or C{str}.
|
|
767
|
+
'''
|
|
768
|
+
return _2Geohash(geohash1).haversineTo(geohash2, **radius_wrap)
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
def neighbors(geohash):
|
|
772
|
+
'''Return the L{Geohash}es for all 8 adjacent cells.
|
|
773
|
+
|
|
774
|
+
@arg geohash: Cell for which neighbors are requested
|
|
775
|
+
(L{Geohash} or C{str}).
|
|
776
|
+
|
|
777
|
+
@return: A L{Neighbors8Dict}C{(N, NE, E, SE, S, SW, W, NW)}
|
|
778
|
+
of L{Geohash}es.
|
|
779
|
+
|
|
780
|
+
@raise TypeError: The B{C{geohash}} is not a L{Geohash},
|
|
781
|
+
C{LatLon} or C{str}.
|
|
782
|
+
'''
|
|
783
|
+
return _2Geohash(geohash).neighbors
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
def precision(res1, res2=None):
|
|
787
|
+
'''Determine the L{Geohash} precisions to meet a or both given
|
|
788
|
+
(geographic) resolutions.
|
|
789
|
+
|
|
790
|
+
@arg res1: The required primary I{(longitudinal)} resolution
|
|
791
|
+
(C{degrees}).
|
|
792
|
+
@kwarg res2: Optional, required secondary I{(latitudinal)}
|
|
793
|
+
resolution (C{degrees}).
|
|
794
|
+
|
|
795
|
+
@return: The L{Geohash} precision or length (C{int}, 1..12).
|
|
796
|
+
|
|
797
|
+
@raise GeohashError: Invalid B{C{res1}} or B{C{res2}}.
|
|
798
|
+
|
|
799
|
+
@see: C++ class U{Geohash
|
|
800
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Geohash.html>}.
|
|
801
|
+
'''
|
|
802
|
+
r = Degrees_(res1=res1, low=_0_0, Error=GeohashError)
|
|
803
|
+
if res2 is None:
|
|
804
|
+
t = r, r
|
|
805
|
+
for p in range(1, _MaxPrec):
|
|
806
|
+
if resolution2(p, None) <= t:
|
|
807
|
+
return p
|
|
808
|
+
|
|
809
|
+
else:
|
|
810
|
+
t = r, Degrees_(res2=res2, low=_0_0, Error=GeohashError)
|
|
811
|
+
for p in range(1, _MaxPrec):
|
|
812
|
+
if resolution2(p, p) <= t:
|
|
813
|
+
return p
|
|
814
|
+
|
|
815
|
+
return _MaxPrec
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
class Resolutions2Tuple(_NamedTuple):
|
|
819
|
+
'''2-Tuple C{(res1, res2)} with the primary I{(longitudinal)} and
|
|
820
|
+
secondary I{(latitudinal)} resolution, both in C{degrees}.
|
|
821
|
+
'''
|
|
822
|
+
_Names_ = ('res1', 'res2')
|
|
823
|
+
_Units_ = ( Degrees_, Degrees_)
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
def resolution2(prec1, prec2=None):
|
|
827
|
+
'''Determine the (geographic) resolutions of given L{Geohash}
|
|
828
|
+
precisions.
|
|
829
|
+
|
|
830
|
+
@arg prec1: The given primary I{(longitudinal)} precision
|
|
831
|
+
(C{int} 1..12).
|
|
832
|
+
@kwarg prec2: Optional, secondary I{(latitudinal)} precision
|
|
833
|
+
(C{int} 1..12).
|
|
834
|
+
|
|
835
|
+
@return: L{Resolutions2Tuple}C{(res1, res2)} with the
|
|
836
|
+
(geographic) resolutions C{degrees}, where C{res2}
|
|
837
|
+
B{C{is}} C{res1} if no B{C{prec2}} is given.
|
|
838
|
+
|
|
839
|
+
@raise GeohashError: Invalid B{C{prec1}} or B{C{prec2}}.
|
|
840
|
+
|
|
841
|
+
@see: I{Karney}'s C++ class U{Geohash
|
|
842
|
+
<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Geohash.html>}.
|
|
843
|
+
'''
|
|
844
|
+
res1, res2 = _360_0, _180_0 # note ... lon, lat!
|
|
845
|
+
|
|
846
|
+
if prec1:
|
|
847
|
+
p = 5 * max(0, min(Int(prec1=prec1, Error=GeohashError), _MaxPrec))
|
|
848
|
+
res1 = res2 = ldexp(res1, -(p - p // 2))
|
|
849
|
+
|
|
850
|
+
if prec2:
|
|
851
|
+
p = 5 * max(0, min(Int(prec2=prec2, Error=GeohashError), _MaxPrec))
|
|
852
|
+
res2 = ldexp(res2, -(p // 2))
|
|
853
|
+
|
|
854
|
+
return Resolutions2Tuple(res1, res2)
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
def sizes(geohash):
|
|
858
|
+
'''Return the lat- and longitudinal size of this L{Geohash} cell.
|
|
859
|
+
|
|
860
|
+
@arg geohash: Cell for which size are required (L{Geohash} or
|
|
861
|
+
C{str}).
|
|
862
|
+
|
|
863
|
+
@return: A L{LatLon2Tuple}C{(lat, lon)} with the latitudinal
|
|
864
|
+
height and longitudinal width in (C{meter}).
|
|
865
|
+
|
|
866
|
+
@raise TypeError: The B{C{geohash}} is not a L{Geohash},
|
|
867
|
+
C{LatLon} or C{str}.
|
|
868
|
+
'''
|
|
869
|
+
return _2Geohash(geohash).sizes
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
def vincentys_(geohash1, geohash2, **radius_wrap):
|
|
873
|
+
'''Compute the distance between two geohashes using the
|
|
874
|
+
L{pygeodesy.vincentys} formula.
|
|
875
|
+
|
|
876
|
+
@arg geohash1: First geohash (L{Geohash}, C{LatLon} or C{str}).
|
|
877
|
+
@arg geohash2: Second geohash (L{Geohash}, C{LatLon} or C{str}).
|
|
878
|
+
@kwarg radius_wrap: Optional keyword arguments for function
|
|
879
|
+
L{pygeodesy.vincentys}.
|
|
880
|
+
|
|
881
|
+
@return: Distance (C{meter}, same units as B{C{radius}}).
|
|
882
|
+
|
|
883
|
+
@raise TypeError: If B{C{geohash1}} or B{C{geohash2}} is not a
|
|
884
|
+
L{Geohash}, C{LatLon} or C{str}.
|
|
885
|
+
'''
|
|
886
|
+
return _2Geohash(geohash1).vincentysTo(geohash2, **radius_wrap)
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
__all__ += _ALL_OTHER(bounds, # functions
|
|
890
|
+
decode, decode2, decode_error, distance_,
|
|
891
|
+
encode, equirectangular_, euclidean_, haversine_,
|
|
892
|
+
neighbors, precision, resolution2, sizes, vincentys_)
|
|
893
|
+
|
|
894
|
+
# **) MIT License
|
|
895
|
+
#
|
|
896
|
+
# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
897
|
+
#
|
|
898
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
899
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
900
|
+
# to deal in the Software without restriction, including without limitation
|
|
901
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
902
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
903
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
904
|
+
#
|
|
905
|
+
# The above copyright notice and this permission notice shall be included
|
|
906
|
+
# in all copies or substantial portions of the Software.
|
|
907
|
+
#
|
|
908
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
909
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
910
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
911
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
912
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
913
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
914
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|