pygeodesy 24.7.7__py2.py3-none-any.whl → 24.8.4__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.
@@ -28,8 +28,8 @@ from pygeodesy.named import _name2__, _Pass
28
28
  from pygeodesy.namedTuples import LatLon4Tuple, _NamedTupleTo , Vector3Tuple, \
29
29
  Vector4Tuple, Bearing2Tuple # PYCHOK .sphericalBase
30
30
  # from pygeodesy.nvectorBase import _N_vector # _MODS
31
- from pygeodesy.props import deprecated_method, Property, Property_RO, \
32
- property_doc_, property_RO, _update_all
31
+ from pygeodesy.props import deprecated_method, Property, Property_RO, property_doc_, \
32
+ property_RO, property_ROnce, _update_all
33
33
  # from pygeodesy,resections import cassini, collins5, pierlot, pierlotx, \
34
34
  # tienstra7 # _MODS
35
35
  # from pygeodesy.streprs import Fmt # from .fsums
@@ -43,7 +43,7 @@ from pygeodesy.vector3d import Vector3d, _xyzhdlln4
43
43
  # from math import atan2, degrees, fabs, radians, sqrt # from .fmath, .utily
44
44
 
45
45
  __all__ = _ALL_LAZY.cartesianBase
46
- __version__ = '24.06.11'
46
+ __version__ = '24.07.29'
47
47
 
48
48
  _r_ = 'r'
49
49
  _theta_ = 'theta'
@@ -211,12 +211,11 @@ class CartesianBase(Vector3d):
211
211
  r = Cartesian(*c, **kwds)
212
212
  return r.renamed(n) if n else r
213
213
 
214
- @property_RO
214
+ @property_ROnce
215
215
  def Ecef(self):
216
216
  '''Get the ECEF I{class} (L{EcefKarney}), I{once}.
217
217
  '''
218
- CartesianBase.Ecef = E = _MODS.ecef.EcefKarney # overwrite property_RO
219
- return E
218
+ return _MODS.ecef.EcefKarney
220
219
 
221
220
  @Property_RO
222
221
  def _ecef9(self):
@@ -301,7 +300,7 @@ class CartesianBase(Vector3d):
301
300
  coordinates I{at height} in C{meter}, conventionally.
302
301
 
303
302
  @note: This cartesian's coordinates are returned if B{C{earth}} and this
304
- datum or B{C{heigth}} and/or this height are C{None} or undefined.
303
+ datum or B{C{height}} and/or this height are C{None} or undefined.
305
304
 
306
305
  @note: Include keyword argument C{B{datum}=None} if class B{C{Cartesian}}
307
306
  does not accept a B{C{datum}} keyword agument.
pygeodesy/css.py CHANGED
@@ -27,13 +27,13 @@ from pygeodesy.namedTuples import EasNor2Tuple, EasNor3Tuple, \
27
27
  from pygeodesy.props import deprecated_Property_RO, Property, \
28
28
  Property_RO, _update_all
29
29
  from pygeodesy.streprs import Fmt, _fstrENH2, _fstrLL0, _xzipairs
30
- from pygeodesy.units import Bearing, Degrees, Easting, Height, _heigHt, \
30
+ from pygeodesy.units import Azimuth, Degrees, Easting, Height, _heigHt, \
31
31
  Lat_, Lon_, Northing, Scalar
32
32
 
33
33
  # from math import fabs # from .karney
34
34
 
35
35
  __all__ = _ALL_LAZY.css
36
- __version__ = '24.06.11'
36
+ __version__ = '24.07.25'
37
37
 
38
38
 
39
39
  def _CS0(cs0):
@@ -578,7 +578,7 @@ class EasNorAziRk4Tuple(_NamedTuple):
578
578
  C{reciprocal} of azimuthal northing scale, both in C{degrees}.
579
579
  '''
580
580
  _Names_ = (_easting_, _northing_, _azimuth_, _reciprocal_)
581
- _Units_ = ( Easting, Northing, Bearing, Scalar)
581
+ _Units_ = ( Easting, Northing, Azimuth, Scalar)
582
582
 
583
583
 
584
584
  class EasNorAziRkEqu6Tuple(_NamedTuple):
@@ -589,7 +589,7 @@ class EasNorAziRkEqu6Tuple(_NamedTuple):
589
589
  C{equatorarc} and C{equatorazimuth}, all in C{degrees}.
590
590
  '''
