def _reframeTransforms(rf2, rf, epoch): '''(INTERNAL) Get 0, 1 or 2 Helmert C{Transforms} to convert reference frame C{rf} observed at C{epoch} into C{rf2}. ''' n2 = rf2.name # .upper() n1 = rf.name # .upper() if n1 == n2 or (n1.startswith(_S.ITRF) and n2.startswith(_S.WGS84)) \ or (n2.startswith(_S.ITRF) and n1.startswith(_S.WGS84)): return () # PYCHOK returns if (n1, n2) in _trfXs: return (_2Transform((n1, n2), epoch, _Forward), ) # PYCHOK returns if (n2, n1) in _trfXs: return (_2Transform((n2, n1), epoch, _Reverse), ) # PYCHOK returns n = _intermediate(n1, n2) if n: return ( _2Transform((n1, n), epoch, _Forward), # PYCHOK returns _2Transform((n, n2), epoch, _Forward)) n = _intermediate(n2, n1) if n: return ( _2Transform((n, n1), epoch, _Reverse), # PYCHOK returns _2Transform((n2, n), epoch, _Reverse)) t = _SPACE_(RefFrame.__name__, repr(n1), _to_, repr(n2)) raise TRFError(_no_(_conversion_), txt=t)
def _zetaInv(self, taup, lam): '''(INTERNAL) Invert C{zeta} using Newton's method. @return: 2-Tuple C{(u, v)}. @see: C{void TMExact::zetainv(real taup, real lam, real &u, real &v)}. @raise EllipticError: No convergence. ''' psi = asinh(taup) sca = _1_0 / hypot1(taup) u, v, trip = self._zetaInv0(psi, lam) if trip: self._iteration = 0 else: stol2 = _TOL_10 / max(psi**2, _1_0) U, V = Fsum(u), Fsum(v) # min iterations = 2, max = 6, mean = 4.0 for self._iteration in range(1, _TRIPS): # GEOGRAPHICLIB_PANIC sncndn6 = self._sncndn6(u, v) T, L, _ = self._zeta3(*sncndn6) dw, dv = self._zetaDwd(*sncndn6) T = (taup - T) * sca L -= lam u, du = U.fsum2_(T * dw, L * dv) v, dv = V.fsum2_(T * dv, -L * dw) if trip: break trip = hypot2(du, dv) < stol2 else: t = unstr(self._zetaInv.__name__, taup, lam) raise EllipticError(_no_(_convergence_), txt=t) return u, v
def _sigmaInv(self, xi, eta): '''(INTERNAL) Invert C{sigma} using Newton's method. @return: 2-Tuple C{(u, v)}. @see: C{void TMExact::sigmainv(real xi, real eta, real &u, real &v)}. @raise EllipticError: No convergence. ''' u, v, trip = self._sigmaInv0(xi, eta) if trip: self._iteration = 0 else: U, V = Fsum(u), Fsum(v) # min iterations = 2, max = 7, mean = 3.9 for self._iteration in range(1, _TRIPS): # GEOGRAPHICLIB_PANIC sncndn6 = self._sncndn6(u, v) X, E, _ = self._sigma3(v, *sncndn6) dw, dv = self._sigmaDwd(*sncndn6) X = xi - X E -= eta u, du = U.fsum2_(X * dw, E * dv) v, dv = V.fsum2_(X * dv, -E * dw) if trip: break trip = hypot2(du, dv) < _TOL_10 else: t = unstr(self._sigmaInv.__name__, xi, eta) raise EllipticError(_no_(_convergence_), txt=t) return u, v
def _trilaterate5(p1, d1, p2, d2, p3, d3, area=True, eps=EPS1, radius=R_M, wrap=False): # (INTERNAL) Trilaterate three points by area overlap or # by perimeter intersection of three circles (radius is # only needed for spherical LatLon.distanceTo) r1 = Distance_(distance1=d1) r2 = Distance_(distance2=d2) r3 = Distance_(distance3=d3) m = 0 if area else (r1 + r2 + r3) pc = 0 t = [] for _ in range(3): try: c1, c2 = p1.intersections2(r1, p2, r2, wrap=wrap) if area: # check overlap if c1 is c2: # abutting c = c1 else: # nearest point on radical c = p3.nearestOn(c1, c2, within=True, wrap=wrap) d = r3 - p3.distanceTo(c, radius=radius, wrap=wrap) if d > eps: # sufficient overlap t.append((d, c)) m = max(m, d) else: # check intersection for c in ((c1,) if c1 is c2 else (c1, c2)): d = abs(r3 - p3.distanceTo(c, radius=radius, wrap=wrap)) if d < eps: # below margin t.append((d, c)) m = min(m, d) except IntersectionError as x: if _near_concentric_ in str(x): # XXX ConcentricError? pc += 1 p1, r1, p2, r2, p3, r3 = p2, r2, p3, r3, p1, r1 # rotate if t: # get min, max, points and count ... t = tuple(sorted(t)) n = len(t), # as tuple # ... or for a single trilaterated result, # min *is* max, min- *is* maxPoint and n=1 return Trilaterate5Tuple(*(t[0] + t[-1] + n)) if area and pc == 3: # all pairwise concentric ... r, p = min((r1, p1), (r2, p2), (r3, p3)) # ... return smallest point twice, the smallest # and largest distance and n=0 for concentric return Trilaterate5Tuple(float(r), p, float(max(r1, r2, r3)), p, 0) f = max if area else min t = _no_(_overlap_ if area else _intersection_) t = '%s (%s %.3f)' % (t, f.__name__, m) raise IntersectionError(area=area, eps=eps, wrap=wrap, txt=t)
def reverse(self, x, y, name=NN, LatLon=None, **LatLon_kwds): '''Convert an azimuthal gnomonic location to (ellipsoidal) geodetic lat- and longitude. @arg x: Easting of the location (C{meter}). @arg y: Northing of the location (C{meter}). @kwarg name: Optional name for the location (C{str}). @kwarg LatLon: Class to use (C{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if C{B{LatLon}=None}. @return: The geodetic (C{LatLon}) or if B{C{LatLon}} is C{None} an L{Azimuthal7Tuple}C{(x, y, lat, lon, azimuth, scale, datum)}. @raise AzimuthalError: No convergence. @note: The C{lat} will be in the range C{[-90..90] degrees} and C{lon} in the range C{[-180..180] degrees}. The C{azimuth} is clockwise from true North. The scale is C{1 / reciprocal**2} in C{radial} direction and C{1 / reciprocal} in the direction perpendicular to this. ''' x = Scalar(x=x) y = Scalar(y=y) z = atan2d(x, y) # (x, y) for azimuth from true North q = hypot(x, y) d = e = self.equatoradius s = e * atan(q / e) if q > e: def _d(r, q): return (r.M12 - q * r.m12) * r.m12 # negated q = 1 / q else: # little == True def _d(r, q): # PYCHOK _d return (q * r.M12 - r.m12) * r.M12 # negated e *= _Karney_eps S = Fsum(s) g = self.geodesic.Line(self.lat0, self.lon0, z, self._mask) for self._iteration in range(1, _TRIPS): r = g.Position(s, self._mask) if abs(d) < e: break s, d = S.fsum2_(_d(r, q)) else: raise AzimuthalError(x=x, y=y, txt=_no_(Fmt.convergence(e))) t = self._7Tuple(x, y, r, r.M12) if LatLon is None else \ self._toLatLon(r.lat2, r.lon2, LatLon, LatLon_kwds) t._iteration = self._iteration return self._xnamed(t, name=name)
def _direct(self, distance, bearing, llr, height=None): '''(INTERNAL) Direct Vincenty method. @raise TypeError: The B{C{other}} point is not L{LatLon}. @raise ValueError: If this and the B{C{other}} point's L{Datum} ellipsoids are not compatible. @raise VincentyError: Vincenty fails to converge for the current L{LatLon.epsilon} and L{LatLon.iterations} limit. ''' E = self.ellipsoid() c1, s1, t1 = _r3(self.lat, E.f) i = radians(bearing) # initial bearing (forward azimuth) si, ci = sincos2(i) s12 = atan2(t1, ci) * 2 sa = c1 * si c2a = 1 - sa**2 if c2a < EPS: c2a = 0 A, B = 1, 0 else: # e22 == (a / b)**2 - 1 A, B = _p2(c2a * E.e22) s = d = distance / (E.b * A) for self._iteration in range(1, self._iterations + 1): ss, cs = sincos2(s) c2sm = cos(s12 + s) s_, s = s, d + _ds(B, cs, ss, c2sm) if abs(s - s_) < self._epsilon: break else: raise VincentyError(_no_(_convergence_), txt=repr(self)) # self.toRepr() t = s1 * ss - c1 * cs * ci # final bearing (reverse azimuth +/- 180) r = atan2b(sa, -t) if llr: # destination latitude in [-90, 90) a = degrees90( atan2(s1 * cs + c1 * ss * ci, (1 - E.f) * hypot(sa, t))) # destination longitude in [-180, 180) b = degrees180( atan2(ss * si, c1 * cs - s1 * ss * ci) - _dl(E.f, c2a, sa, s, cs, ss, c2sm) + radians(self.lon)) h = self.height if height is None else height d = self.classof(a, b, height=h, datum=self.datum) else: d = None return Destination2Tuple(d, r)
def __getattr__(name): # __getattr__ only for Python 3.7+ # only called once for each undefined pygeodesy attribute if name in imports: # importlib.import_module() implicitly sets sub-modules # on this module as appropriate for direct imports (see # note in the _lazy_import.__doc__ above). mod, _, attr = imports[name].partition(_DOT_) if mod not in imports: raise LazyImportError(_no_(_module_), txt=_DOT_(parent, mod)) imported = import_module(_DOT_(_pygeodesy_, mod), parent) pkg = getattr(imported, _p_a_c_k_a_g_e_, None) if pkg not in packages: # invalid package raise LazyImportError(_DOT_(mod, _p_a_c_k_a_g_e_), repr(pkg)) # import the module or module attribute if attr: imported = getattr(imported, attr, MISSING) elif name != mod: imported = getattr(imported, name, MISSING) if imported is MISSING: raise LazyImportError(_no_(_attribute_), txt=_DOT_(mod, attr or name)) elif name in (_a_l_l_,): # XXX '_d_i_r_', '_m_e_m_b_e_r_s_'? imported = _ALL_INIT + tuple(imports.keys()) mod = NN else: raise LazyImportError(_no_(_module_, _or_, _attribute_), txt=_DOT_(parent, name)) setattr(package, name, imported) if isLazy > 1: z = NN if mod and mod != name: z = ' from .%s' % (mod,) if isLazy > 2: try: # see C{_caller3} _, f, s = _caller3(2) z = '%s by %s line %d' % (z, f, s) except ValueError: # PYCHOK no cover pass print('# lazily imported %s%s' % (_DOT_(parent, name), z)) return imported # __getattr__
def trilaterate5( self, distance1, point2, distance2, point3, distance3, # PYCHOK signature area=False, eps=EPS1, radius=R_M, wrap=False): '''B{Not implemented} for C{B{area}=True} or C{B{wrap}=True} and falls back to method C{trilaterate} otherwise. @return: A L{Trilaterate5Tuple}C{(min, minPoint, max, maxPoint, n)} with a single trilaterated intersection C{minPoint I{is} maxPoint}, C{min I{is} max} the nearest intersection margin and count C{n = 1}. @raise IntersectionError: No intersection, trilateration failed. @raise NotImplementedError: Keyword argument C{B{area}=True} or B{C{wrap}=True} not (yet) supported. @raise TypeError: Invalid B{C{point2}} or B{C{point3}}. @raise ValueError: Some B{C{points}} coincide or invalid B{C{distance1}}, B{C{distance2}}, B{C{distance3}} or B{C{radius}}. ''' if area or wrap: from pygeodesy.named import notImplemented notImplemented(self, self.trilaterate5, area=area, wrap=wrap) t = _trilaterate(self, distance1, self.others(point2=point2), distance2, self.others(point3=point3), distance3, radius=radius, height=None, useZ=True, LatLon=self.classof) # ... and handle B{C{eps}} and C{IntersectionError} as # method C{.latlonBase.LatLonBase.trilaterate2} d = self.distanceTo(t, radius=radius, wrap=wrap) # PYCHOK distanceTo d = abs(distance1 - d), abs(distance2 - d), abs(distance3 - d) d = float(min(d)) if d < eps: # min is max, minPoint is maxPoint return Trilaterate5Tuple(d, t, d, t, 1) # n = 1 t = _SPACE_(_no_(_intersection_), Fmt.PAREN(min.__name__, Fmt.f(d, prec=3))) raise IntersectionError(area=area, eps=eps, wrap=wrap, txt=t)
def elevation2(lat, lon, timeout=2.0): '''Get the geoid elevation at an C{NAD83} to C{NAVD88} location. @arg lat: Latitude (C{degrees}). @arg lon: Longitude (C{degrees}). @kwarg timeout: Optional, query timeout (seconds). @return: An L{Elevation2Tuple}C{(elevation, data_source)} or (C{None, "error"}) in case of errors. @raise ValueError: Invalid B{C{timeout}}. @note: The returned C{elevation} is C{None} if B{C{lat}} or B{C{lon}} is invalid or outside the C{Conterminous US (CONUS)}, if conversion failed or if the query timed out. The C{error} is the C{HTTP-, IO-, SSL-, Type-, URL-} or C{ValueError} as a string (C{str}). @see: U{USGS National Map<https://NationalMap.gov/epqs>}, the U{FAQ<https://www.USGS.gov/faqs/what-are-projection- horizontal-and-vertical-datum-units-and-resolution-3dep-standard-dems>}, U{geoid.py<https://Gist.GitHub.com/pyRobShrk>}, module L{geoids}, classes L{GeoidG2012B}, L{GeoidKarney} and L{GeoidPGM}. ''' try: x = _qURL('https://NED.USGS.gov/epqs/pqs.php', # 'https://NationalMap.gov/epqs/pqs.php' x=Lon(lon).toStr(prec=6), y=Lat(lat).toStr(prec=6), units='Meters', # 'Feet', capitalized output=_XML_.lower(), # _JSON_, lowercase only timeout=Scalar(timeout=timeout)) if x[:6] == '<?xml ': e = _xml('Elevation', x) try: e = float(e) if -1000000 < e < 1000000: return Elevation2Tuple(e, _xml('Data_Source', x)) e = 'non-CONUS %.2F' % (e,) except (TypeError, ValueError): pass else: e = _no_(_XML_, Fmt.QUOTE2(clips(x, limit=128, white=_SPACE_))) except (HTTPError, IOError, TypeError, ValueError) as x: e = repr(x) e = _error(elevation2, lat, lon, e) return Elevation2Tuple(None, e)
def geoidHeight2(lat, lon, model=0, timeout=2.0): '''Get the C{NAVD88} geoid height at an C{NAD83} location. @arg lat: Latitude (C{degrees}). @arg lon: Longitude (C{degrees}). @kwarg model: Optional, geoid model ID (C{int}). @kwarg timeout: Optional, query timeout (seconds). @return: An L{GeoidHeight2Tuple}C{(height, model_name)} or C{(None, "error"}) in case of errors. @raise ValueError: Invalid B{C{timeout}}. @note: The returned C{height} is C{None} if B{C{lat}} or B{C{lon}} is invalid or outside the C{Conterminous US (CONUS)}, if the B{C{model}} was invalid, if conversion failed or if the query timed out. The C{error} is the C{HTTP-, IO-, SSL-, Type-, URL-} or C{ValueError} as a string (C{str}). @see: U{NOAA National Geodetic Survey <https://www.NGS.NOAA.gov/INFO/geodesy.shtml>}, U{Geoid<https://www.NGS.NOAA.gov/web_services/geoid.shtml>}, U{USGS10mElev.py<https://Gist.GitHub.com/pyRobShrk>}, module L{geoids}, classes L{GeoidG2012B}, L{GeoidKarney} and L{GeoidPGM}. ''' try: j = _qURL('https://Geodesy.NOAA.gov/api/geoid/ght', lat=Lat(lat).toStr(prec=6), lon=Lon(lon).toStr(prec=6), model=(model if model else NN), timeout=Scalar(timeout=timeout)) # PYCHOK indent if j[:1] == '{' and j[-1:] == '}' and j.find('"error":') > 0: d, e = _json(j), 'geoidHeight' if isinstance(_xkwds_get(d, error=_n_a_), float): h = d.get(e, None) if h is not None: m = _xkwds_get(d, geoidModel=_n_a_) return GeoidHeight2Tuple(h, m) else: e = _JSON_ e = _no_(e, Fmt.QUOTE2(clips(j, limit=256, white=_SPACE_))) except (HTTPError, IOError, ParseError, TypeError, ValueError) as x: e = repr(x) e = _error(geoidHeight2, lat, lon, e) return GeoidHeight2Tuple(None, e)
def _xml(tag, xml): '''(INTERNAL) Get a <tag>value</tag> from XML. ''' # b'<?xml version="1.0" encoding="utf-8" ?> # <USGS_Elevation_Point_Query_Service> # <Elevation_Query x="-121.914200" y="37.881600"> # <Data_Source>3DEP 1/3 arc-second</Data_Source> # <Elevation>3851.03</Elevation> # <Units>Feet</Units> # </Elevation_Query> # </USGS_Elevation_Point_Query_Service>' i = xml.find(Fmt.TAG(tag)) if i > 0: i += len(tag) + 2 j = xml.find(Fmt.TAGEND(tag), i) if j > i: return Str(xml[i:j].strip(), name=tag) return _no_(_XML_, Fmt.TAG(tag))
def _tanf(self, txi): # called from .Ellipsoid.auxAuthalic '''(INTERNAL) Function M{tan-phi from tan-xi}. ''' tol = _tol(_TOL, txi) e2 = self.datum.ellipsoid.e2 qx = self._qx ta = txi Ta = Fsum(ta) for self._iteration in range(1, _NUMIT): # max 2, mean 1.99 # dtxi/dta = (scxi / sca)^3 * 2 * (1 - e^2) / (qZ * (1 - e^2 * sa^2)^2) ta2 = ta**2 sca2 = ta2 + _1_0 txia = self._txif(ta) s3qx = sqrt3(sca2 / (_1_0 + txia**2)) * qx ta, d = Ta.fsum2_( (txi - txia) * s3qx * (_1_0 - e2 * ta2 / sca2)**2) if abs(d) < tol: return ta raise AlbersError(iteration=_NUMIT, txt=_no_(Fmt.convergence(tol)))
def convertRefFrame(self, reframe2): '''Convert this point to an other reference frame. @arg reframe2: Reference frame to convert I{to} (L{RefFrame}). @return: The converted point (ellipsoidal C{LatLon}) or this point if conversion is C{nil}. @raise TRFError: No B{C{.reframe}} or no conversion available from B{C{.reframe}} to B{C{reframe2}}. @raise TypeError: The B{C{reframe2}} is not a L{RefFrame}. @example: >>> p = LatLon(51.4778, -0.0016, reframe=RefFrames.ETRF2000) # default Datums.WGS84 >>> p.convertRefFrame(RefFrames.ITRF2014) # 51.477803°N, 000.001597°W, +0.01m ''' from pygeodesy.trf import RefFrame, _reframeTransforms _xinstanceof(RefFrame, reframe2=reframe2) if not self.reframe: t = _SPACE_(_DOT_(repr(self), 'reframe'), MISSING) raise TRFError(_no_(_conversion_), txt=t) ts = _reframeTransforms(reframe2, self.reframe, self.epoch) if ts: c = self.toCartesian() for t in ts: c = c._applyHelmert(t, False) ll = c.toLatLon(datum=self.datum, LatLon=self.classof, epoch=self.epoch, reframe=reframe2) # ll.reframe, ll.epoch = reframe2, self.epoch else: ll = self return ll
def __init__(self, sa1, ca1, sa2, ca2, k, datum, name): '''(INTERNAL) New C{AlbersEqualArea...} instance. ''' if datum not in (None, self._datum): self._datum = _ellipsoidal_datum(datum, name=name) if name: self.name = name E = self.datum.ellipsoid b_a = E.b_a # fm = 1 - E.f e2 = E.e2 e12 = E.e12 # e2m = 1 - E.e2 self._qZ = qZ = _1_0 + e12 * self._atanhee(1) self._qZa2 = qZ * E.a2 self._qx = qZ / (_2_0 * e12) c = min(ca1, ca2) if c < 0: raise AlbersError(clat1=ca1, clat2=ca2) polar = c < _EPS__2 # == 0 # determine hemisphere of tangent latitude if sa1 < 0: # and sa2 < 0: self._sign = -1 # internally, tangent latitude positive sa1, sa2 = -sa1, neg(sa2) if sa1 > sa2: # make phi1 < phi2 sa1, sa2 = sa2, sa1 ca1, ca2 = ca2, ca1 if sa1 < 0: # or sa2 < 0: raise AlbersError(slat1=sa1, slat2=sa2) # avoid singularities at poles ca1, ca2 = max(_EPS__2, ca1), max(_EPS__2, ca2) ta1, ta2 = sa1 / ca1, sa2 / ca2 par1 = abs(ta1 - ta2) < _EPS__4 # ta1 == ta2 if par1 or polar: C, ta0 = _1_0, ta2 else: s1_qZ, C = self._s1_qZ_C2(ca1, sa1, ta1, ca2, sa2, ta2) ta0 = (ta2 + ta1) * _0_5 Ta0 = Fsum(ta0) tol = _tol(_TOL0, ta0) for self._iteration in range(1, _NUMIT0): ta02 = ta0**2 sca02 = ta02 + _1_0 sca0 = sqrt(sca02) sa0 = ta0 / sca0 sa01 = sa0 + _1_0 sa02 = sa0**2 # sa0m = 1 - sa0 = 1 / (sec(a0) * (tan(a0) + sec(a0))) sa0m = _1_0 / (sca0 * (ta0 + sca0)) # scb0^2 * sa0 g = (_1_0 + (b_a * ta0)**2) * sa0 dg = e12 * sca02 * (_1_0 + 2 * ta02) + e2 D = sa0m * (_1_0 - e2 * (_1_0 + sa01 * 2 * sa0)) / (e12 * sa01) # dD/dsa0 dD = -2 * (_1_0 - e2 * sa02 * (_3_0 + 2 * sa0)) / (e12 * sa01**2) sa02_ = _1_0 - e2 * sa02 sa0m_ = sa0m / (_1_0 - e2 * sa0) BA = sa0m_ * (self._atanhx1(e2 * sa0m_**2) * e12 - e2 * sa0m) \ - sa0m**2 * e2 * (2 + (_1_0 + e2) * sa0) / (e12 * sa02_) # == B + A dAB = 2 * e2 * (2 - e2 * (_1_0 + sa02)) / (e12 * sa02_**2 * sca02) u_du = fsum_(s1_qZ * g, -D, g * BA) \ / fsum_(s1_qZ * dg, -dD, dg * BA, g * dAB) # == u/du ta0, d = Ta0.fsum2_(-u_du * (sca0 * sca02)) if abs(d) < tol: break else: raise AlbersError(iteration=_NUMIT0, txt=_no_(Fmt.convergence(tol))) self._txi0 = txi0 = self._txif(ta0) self._scxi0 = hypot1(txi0) self._sxi0 = sxi0 = txi0 / self._scxi0 self._m02 = m02 = _1_0 / (_1_0 + (b_a * ta0)**2) self._n0 = n0 = ta0 / hypot1(ta0) if polar: self._polar = True self._nrho0 = self._m0 = _0_0 else: self._m0 = sqrt(m02) # == nrho0 / E.a self._nrho0 = E.a * self._m0 # == E.a * sqrt(m02) self._k0_(_1_0 if par1 else (k * sqrt(C / (m02 + n0 * qZ * sxi0)))) self._lat0 = _Lat(lat0=self._sign * atand(ta0))
def _convergenceError(where, *args): # PYCHOK no cover '''(INTERNAL) Return an L{EllipticError}. ''' t = _SPACE_(where.__name__, repr(args)) return EllipticError(_no_(_convergence_), txt=t) # unstr
def _inverse(self, other, azis, wrap): '''(INTERNAL) Inverse Vincenty method. @raise TypeError: The B{C{other}} point is not L{LatLon}. @raise ValueError: If this and the B{C{other}} point's L{Datum} ellipsoids are not compatible. @raise VincentyError: Vincenty fails to converge for the current L{LatLon.epsilon} and L{LatLon.iterations} limit and/or if this and the B{C{other}} point are coincident or near-antipodal. ''' E = self.ellipsoids(other) c1, s1, _ = _r3(self.lat, E.f) c2, s2, _ = _r3(other.lat, E.f) c1c2, s1c2 = c1 * c2, s1 * c2 c1s2, s1s2 = c1 * s2, s1 * s2 dl, _ = unroll180(self.lon, other.lon, wrap=wrap) ll = dl = radians(dl) for self._iteration in range(1, self._iterations + 1): ll_ = ll sll, cll = sincos2(ll) ss = hypot(c2 * sll, c1s2 - s1c2 * cll) if ss < EPS: # coincident or antipodal, ... if self.isantipodeTo(other, eps=self._epsilon): t = '%r %sto %r' % (self, _antipodal_, other) raise VincentyError(_ambiguous_, txt=t) # return zeros like Karney, but unlike Veness return Distance3Tuple(0.0, 0, 0) cs = s1s2 + c1c2 * cll s = atan2(ss, cs) sa = c1c2 * sll / ss c2a = 1 - sa**2 if abs(c2a) < EPS: c2a = 0 # equatorial line ll = dl + E.f * sa * s else: c2sm = cs - 2 * s1s2 / c2a ll = dl + _dl(E.f, c2a, sa, s, cs, ss, c2sm) if abs(ll - ll_) < self._epsilon: break # # omitted and applied only after failure to converge below, see footnote # # under Inverse at <https://WikiPedia.org/wiki/Vincenty's_formulae> # # <https://GitHub.com/ChrisVeness/geodesy/blob/master/latlon-vincenty.js> # elif abs(ll) > PI and self.isantipodeTo(other, eps=self._epsilon): # raise VincentyError('%s, %r %sto %r' % ('ambiguous', self, # _antipodal_, other)) else: t = _antipodal_ if self.isantipodeTo(other, eps=self._epsilon) else NN t = _SPACE_(repr(self), NN(t, _to_), repr(other)) raise VincentyError(_no_(_convergence_), txt=t) if c2a: # e22 == (a / b)**2 - 1 A, B = _p2(c2a * E.e22) s = A * (s - _ds(B, cs, ss, c2sm)) b = E.b # if self.height or other.height: # b += self._havg(other) d = b * s if azis: # forward and reverse azimuth sll, cll = sincos2(ll) f = atan2b(c2 * sll, c1s2 - s1c2 * cll) r = atan2b(c1 * sll, -s1c2 + c1s2 * cll) else: f = r = 0 return Distance3Tuple(d, f, r)
def _intersects2( c1, r1, c2, r2, height=None, wrap=True, # MCCABE 17 equidistant=None, tol=_TOL_M, LatLon=None, **LatLon_kwds): # (INTERNAL) Intersect two spherical circles, see L{_intersections2} # above, separated to allow callers to embellish any exceptions from pygeodesy.sphericalTrigonometry import _intersects2 as _si2, LatLon as _LLS from pygeodesy.vector3d import _intersects2 as _vi2 def _latlon4(t, h, n): r = _LatLon4Tuple(t.lat, t.lon, h, t.datum, LatLon, LatLon_kwds) r._iteration = t.iteration # ._iteration for tests return _xnamed(r, n) if r1 < r2: c1, c2 = c2, c1 r1, r2 = r2, r1 E = c1.ellipsoids(c2) if r1 > (min(E.b, E.a) * PI): raise ValueError(_exceed_PI_radians_) if wrap: # unroll180 == .karney._unroll2 c2 = _unrollon(c1, c2) # distance between centers and radii are # measured along the ellipsoid's surface m = c1.distanceTo(c2, wrap=False) # meter if m < max(r1 - r2, EPS): raise ValueError(_near_concentric_) if fsum_(r1, r2, -m) < 0: raise ValueError(_too_(Fmt.distant(m))) f = _radical2(m, r1, r2).ratio # "radical fraction" r = E.rocMean(favg(c1.lat, c2.lat, f=f)) e = max(m2degrees(tol, radius=r), EPS) # get the azimuthal equidistant projection A = _Equidistant2(equidistant, c1.datum) # gu-/estimate initial intersections, spherically ... t1, t2 = _si2(_LLS(c1.lat, c1.lon, height=c1.height), r1, _LLS(c2.lat, c2.lon, height=c2.height), r2, radius=r, height=height, wrap=False, too_d=m) h, n = t1.height, t1.name # ... and then iterate like Karney suggests to find # tri-points of median lines, @see: references under # method LatLonEllipsoidalBase.intersections2 above ts, ta = [], None for t in ((t1, ) if t1 is t2 else (t1, t2)): p = None # force first d == p to False for i in range(1, _TRIPS): A.reset(t.lat, t.lon) # gu-/estimate as origin # convert centers to projection space t1 = A.forward(c1.lat, c1.lon) t2 = A.forward(c2.lat, c2.lon) # compute intersections in projection space v1, v2 = _vi2( t1, r1, # XXX * t1.scale?, t2, r2, # XXX * t2.scale?, sphere=False, too_d=m) # convert intersections back to geodetic t1 = A.reverse(v1.x, v1.y) d1 = euclid(t1.lat - t.lat, t1.lon - t.lon) if v1 is v2: # abutting t, d = t1, d1 else: t2 = A.reverse(v2.x, v2.y) d2 = euclid(t2.lat - t.lat, t2.lon - t.lon) # consider only the closer intersection t, d = (t1, d1) if d1 < d2 else (t2, d2) # break if below tolerance or if unchanged if d < e or d == p: t._iteration = i # _NamedTuple._iteration ts.append(t) if v1 is v2: # abutting ta = t break p = d else: raise ValueError(_no_(Fmt.convergence(tol))) if ta: # abutting circles r = _latlon4(ta, h, n) elif len(ts) == 2: return _latlon4(ts[0], h, n), _latlon4(ts[1], h, n) elif len(ts) == 1: # XXX assume abutting r = _latlon4(ts[0], h, n) else: raise _AssertionError(ts=ts) return r, r
def _nearestOn(p, p1, p2, within=True, height=None, wrap=True, equidistant=None, tol=_TOL_M, LatLon=None, **LatLon_kwds): # (INTERNAL) Get closet point, like L{_intersects2} above, # separated to allow callers to embellish any exceptions from pygeodesy.sphericalNvector import LatLon as _LLS from pygeodesy.vector3d import _nearestOn as _vnOn, Vector3d def _v(t, h): return Vector3d(t.x, t.y, h) _ = p.ellipsoids(p1) E = p.ellipsoids(p2) if wrap: p1 = _unrollon(p, p1) p2 = _unrollon(p, p2) p2 = _unrollon(p1, p2) r = E.rocMean(fmean_(p.lat, p1.lat, p2.lat)) e = max(m2degrees(tol, radius=r), EPS) # get the azimuthal equidistant projection A = _Equidistant2(equidistant, p.datum) # gu-/estimate initial nearestOn, spherically ... wrap=False t = _LLS(p.lat, p.lon, height=p.height).nearestOn(_LLS(p1.lat, p1.lon, height=p1.height), _LLS(p2.lat, p2.lon, height=p2.height), within=within, height=height) n = t.name h = h1 = h2 = 0 if height is False: # use height as Z component h = t.height h1 = p1.height h2 = p2.height # ... and then iterate like Karney suggests to find # tri-points of median lines, @see: references under # method LatLonEllipsoidalBase.intersections2 above c = None # force first d == c to False # closest to origin, .z to interpolate height p = Vector3d(0, 0, h) for i in range(1, _TRIPS): A.reset(t.lat, t.lon) # gu-/estimate as origin # convert points to projection space t1 = A.forward(p1.lat, p1.lon) t2 = A.forward(p2.lat, p2.lon) # compute nearestOn in projection space v = _vnOn(p, _v(t1, h1), _v(t2, h2), within=within) # convert nearestOn back to geodetic r = A.reverse(v.x, v.y) d = euclid(r.lat - t.lat, r.lon - t.lon) # break if below tolerance or if unchanged t = r if d < e or d == c: t._iteration = i # _NamedTuple._iteration if height is False: h = v.z # nearest interpolated break c = d else: raise ValueError(_no_(Fmt.convergence(tol))) r = _LatLon4Tuple(t.lat, t.lon, h, t.datum, LatLon, LatLon_kwds) r._iteration = t.iteration # ._iteration for tests return _xnamed(r, n)
def _trilaterate3d2(c1, r1, c2, r2, c3, r3, eps=EPS, Vector=None, **Vector_kwds): # MCCABE 13 # (INTERNAL) Intersect three spheres or circles, see L{trilaterate3d2} # above, separated to allow callers to embellish any exceptions def _0f3d(F): # map numpy 4-vector to floats and split F0, x, y, z = map(float, F) return F0, Vector3d(x, y, z) def _N3(t01, x, z): # compute x, y and z and return as Vector v = x.plus(z.times(t01)) n = trilaterate3d2.__name__ return _V_n(v, n, Vector, Vector_kwds) def _real_roots(numpy, *coeffs): # non-complex roots of a polynomial rs = numpy.polynomial.polynomial.polyroots(coeffs) return tuple(float(r) for r in rs if not numpy.iscomplex(r)) def _txt(c1, r1, c2, r2): # check for concentric or too distant spheres d = c1.minus(c2).length if d < abs(r1 - r2): t = _near_concentric_ elif d > (r1 + r2): t = _too_(Fmt.distant(d)) else: return NN return _SPACE_(c1.name, 'and', c2.name, t) np = Vector3d._numpy if np is None: # get numpy, once or ImportError Vector3d._numpy = np = _xnumpy(trilaterate3d2, 1, 10) # macOS' Python 2.7 numpy 1.8 OK c2 = _otherV3d(center2=c2) c3 = _otherV3d(center3=c3) A = [] # 3 x 4 b = [] # 1 x 3 or 3 x 1 for c, d in ((c1, r1), (c2, Radius_(radius2=r2, low=eps)), (c3, Radius_(radius3=r3, low=eps))): A.append((_1_0, -2 * c.x, -2 * c.y, -2 * c.z)) b.append(d**2 - c.length2) try: # <https://NumPy.org/doc/stable/reference/generated/numpy.seterr.html> e = np.seterr(all='raise') # throw FloatingPointError for numpy errors X = np.dot(np.linalg.pinv(A), b) # Moore-Penrose pseudo-inverse Z, _ = _null_space2(np, A, eps) if Z is None: t = () # coincident, colinear, concentric, etc. else: X0, x = _0f3d(X) Z0, z = _0f3d(Z) # quadratic polynomial coefficients, ordered (^0, ^1, ^2) t = _real_roots(np, x.length2 - X0, # fdot(X, -_1_0, *x.xyz) z.dot(x) * 2 - Z0, # fdot(Z, -_0_5, *x.xyz) * 2 z.length2) # fdot(Z, _0_0, *z.xyz) finally: # restore numpy error handling np.seterr(**e) if not t: # coincident, colinear, too distant, no intersection, etc. raise FloatingPointError(_txt(c1, r1, c2, r2) or _txt(c1, r1, c3, r3) or _txt(c2, r2, c3, r3) or (_colinear_ if _iscolinearWith(c1, c2, c3, eps=eps) else _no_(_intersection_))) elif len(t) < 2: # one intersection t *= 2 v = _N3(t[0], x, z) if abs(t[0] - t[1]) < eps: # abutting t = v, v else: # "lowest" intersection first (to avoid test failures) u = _N3(t[1], x, z) t = (u, v) if u < v else (v, u) return t
def _lazy_import2(_pygeodesy_): # MCCABE 15 '''Check for and set up C{lazy import}. @arg _pygeodesy_: The name of the package (C{str}) performing the imports, to help facilitate resolving relative imports, usually C{__package__}. @return: 2-Tuple C{(package, getattr)} of the importing package for easy reference within itself and the callable to be set to `__getattr__`. @raise LazyImportError: Lazy import not supported or not enabled, an import failed or the package name or module name or attribute name is invalid or does not exist. @note: This is the original function U{modutil.lazy_import <https://GitHub.com/brettcannon/modutil/blob/master/modutil.py>} modified to handle the C{__all__} and C{__dir__} attributes and call C{importlib.import_module(<module>.<name>, ...)} without causing a C{ModuleNotFoundError}. @see: The original U{modutil<https://PyPi.org/project/modutil>} and U{PEP 562<https://www.Python.org/dev/peps/pep-0562>}. ''' if _sys.version_info[:2] < (3, 7): # not supported before 3.7 t = _no_(_DOT_(_pygeodesy_, _lazy_import2.__name__)) raise LazyImportError(t, txt=_Python_(_sys)) import_module, package, parent = _lazy_init3(_pygeodesy_) packages = (parent, '__main__', NN, _DOT_(parent, _deprecated_)) imports = _all_imports() def __getattr__(name): # __getattr__ only for Python 3.7+ # only called once for each undefined pygeodesy attribute if name in imports: # importlib.import_module() implicitly sets sub-modules # on this module as appropriate for direct imports (see # note in the _lazy_import.__doc__ above). mod, _, attr = imports[name].partition(_DOT_) if mod not in imports: raise LazyImportError(_no_(_module_), txt=_DOT_(parent, mod)) imported = import_module(_DOT_(_pygeodesy_, mod), parent) pkg = getattr(imported, _p_a_c_k_a_g_e_, None) if pkg not in packages: # invalid package raise LazyImportError(_DOT_(mod, _p_a_c_k_a_g_e_), repr(pkg)) # import the module or module attribute if attr: imported = getattr(imported, attr, MISSING) elif name != mod: imported = getattr(imported, name, MISSING) if imported is MISSING: raise LazyImportError(_no_(_attribute_), txt=_DOT_(mod, attr or name)) elif name in (_a_l_l_,): # XXX '_d_i_r_', '_m_e_m_b_e_r_s_'? imported = _ALL_INIT + tuple(imports.keys()) mod = NN else: raise LazyImportError(_no_(_module_, _or_, _attribute_), txt=_DOT_(parent, name)) setattr(package, name, imported) if isLazy > 1: z = NN if mod and mod != name: z = ' from .%s' % (mod,) if isLazy > 2: try: # see C{_caller3} _, f, s = _caller3(2) z = '%s by %s line %d' % (z, f, s) except ValueError: # PYCHOK no cover pass print('# lazily imported %s%s' % (_DOT_(parent, name), z)) return imported # __getattr__ return package, __getattr__ # _lazy_import2