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 _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 __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 _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 _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