591
591
  _Names_ = EasNorAziRk4Tuple._Names_ + ('equatorarc', 'equatorazimuth')
592
- _Units_ = EasNorAziRk4Tuple._Units_ + ( Degrees, Bearing)
592
+ _Units_ = EasNorAziRk4Tuple._Units_ + ( Degrees, Azimuth)
593
593
 
594
594
 
595
595
  class LatLonAziRk4Tuple(_NamedTuple):
@@ -598,7 +598,7 @@ class LatLonAziRk4Tuple(_NamedTuple):
598
598
  C{reciprocal} the reciprocal of azimuthal northing scale.
599
599
  '''
600
600
  _Names_ = (_lat_, _lon_, _azimuth_, _reciprocal_)
601
- _Units_ = ( Lat_, Lon_, Bearing, Scalar)
601
+ _Units_ = ( Lat_, Lon_, Azimuth, Scalar)
602
602
 
603
603
 
604
604
  def toCss(latlon, cs0=None, height=None, Css=Css, **name):
pygeodesy/ecef.py CHANGED
@@ -76,7 +76,7 @@ from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
76
76
  from pygeodesy.named import _name__, _name1__, _NamedBase, _NamedTuple, _Pass, _xnamed
77
77
  from pygeodesy.namedTuples import LatLon2Tuple, LatLon3Tuple, \
78
78
  PhiLam2Tuple, Vector3Tuple, Vector4Tuple
79
- from pygeodesy.props import deprecated_method, Property_RO, property_RO, property_doc_
79
+ from pygeodesy.props import deprecated_method, Property_RO, property_ROver, property_doc_
80
80
  # from pygeodesy.streprs import Fmt, unstr # from .fsums
81
81
  from pygeodesy.units import _isRadius, Degrees, Height, Int, Lam, Lat, Lon, Meter, \
82
82
  Phi, Scalar, Scalar_
@@ -86,7 +86,7 @@ from pygeodesy.utily import atan1, atan1d, atan2d, degrees90, degrees180, \
86
86
  from math import atan2, cos, degrees, fabs, radians, sqrt
87
87
 
88
88
  __all__ = _ALL_LAZY.ecef
89
- __version__ = '24.06.11'
89
+ __version__ = '24.06.12'
90
90
 
91
91
  _Ecef_ = 'Ecef'
92
92
  _prolate_ = 'prolate'
@@ -261,13 +261,12 @@ class _EcefBase(_NamedBase):
261
261
  raise EcefError(phi=phi, lam=lam, height=height, cause=x)
262
262
  return self._forward(*plhn, M=M, _philam=True)
263
263
 
264
- @property_RO
264
+ @property_ROver
265
265
  def _Geocentrics(self):
266
266
  '''(INTERNAL) Get the valid geocentric classes. I{once}.
267
267
  '''
268
- _EcefBase._Geocentrics = t = (Ecef9Tuple, # overwrite property_RO
269
- _MODS.cartesianBase.CartesianBase)
270
- return t
268
+ return (Ecef9Tuple, # overwrite property_ROver
269
+ _MODS.cartesianBase.CartesianBase)
271
270
 
272
271
  @Property_RO
273
272
  def _isYou(self):
@@ -1028,12 +1027,11 @@ class Ecef9Tuple(_NamedTuple):
1028
1027
  _Names_ = (_x_, _y_, _z_, _lat_, _lon_, _height_, _C_, _M_, _datum_)
1029
1028
  _Units_ = ( Meter, Meter, Meter, Lat, Lon, Height, Int, _Pass, _Pass)
1030
1029
 
1031
- @property_RO
1030
+ @property_ROver
1032
1031
  def _CartesianBase(self):
1033
1032
  '''(INTERNAL) Get class C{CartesianBase}, I{once}.
