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.
Files changed (115) hide show
  1. PyGeodesy-24.3.24.dist-info/METADATA +272 -0
  2. PyGeodesy-24.3.24.dist-info/RECORD +115 -0
  3. PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
  4. PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
  5. pygeodesy/LICENSE +21 -0
  6. pygeodesy/__init__.py +615 -0
  7. pygeodesy/__main__.py +103 -0
  8. pygeodesy/albers.py +867 -0
  9. pygeodesy/auxilats/_CX_4.py +218 -0
  10. pygeodesy/auxilats/_CX_6.py +314 -0
  11. pygeodesy/auxilats/_CX_8.py +475 -0
  12. pygeodesy/auxilats/__init__.py +54 -0
  13. pygeodesy/auxilats/__main__.py +86 -0
  14. pygeodesy/auxilats/auxAngle.py +548 -0
  15. pygeodesy/auxilats/auxDLat.py +302 -0
  16. pygeodesy/auxilats/auxDST.py +296 -0
  17. pygeodesy/auxilats/auxLat.py +848 -0
  18. pygeodesy/auxilats/auxily.py +272 -0
  19. pygeodesy/azimuthal.py +1150 -0
  20. pygeodesy/basics.py +892 -0
  21. pygeodesy/booleans.py +2031 -0
  22. pygeodesy/cartesianBase.py +1062 -0
  23. pygeodesy/clipy.py +704 -0
  24. pygeodesy/constants.py +516 -0
  25. pygeodesy/css.py +660 -0
  26. pygeodesy/datums.py +752 -0
  27. pygeodesy/deprecated/__init__.py +61 -0
  28. pygeodesy/deprecated/bases.py +40 -0
  29. pygeodesy/deprecated/classes.py +262 -0
  30. pygeodesy/deprecated/consterns.py +54 -0
  31. pygeodesy/deprecated/datum.py +40 -0
  32. pygeodesy/deprecated/functions.py +375 -0
  33. pygeodesy/deprecated/nvector.py +48 -0
  34. pygeodesy/deprecated/rhumbBase.py +32 -0
  35. pygeodesy/deprecated/rhumbaux.py +33 -0
  36. pygeodesy/deprecated/rhumbsolve.py +33 -0
  37. pygeodesy/deprecated/rhumbx.py +33 -0
  38. pygeodesy/dms.py +986 -0
  39. pygeodesy/ecef.py +1348 -0
  40. pygeodesy/elevations.py +279 -0
  41. pygeodesy/ellipsoidalBase.py +1224 -0
  42. pygeodesy/ellipsoidalBaseDI.py +913 -0
  43. pygeodesy/ellipsoidalExact.py +343 -0
  44. pygeodesy/ellipsoidalGeodSolve.py +343 -0
  45. pygeodesy/ellipsoidalKarney.py +403 -0
  46. pygeodesy/ellipsoidalNvector.py +685 -0
  47. pygeodesy/ellipsoidalVincenty.py +590 -0
  48. pygeodesy/ellipsoids.py +2476 -0
  49. pygeodesy/elliptic.py +1198 -0
  50. pygeodesy/epsg.py +243 -0
  51. pygeodesy/errors.py +804 -0
  52. pygeodesy/etm.py +1190 -0
  53. pygeodesy/fmath.py +1013 -0
  54. pygeodesy/formy.py +1818 -0
  55. pygeodesy/frechet.py +865 -0
  56. pygeodesy/fstats.py +760 -0
  57. pygeodesy/fsums.py +1898 -0
  58. pygeodesy/gars.py +358 -0
  59. pygeodesy/geodesicw.py +581 -0
  60. pygeodesy/geodesicx/_C4_24.py +1699 -0
  61. pygeodesy/geodesicx/_C4_27.py +2395 -0
  62. pygeodesy/geodesicx/_C4_30.py +3301 -0
  63. pygeodesy/geodesicx/__init__.py +48 -0
  64. pygeodesy/geodesicx/__main__.py +91 -0
  65. pygeodesy/geodesicx/gx.py +1382 -0
  66. pygeodesy/geodesicx/gxarea.py +535 -0
  67. pygeodesy/geodesicx/gxbases.py +154 -0
  68. pygeodesy/geodesicx/gxline.py +669 -0
  69. pygeodesy/geodsolve.py +426 -0
  70. pygeodesy/geohash.py +914 -0
  71. pygeodesy/geoids.py +1884 -0
  72. pygeodesy/hausdorff.py +892 -0
  73. pygeodesy/heights.py +1155 -0
  74. pygeodesy/interns.py +687 -0
  75. pygeodesy/iters.py +545 -0
  76. pygeodesy/karney.py +919 -0
  77. pygeodesy/ktm.py +633 -0
  78. pygeodesy/latlonBase.py +1766 -0
  79. pygeodesy/lazily.py +960 -0
  80. pygeodesy/lcc.py +684 -0
  81. pygeodesy/ltp.py +1107 -0
  82. pygeodesy/ltpTuples.py +1563 -0
  83. pygeodesy/mgrs.py +721 -0
  84. pygeodesy/named.py +1324 -0
  85. pygeodesy/namedTuples.py +683 -0
  86. pygeodesy/nvectorBase.py +695 -0
  87. pygeodesy/osgr.py +781 -0
  88. pygeodesy/points.py +1686 -0
  89. pygeodesy/props.py +628 -0
  90. pygeodesy/resections.py +1048 -0
  91. pygeodesy/rhumb/__init__.py +46 -0
  92. pygeodesy/rhumb/aux_.py +397 -0
  93. pygeodesy/rhumb/bases.py +1148 -0
  94. pygeodesy/rhumb/ekx.py +563 -0
  95. pygeodesy/rhumb/solve.py +572 -0
  96. pygeodesy/simplify.py +647 -0
  97. pygeodesy/solveBase.py +472 -0
  98. pygeodesy/sphericalBase.py +724 -0
  99. pygeodesy/sphericalNvector.py +1264 -0
  100. pygeodesy/sphericalTrigonometry.py +1447 -0
  101. pygeodesy/streprs.py +627 -0
  102. pygeodesy/trf.py +2079 -0
  103. pygeodesy/triaxials.py +1484 -0
  104. pygeodesy/units.py +969 -0
  105. pygeodesy/unitsBase.py +349 -0
  106. pygeodesy/ups.py +538 -0
  107. pygeodesy/utily.py +1231 -0
  108. pygeodesy/utm.py +762 -0
  109. pygeodesy/utmups.py +318 -0
  110. pygeodesy/utmupsBase.py +517 -0
  111. pygeodesy/vector2d.py +785 -0
  112. pygeodesy/vector3d.py +968 -0
  113. pygeodesy/vector3dBase.py +1049 -0
  114. pygeodesy/webmercator.py +383 -0
  115. 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.