1034
1033
  '''
1035
- Ecef9Tuple._CartesianBase = C = _MODS.cartesianBase.CartesianBase # overwrite property_RO
1036
- return C
1034
+ return _MODS.cartesianBase.CartesianBase # overwrite property_ROver
1037
1035
 
1038
1036
  @deprecated_method
1039
1037
  def convertDatum(self, datum2): # for backward compatibility
pygeodesy/ellipsoids.py CHANGED
@@ -83,7 +83,7 @@ from pygeodesy.named import _lazyNamedEnumItem as _lazy, _name__, _NamedEnum, \
83
83
  _NamedEnumItem, _NamedTuple, _Pass, _ALL_LAZY, _MODS
84
84
  from pygeodesy.namedTuples import Distance2Tuple, Vector3Tuple, Vector4Tuple
85
85
  from pygeodesy.props import deprecated_Property_RO, Property_RO, property_doc_, \
86
- deprecated_property_RO, property_RO
86
+ deprecated_property_RO, property_RO, property_ROver
87
87
  from pygeodesy.streprs import Fmt, fstr, instr, strs, unstr
88
88
  # from pygeodesy.triaxials import _hartzell3 # _MODS
89
89
  from pygeodesy.units import Bearing_, Distance, Float, Float_, Height, Lamd, Lat, Meter, \
@@ -93,7 +93,7 @@ from pygeodesy.utily import atan1, atan1d, atan2b, degrees90, m2radians, radians
93
93
  from math import asinh, atan, atanh, cos, degrees, exp, fabs, radians, sin, sinh, sqrt, tan
94
94
 
95
95
  __all__ = _ALL_LAZY.ellipsoids
96
- __version__ = '24.06.24'
96
+ __version__ = '24.07.25'
97
97
 
98
98
  _f_0_0 = Float(f =_0_0) # zero flattening
99
99
  _f__0_0 = Float(f_=_0_0) # zero inverse flattening
@@ -1029,17 +1029,18 @@ class Ellipsoid(_NamedEnumItem):
1029
1029
  raise _ValueError(exact=g, ellipsoid=E, txt_not_=self.name)
1030
1030
  return g
1031
1031
 
1032
- @property_RO
1032
+ @property_ROver
1033
1033
  def _Geodesics(self):
1034
1034
  '''(INTERNAL) Get all C{Geodesic...} classes, I{once}.
1035
1035
  '''
1036
+ t = (_MODS.geodesicx.GeodesicExact,
1037
+ _MODS.geodsolve.GeodesicSolve)
1036
1038
  try:
1037
- t = _MODS.geodesicw._wrapped.Geodesic,
1039
+ t += (_MODS.geodesicw.Geodesic,
1040
+ _MODS.geodesicw._wrapped.Geodesic)
1038
1041
  except ImportError:
1039
- t = ()
1040
- Ellipsoid._Geodesics = t = (_MODS.geodesicx.GeodesicExact, # overwrite property_RO
1041
- _MODS.geodsolve.GeodesicSolve) + t
1042
- return t
1042
+ pass
1043
+ return t # overwrite property_ROver
1043
1044
 
1044
1045
  @property_RO
1045
1046
  def geodesicw(self):
@@ -1517,14 +1518,13 @@ class Ellipsoid(_NamedEnumItem):
1517
1518
  # raise _IsnotError(_ellipsoidal_, ellipsoid=self)
1518
1519
  return _MODS.rhumb.ekx.Rhumb(self, name=self.name)
1519
1520
 
1520
- @property_RO
1521
+ @property_ROver
1521
1522
  def _Rhumbs(self):
1522
1523
  '''(INTERNAL) Get all C{Rhumb...} classes, I{once}.
1523
1524
  '''
1524
1525
  p = _MODS.rhumb
1525
- Ellipsoid._Rhumbs = t = (p.aux_.RhumbAux, # overwrite property_RO
1526
- p.ekx.Rhumb, p.solve.RhumbSolve)
1527
- return t
1526
+ return (p.aux_.RhumbAux, # overwrite property_ROver
1527
+ p.ekx.Rhumb, p.solve.RhumbSolve)
1528
1528
 
1529
1529
  @property
1530
1530
  def rhumbsolve(self):
pygeodesy/formy.py CHANGED
@@ -42,7 +42,7 @@ from contextlib import contextmanager
42
42
  from math import asin, atan, atan2, cos, degrees, fabs, radians, sin, sqrt # pow
43
43
 
44
44
  __all__ = _ALL_LAZY.formy
45
- __version__ = '24.06.15'
45
+ __version__ = '24.07.29'
46
46
 
47
47
  _RADIANS2 = (PI / _180_0)**2 # degrees- to radians-squared
48
48
  _ratio_ = 'ratio'
@@ -962,7 +962,7 @@ def hartzell(pov, los=False, earth=_WGS84, **name_LatLon_and_kwds):
962
962
  B{C{datum}} if different and to convert from B{C{earth}}.
963
963
 
964
964
  @return: The intersection (L{Vector3d}, B{C{pov}}'s C{cartesian type} or the
965
- given B{C{LatLon}} instance) with attribute C{heigth} set to the
965
+ given B{C{LatLon}} instance) with attribute C{height} set to the
966
966
  distance to the B{C{pov}}.
967
967
 
968
968
  @raise IntersectionError: Invalid B{C{pov}} or B{C{pov}} inside the earth or
pygeodesy/fsums.py CHANGED
@@ -273,11 +273,17 @@ def _stresidual(prefix, residual, R=0, **mod_ratio):
273
273
  def _2sum(a, b): # by .testFmath
274
274
  '''(INTERNAL) Return C{a + b} as 2-tuple (sum, residual).
275
275
  '''
276
+ # Neumaier, A. U{Rundungsfehleranalyse einiger Verfahren zur Summation endlicher
277
+ # Summen<https://OnlineLibrary.Wiley.com/doi/epdf/10.1002/zamm.19740540106>},
278
+ # 1974, Zeitschrift für Angewandte Mathmatik und Mechanik, vol 51, nr 1, p 39-51
279
+ # <https://StackOverflow.com/questions/78633770/can-neumaier-summation-be-sped-up>
276
280
  s = a + b
277
281
  if _isfinite(s):
278
282
  if fabs(a) < fabs(b):
279
- b, a = a, b
280
- return s, (b - (s - a))
283
+ r = (b - s) + a
284
+ else:
285
+ r = (a - s) + b
286
+ return s, r
281
287
  u = unstr(_2sum, a, b)
282
288
  t = Fmt.PARENSPACED(_not_finite_, s)
283
289
  raise _OverflowError(u, txt=t)
@@ -1909,10 +1915,10 @@ class Fsum(_Named): # sync __methods__ with .vector3dBase.Vector3dBase
1909
1915
 
1910
1916
  @raise ResidualError: Invalid B{C{threshold}}.
1911
1917
 
1912
- @note: L{ResidualError}s may be thrown if the non-zero I{ratio}
1913
- C{residual / fsum} exceeds the given B{C{threshold}} and
1914
- if the C{residual} is non-zero and I{significant} vs the
1915
- C{fsum}, i.e. C{(fsum + residual) != fsum} and if optional
1918
+ @note: L{ResidualError}s may be thrown if (1) the non-zero I{ratio}
1919
+ C{residual / fsum} exceeds the given B{C{threshold}} and (2)
1920
+ the C{residual} is non-zero and (3) I{significant} vs the
1921
+ C{fsum}, i.e. C{(fsum + residual) != fsum} and (4) optional
1916
1922
  keyword argument C{raiser=False} is missing. Specify a
1917
1923
  negative B{C{threshold}} for only non-zero C{residual}
1918
1924
  testing without I{significant}.
pygeodesy/gars.py CHANGED
@@ -3,33 +3,33 @@
3
3
 
4
4
  u'''I{Global Area Reference System} (GARS) en-/decoding.
5
5
 
6
- Classes L{Garef} and L{GARSError} and several functions to encode,
7
- decode and inspect I{Global Area Reference System} (GARS) references.
8
-
9
- Transcoded from C++ class U{GARS
10
- <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GARS.html>}
11
- by I{Charles Karney}. See also U{Global Area Reference System
12
- <https://WikiPedia.org/wiki/Global_Area_Reference_System>} and U{NGA (GARS)
13
- <https://Earth-Info.NGA.mil/GandG/coordsys/grids/gars.html>}.
6
+ Class L{Garef} and several functions to encode, decode and inspect
7
+ GARS references.
8
+
9
+ Transcoded from I{Charles Karney}'s C++ class U{GARS
10
+ <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GARS.html>}.
11
+
12
+ @see: U{Global Area Reference System
13
+ <https://WikiPedia.org/wiki/Global_Area_Reference_System>} and U{NGA
14
+ (GARS)<https://Earth-Info.NGA.mil/GandG/coordsys/grids/gars.html>}.
14
15
  '''
15
16
 
16
17
  # from pygeodesy.basics import isstr # from .named
17
18
  from pygeodesy.constants import _off90, _1_over, _0_5, \
18
19
  _1_0 # PYCHOK used!
19
- from pygeodesy.dms import parse3llh, Fmt # parseDMS2
20
20
  from pygeodesy.errors import _ValueError, _xkwds, _xStrError
21
- from pygeodesy.interns import NN, _0to9_, _AtoZnoIO_, _COMMA_
21
+ from pygeodesy.interns import NN, _0to9_, _AtoZnoIO_, _COMMA_, _SPACE_
22
22
  from pygeodesy.lazily import _ALL_LAZY, _ALL_OTHER
23
- from pygeodesy.named import _name__, isstr, Property_RO
23
+ from pygeodesy.named import _name__, Fmt, isstr, Property_RO
24
24
  from pygeodesy.namedTuples import LatLon2Tuple, LatLonPrec3Tuple
25
25
  # from pygeodesy.props import Property_RO # from .named
26
- # from pygeodesy.streprs import Fmt # from .dms
26
+ # from pygeodesy.streprs import Fmt # from .named
27
27
  from pygeodesy.units import Int_, Lat, Lon, Precision_, Scalar_, Str
28
28
 
29
29
  from math import floor
30
30
 
31
31
  __all__ = _ALL_LAZY.gars
32
- __version__ = '24.06.15'
32
+ __version__ = '24.08.02'
33
33
 
34
34
  _Digits = _0to9_
35
35
  _LatLen = 2
@@ -88,7 +88,7 @@ def _2garstr2(garef):
88
88
  if n < _MinLen or n > _MaxLen \
89
89
  or garstr[:3] == 'INV' \
90
90
  or not garstr.isalnum():
91
- raise ValueError
91
+ raise ValueError()
92
92
  return garstr, _2Precision(n - _MinLen)
93
93
 
94
94
  except (AttributeError, TypeError, ValueError) as x:
@@ -111,45 +111,45 @@ class Garef(Str):
111
111
  '''Garef class, a named C{str}.
112
112
  '''
113
113
  # no str.__init__ in Python 3
114
- def __new__(cls, cll, precision=1, **name):
115
- '''New L{Garef} from an other L{Garef} instance or garef
116
- C{str} or from a C{LatLon} instance or lat-/longitude C{str}.
117
-
118
- @arg cll: Cell or location (L{Garef} or C{str}, C{LatLon}
119
- or C{str}).
120
- @kwarg precision: Optional, the desired garef resolution
121
- and length (C{int} 0..2), see function
122
- L{gars.encode} for more details.
114
+ def __new__(cls, lat_gll, lon=None, precision=1, **name):
115
+ '''New L{Garef} from an other L{Garef} instance or garef C{str}
116
+ or from a lat- and longitude.
117
+
118
+ @arg lat_gll: Latitude (C{degrees90}), a garef (L{Garef},
119
+ C{str}) or a location (C{LatLon}, C{LatLon*Tuple}).
120
+ @kwarg lon: Logitude (C{degrees180)}, required if B{C{lat_gll}}
121
+ is C{degrees90}, ignored otherwise.
122
+ @kwarg precision: The desired garef resolution and length (C{int}
123
+ 0..2), see L{encode<pygeodesy.gars.encode>}.
123
124
  @kwarg name: Optional C{B{name}=NN} (C{str}).
124
125
 
125
126
  @return: New L{Garef}.
126
127
 
127
- @raise RangeError: Invalid B{C{cll}} lat- or longitude.
128
+ @raise GARSError: INValid B{C{lat_gll}}.
128
129
 
129
- @raise TypeError: Invalid B{C{cll}}.
130
+ @raise RangeError: Invalid B{C{lat_gll}} or B{C{lon}}.
130
131
 
131
- @raise GARSError: INValid or non-alphanumeric B{C{cll}}.
132
+ @raise TypeError: Invalid B{C{lat_gll}} or B{C{lon}}.
132
133
  '''
133
- ll = p = None
134
-
135
- if isinstance(cll, Garef):
136
- g, p = _2garstr2(str(cll))
137
-
138
- elif isstr(cll):
139
- if _COMMA_ in cll:
140
- ll = _2fll(*parse3llh(cll))
141
- g = encode(*ll, precision=precision) # PYCHOK false
142
- else:
143
- g = cll.upper()
144
-
145
- else: # assume LatLon
146
- try:
147
- ll = _2fll(cll.lat, cll.lon)
148
- g = encode(*ll, precision=precision) # PYCHOK false
149
- except AttributeError:
150
- raise _xStrError(Garef, cll=cll) # Error=GARSError
151
-
152
- self = Str.__new__(cls, g, name=_name__(name, _or_nameof=cll))
134
+ if lon is None:
135
+ if isinstance(lat_gll, Garef):
136
+ g, ll, p = str(lat_gll), lat_gll.latlon, lat_gll.precision
137
+ elif isstr(lat_gll):
138
+ ll = lat_gll.replace(_COMMA_, _SPACE_).split()
139
+ if len(ll) > 1:
140
+ g, ll, p = _encode3(ll[0], ll[1], precision)
141
+ else:
142
+ g, ll = lat_gll.upper(), None
143
+ _, p = _2garstr2(g) # validate
144
+ else: # assume LatLon
145
+ try:
146
+ g, ll, p = _encode3(lat_gll.lat, lat_gll.lon, precision)
147
+ except AttributeError:
148
+ raise _xStrError(Garef, gll=lat_gll, Error=GARSError)
149
+ else:
150
+ g, ll, p = _encode3(lat_gll, lon, precision)
151
+
152
+ self = Str.__new__(cls, g, name=_name__(name, _or_nameof=lat_gll))
153
153
  self._latlon = ll
154
154
  self._precision = p
155
155
  return self
@@ -251,7 +251,7 @@ def decode3(garef, center=True, **name):
251
251
  precision, name=n)
252
252
 
253
253
 
254
- def encode(lat, lon, precision=1): # MCCABE 14
254
+ def encode(lat, lon, precision=1):
255
255
  '''Encode a lat-/longitude as a C{garef} of the given precision.
256
256
 
257
257
  @arg lat: Latitude (C{degrees}).
@@ -269,6 +269,13 @@ def encode(lat, lon, precision=1): # MCCABE 14
269
269
  resolution is B{30′} for B{C{precision}} 0, B{15′} for 1
270
270
  and B{5′} for 2, respectively.
271
271
  '''
272
+ g, _, _ = _encode3(lat, lon, precision)
273
+ return g
274
+
275
+
276
+ def _encode3(lat, lon, precision): # MCCABE 14
277
+ '''Return 3-tuple C{(garef, (lat, lon), p)}.
278
+ '''
272
279
  def _digit(x, y, m):
273
280
  return _Digits[m * (m - y - 1) + x + 1],
274
281
 
@@ -282,12 +289,12 @@ def encode(lat, lon, precision=1): # MCCABE 14
282
289
  p = _2Precision(precision)
283
290
 
284
291
  lat, lon = _2fll(lat, lon)
285
- lat = _off90(lat)
286
292
 
287
- ix, x = _2divmod2(lon, _LonOrig_M4)
288
- iy, y = _2divmod2(lat, _LatOrig_M4)
293
+ ix, x = _2divmod2( lon, _LonOrig_M4)
294
+ iy, y = _2divmod2(_off90(lat), _LatOrig_M4)
289
295
 
290
- g = _str(_Digits, ix + 1, _LonLen) + _str(_Letters, iy, _LatLen)
296
+ g = _str(_Digits, ix + 1, _LonLen) + \
297
+ _str(_Letters, iy, _LatLen)
291
298
  if p > 0:
292
299
  ix, x = divmod(x, _M3)
293
300
  iy, y = divmod(y, _M3)
@@ -295,7 +302,7 @@ def encode(lat, lon, precision=1): # MCCABE 14
295
302
  if p > 1:
296
303
  g += _digit(x, y, _M3)
297
304
 
298
- return NN.join(g)
305
+ return NN.join(g), (lat, lon), p
299
306
 
300
307
 
301
308
  def precision(res):