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 _ in range(self._iterations): 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('%s, %r' % ('no convergence', self)) t = s1 * ss - c1 * cs * ci # final bearing (reverse azimuth +/- 180) r = degrees360(atan2(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 r = self.classof(a, b, height=h, datum=self.datum), r return r
def vincentys_(a2, a1, b21): '''Compute the I{angular} distance between two (spherical) points using U{Vincenty's<http://WikiPedia.org/wiki/Great-circle_distance>} spherical formula. @param a2: End latitude (C{radians}). @param a1: Start latitude (C{radians}). @param b21: Longitudinal delta, M{end-start} (C{radians}). @return: Angular distance (C{radians}). @see: Functions L{vincentys}, L{equirectangular_}, L{euclidean_} and L{haversine_}. @note: Functions L{vincentys_} and L{haversine_} produce equivalent results, but L{vincentys_} is suitable for antipodal points and slightly more expensive than L{haversine_} (M{3 cos, 3 sin, 1 hypot, 1 atan2, 7 mul, 2 add} versus M{2 cos, 2 sin, 2 sqrt, 1 atan2, 5 mul, 1 add}). ''' sa1, ca1, sa2, ca2, sb21, cb21 = sincos2(a1, a2, b21) x = sa1 * sa2 + ca1 * ca2 * cb21 y = ca1 * sa2 - sa1 * ca2 * cb21 y = hypot(ca2 * sb21, y) return atan2(y, x)
def crossingParallels(self, other, lat, wrap=False): '''Return the pair of meridians at which a great circle defined by this and an other point crosses the given latitude. @param other: The other point defining great circle (L{LatLon}). @param lat: Latitude at the crossing (C{degrees}). @keyword wrap: Wrap and unroll longitudes (C{bool}). @return: 2-Tuple C{(lon1, lon2)}, both in C{degrees180} or C{None} if the great circle doesn't reach B{C{lat}}. ''' self.others(other) a1, b1 = self.to2ab() a2, b2 = other.to2ab() a = radians(lat) db, b2 = unrollPI(b1, b2, wrap=wrap) sa, ca, sa1, ca1, \ sa2, ca2, sdb, cdb = sincos2(a, a1, a2, db) x = sa1 * ca2 * ca * sdb y = sa1 * ca2 * ca * cdb - ca1 * sa2 * ca z = ca1 * ca2 * sa * sdb h = hypot(x, y) if h < EPS or abs(z) > h: return None # great circle doesn't reach latitude m = atan2(-y, x) + b1 # longitude at max latitude d = acos1(z / h) # delta longitude to intersections return degrees180(m - d), degrees180(m + d)
def toLcc(latlon, conic=Conics.WRF_Lb, height=None, Lcc=Lcc, name=''): '''Convert an (ellipsoidal) geodetic point to a Lambert location. @param latlon: Ellipsoidal point (C{LatLon}). @keyword conic: Optional Lambert projection to use (L{Conic}). @keyword height: Optional height for the point, overriding the default height (C{meter}). @keyword Lcc: Optional (sub-)class to return the Lambert location (L{Lcc}). @keyword name: Optional B{C{Lcc}} name (C{str}). @return: The Lambert location (L{Lcc}) or an L{EasNor3Tuple}C{(easting, northing, height)} if B{C{Lcc}} is C{None}. @raise TypeError: If B{C{latlon}} is not ellipsoidal. ''' if not isinstance(latlon, _LLEB): raise TypeError('%s not %s: %r' % ('latlon', 'ellipsoidal', latlon)) a, b = latlon.to2ab() c = conic.toDatum(latlon.datum) t = c._n * (b - c._lon0) - c._opt3 st, ct = sincos2(t) r = c._rdef(c._tdef(a)) e = c._E0 + r * st n = c._N0 + c._r0 - r * ct h = latlon.height if height is None else height r = EasNor3Tuple(e, n, h) if Lcc is None else \ Lcc(e, n, h=h, conic=c) return _xnamed(r, name or nameof(latlon))
def greatCircle(self, bearing): '''Compute the vector normal to great circle obtained by heading on the given bearing from this point. Direction of vector is such that initial bearing vector b = c × n, where n is an n-vector representing this point. @param bearing: Bearing from this point (compass C{degrees360}). @return: N-vector representing great circle (L{Nvector}). @example: >>> p = LatLon(53.3206, -1.7297) >>> gc = p.greatCircle(96.0) >>> gc.toStr() # [-0.794, 0.129, 0.594] ''' a, b = self.to2ab() t = radians(bearing) sa, ca, sb, cb, st, ct = sincos2(a, b, t) return Nvector(sb * ct - sa * cb * st, -cb * ct - sa * sb * st, ca * st, name=self.name) # XXX .unit()
def _x3d2(start, end, wrap, n, hs): # see <https://www.EdWilliams.org/intersect.htm> (5) ff a1, b1 = start.to2ab() if isscalar(end): # bearing, make a point a2, b2 = _destination2_(a1, b1, PI_4, radians(end)) else: # must be a point _Trll.others(end, name='end' + n) hs.append(end.height) a2, b2 = end.to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) if max(abs(db), abs(a2 - a1)) < EPS: raise ValueError('intersection %s%s null: %r' % ('path', n, (start, end))) # note, in EdWilliams.org/avform.htm W is + and E is - b21, b12 = db * 0.5, -(b1 + b2) * 0.5 sb21, cb21, sb12, cb12, \ sa21, _, sa12, _ = sincos2(b21, b12, a1 - a2, a1 + a2) x = Vector3d(sa21 * sb12 * cb21 - sa12 * cb12 * sb21, sa21 * cb12 * cb21 + sa12 * sb12 * sb21, cos(a1) * cos(a2) * sin(db), ll=start) return x.unit(), (db, (a2 - a1)) # negated d
def bearing_(a1, b1, a2, b2, final=False, wrap=False): '''Compute the initial or final bearing (forward or reverse azimuth) between a (spherical) start and end point. @param a1: Start latitude (C{radians}). @param b1: Start longitude (C{radians}). @param a2: End latitude (C{radians}). @param b2: End longitude (C{radians}). @keyword final: Return final bearing if C{True}, initial otherwise (C{bool}). @keyword wrap: Wrap and L{unrollPI} longitudes (C{bool}). @return: Initial or final bearing (compass C{radiansPI2}) or zero if start and end point coincide. ''' if final: a1, b1, a2, b2 = a2, b2, a1, b1 r = PI + PI2 else: r = PI2 db, _ = unrollPI(b1, b2, wrap=wrap) sa1, ca1, sa2, ca2, sdb, cdb = sincos2(a1, a2, db) # see <http://MathForum.org/library/drmath/view/55417.html> x = ca1 * sa2 - sa1 * ca2 * cdb y = sdb * ca2 return (atan2(y, x) + r) % PI2 # wrapPI2
def intermediateTo(self, other, fraction, height=None, wrap=False): '''Locate the point at given fraction between this and an other point. @param other: The other point (L{LatLon}). @param fraction: Fraction between both points (float, 0.0 = this point, 1.0 = the other point). @keyword height: Optional height, overriding the fractional height (C{meter}). @keyword wrap: Wrap and unroll longitudes (C{bool}). @return: Intermediate point (L{LatLon}). @raise TypeError: The B{C{other}} point is not L{LatLon}. @example: >>> p1 = LatLon(52.205, 0.119) >>> p2 = LatLon(48.857, 2.351) >>> p = p1.intermediateTo(p2, 0.25) # 51.3721°N, 000.7073°E @JSname: I{intermediatePointTo}. ''' self.others(other) a1, b1 = self.to2ab() a2, b2 = other.to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) r = haversine_(a2, a1, db) sr = sin(r) if abs(sr) > EPS: sa1, ca1, sa2, ca2, \ sb1, cb1, sb2, cb2 = sincos2(a1, a2, b1, b2) A = sin((1 - fraction) * r) / sr B = sin(fraction * r) / sr x = A * ca1 * cb1 + B * ca2 * cb2 y = A * ca1 * sb1 + B * ca2 * sb2 z = A * sa1 + B * sa2 a = atan2(z, hypot(x, y)) b = atan2(y, x) else: # points too close a = favg(a1, a2, f=fraction) b = favg(b1, b2, f=fraction) if height is None: h = self._havg(other, f=fraction) else: h = height return self.classof(degrees90(a), degrees180(b), height=h)
def to3llh(self, datum=Datums.WGS84): '''Convert this (geocentric) Cartesian (x/y/z) point to (ellipsoidal, geodetic) lat-, longitude and height on the given datum. Uses Bowring’s (1985) formulation for μm precision in concise form: U{'The accuracy of geodetic latitude and height equations' <http://www.ResearchGate.net/publication/ 233668213_The_Accuracy_of_Geodetic_Latitude_and_Height_Equations>}, B. R. Bowring, Survey Review, Vol 28, 218, Oct 1985. See also Ralph M. Toms U{'An Efficient Algorithm for Geocentric to Geodetic Coordinate Conversion'<http://www.OSTI.gov/scitech/biblio/110235>}, Sept 1995 and U{'An Improved Algorithm for Geocentric to Geodetic Coordinate Conversion'<http://www.OSTI.gov/scitech/servlets/purl/231228>}, Apr 1996, from Lawrence Livermore National Laboratory. @keyword datum: Optional datum to use (L{Datum}). @return: 3-Tuple (lat, lon, heigth) in (C{degrees90}, C{degrees180}, C{meter}). ''' E = datum.ellipsoid x, y, z = self.to3xyz() p = hypot(x, y) # distance from minor axis r = hypot(p, z) # polar radius if min(p, r) > EPS: # parametric latitude (Bowring eqn 17, replaced) t = (E.b * z) / (E.a * p) * (1 + E.e22 * E.b / r) c = 1 / hypot1(t) s = t * c # geodetic latitude (Bowring eqn 18) a = atan2(z + E.e22 * E.b * s**3, p - E.e2 * E.a * c**3) b = atan2(y, x) # ... and longitude # height above ellipsoid (Bowring eqn 7) sa, ca = sincos2(a) # r = E.a / E.e2s(sa) # length of normal terminated by minor axis # h = p * ca + z * sa - (E.a * E.a / r) h = fsum_(p * ca, z * sa, -E.a * E.e2s(sa)) a, b = degrees90(a), degrees180(b) # see <http://GIS.StackExchange.com/questions/28446> elif p > EPS: # latitude arbitrarily zero a, b, h = 0.0, degrees180(atan2(y, x)), p - E.a else: # polar latitude, longitude arbitrarily zero a, b, h = copysign(90.0, z), 0.0, abs(z) - E.b return a, b, h
def to3xyz(self): '''Convert this (geodetic) point to (n-)vector (normal to the earth's surface) x/y/z components, ignoring the height. @return: 3-Tuple (x, y, z) in (units, NOT meter). ''' # Kenneth Gade eqn 3, but using right-handed # vector x -> 0°E,0°N, y -> 90°E,0°N, z -> 90°N a, b = self.to2ab() sa, ca, sb, cb = sincos2(a, b) return ca * cb, ca * sb, sa
def _sigmaInv0(self, xi, eta): '''Starting point for C{sigmaInv}. @return: 3-Tuple C{(u, v, trip)}. @see: C{bool TMExact::sigmainv0(real xi, real eta, real &u, real &v)}. ''' trip = False if eta > self._Ev_KE_5_4 or xi < min(-self._Eu_E_1_4, eta - self._Ev_KE): # sigma as a simple pole at # w = w0 = Eu.K() + i * Ev.K() # and sigma is approximated by # sigma = (Eu.E() + i * Ev.KE()) + 1/(w - w0) x = xi - self._Eu_E y = eta - self._Ev_KE d = x**2 + y**2 u = self._Eu_K + x / d v = self._Ev_K - y / d elif eta > self._Ev_KE or (eta > self._Ev_KE_3_4 and xi < self._Eu_E_1_4): # At w = w0 = i * Ev.K(), we have # sigma = sigma0 = i * Ev.KE() # sigma' = sigma'' = 0 # # including the next term in the Taylor series gives: # sigma = sigma0 - _mv / 3 * (w - w0)^3 # # When inverting this, we map arg(w - w0) = [-pi/2, -pi/6] # to arg(sigma - sigma0) = [-pi/2, pi/2] # mapping arg = [-pi/2, -pi/6] to [-pi/2, pi/2] d = eta - self._Ev_KE r = hypot(xi, d) # Error using this guess is about 0.068 * rad^(5/3) trip = r < _TAYTOL2 # Map the range [-90, 180] in sigma space to [-90, 0] in # w space. See discussion in zetainv0 on the cut for ang. r = cbrt(r * self._3_mv) a = atan2(d - xi, xi + d) / 3.0 - PI_4 s, c = sincos2(a) u = r * c v = r * s + self._Ev_K else: # use w = sigma * Eu.K/Eu.E (correct in the limit _e -> 0) r = self._Eu_K / self._Eu_E u = xi * r v = eta * r return u, v, trip
def sncndn(self, x): '''The Jacobi elliptic function. @param x: The argument (C{float}). @return: 3-Tuple C{(sn(x, k), cn(x, k), dn(x, k))}. ''' # Bulirsch's sncndn routine, p 89. mc = self._kp2 if mc: if mc < 0: d = 1.0 - mc mc /= -d d = sqrt(d) x *= d else: d = 0 a, c, mn = 1.0, 0, [] for _ in range(self._trips_): # GEOGRAPHICLIB_PANIC # This converges quadratically, max 6 trips mc = sqrt(mc) mn.append((a, mc)) c = (a + mc) * 0.5 if not abs(a - mc) > (_tolJAC * a): break mc *= a a = c else: raise EllipticError('no %s convergence' % ('sncndn',)) x *= c dn = 1 sn, cn = sincos2(x) if sn: a = cn / sn c *= a while mn: m, n = mn.pop() a *= c c *= dn dn = (n + a) / (m + a) a = c / m sn = copysign(1.0 / hypot1(c), sn) cn = c * sn if d: cn, dn = dn, cn sn /= d else: sn = tanh(x) cn = dn = 1.0 / cosh(x) return sn, cn, dn
def to3xyz(self): '''Convert this (geodetic) point to (n-)vector (normal to the earth's surface) x/y/z components, ignoring the height. @return: A L{Vector3Tuple}C{(x, y, z)} in C{units}, NOT C{meter}. ''' # Kenneth Gade eqn 3, but using right-handed # vector x -> 0°E,0°N, y -> 90°E,0°N, z -> 90°N a, b = self.to2ab() sa, ca, sb, cb = sincos2(a, b) r = Vector3Tuple(ca * cb, ca * sb, sa) return self._xnamed(r)
def _destination2_(a, b, r, t): '''(INTERNAL) Computes destination lat- and longitude. @param a: Latitude (C{radians}). @param b: Longitude (C{radians}). @param r: Angular distance (C{radians}). @param t: Bearing (compass C{radians}). @return: 2-Tuple (lat, lon) of (radians, radians). ''' # see <https://www.EdWilliams.org/avform.htm#LL> sa, ca, sr, cr, st, ct = sincos2(a, r, t) a = asin(ct * sr * ca + cr * sa) d = atan2(st * sr * ca, cr - sa * sin(a)) # note, in EdWilliams.org/avform.htm W is + and E is - return a, b + d
def to3xyz(self): # overloads _LatLonHeightBase.to3xyz '''Convert this (ellipsoidal) geodetic C{LatLon} point to (geocentric) cartesian x/y/z components. @return: 3-Tuple (x, y, z) in (C{meter}). ''' a, b = self.to2ab() sa, ca, sb, cb = sincos2(a, b) E = self.ellipsoid() # radius of curvature in prime vertical t = E.e2s2(sa) if t > EPS1: r = E.a elif t > EPS: r = E.a / sqrt(t) else: r = 0 h = self.height t = (h + r) * ca return (t * cb, t * sb, (h + r * E.e12) * sa)
def intermediateTo(self, other, fraction, height=None): '''Locate the point at a given fraction between this and an other point. @param other: The other point (L{LatLon}). @param fraction: Fraction between both points (float, 0.0 = this point, 1.0 = the other point). @keyword height: Optional height at the intermediate point, overriding the fractional height (C{meter}). @return: Intermediate point (L{LatLon}). @raise TypeError: The B{C{other}} point is not L{LatLon}. @raise Valuerror: Points coincide. @example: >>> p = LatLon(52.205, 0.119) >>> q = LatLon(48.857, 2.351) >>> i = p.intermediateTo(q, 0.25) # 51.3721°N, 000.7074°E @JSname: I{intermediatePointTo}. ''' self.others(other) p = self.toNvector() q = other.toNvector() x = p.cross(q, raiser='points') d = x.unit().cross(p) # unit(p × q) × p # angular distance α, tan(α) = |p × q| / p ⋅ q s, c = sincos2(atan2(x.length, p.dot(q)) * fraction) # interpolated i = p.times(c).plus(d.times(s)) # p * cosα + d * sinα h = self._havg(other, f=fraction) if height is None else height return i.toLatLon( height=h, LatLon=self.classof) # Nvector(i.x, i.y, i.z).toLatLon(...)
def destination(self, distance, bearing, radius=R_M, height=None): '''Locate the destination from this point after having travelled the given distance on the given bearing. @param distance: Distance travelled (C{meter}, same units as B{C{radius}}). @param bearing: Bearing from this point (compass C{degrees360}). @keyword radius: Optional, mean earth radius (C{meter}). @keyword height: Optional height at destination, overriding the default height (C{meter}, same units as B{C{radius}}). @return: Destination point (L{LatLon}). @raise Valuerror: Polar coincidence. @example: >>> p = LatLon(51.4778, -0.0015) >>> q = p.destination(7794, 300.7) >>> q.toStr() # 51.513546°N, 000.098345°W @JSname: I{destinationPoint}. ''' p = self.toNvector() e = NorthPole.cross(p, raiser='pole').unit() # east vector at p n = p.cross(e) # north vector at p s, c = sincos2d(bearing) q = n.times(c).plus(e.times(s)) # direction vector @ p s, c = sincos2(float(distance) / float(radius)) # angular distance in radians n = p.times(c).plus(q.times(s)) return n.toLatLon( height=height, LatLon=self.classof) # Nvector(n.x, n.y, n.z).toLatLon(...)
def to3xyz(self): # overloads _LatLonHeightBase.to3xyz '''Convert this (ellipsoidal) geodetic C{LatLon} point to (geocentric) cartesian x/y/z components. @return: A L{Vector3Tuple}C{(x, y, z)}. ''' if self._3xyz is None: a, b = self.to2ab() sa, ca, sb, cb = sincos2(a, b) E = self.ellipsoid() # radius of curvature in prime vertical t = E.e2s2(sa) # r, _ = E.roc2_(sa, 1) if t < EPS: r = 0 elif t > EPS1: r = E.a else: r = E.a / sqrt(t) h = self.height t = (h + r) * ca self._3xyz = Vector3Tuple(t * cb, t * sb, (h + r * E.e12) * sa) return self._xrenamed(self._3xyz)
def midpointTo(self, other, height=None, wrap=False): '''Find the midpoint between this and an other point. @param other: The other point (L{LatLon}). @keyword height: Optional height for midpoint, overriding the mean height (C{meter}). @keyword wrap: Wrap and unroll longitudes (C{bool}). @return: Midpoint (L{LatLon}). @raise TypeError: The B{C{other}} point is not L{LatLon}. @example: >>> p1 = LatLon(52.205, 0.119) >>> p2 = LatLon(48.857, 2.351) >>> m = p1.midpointTo(p2) # '50.5363°N, 001.2746°E' ''' self.others(other) # see <https://MathForum.org/library/drmath/view/51822.html> a1, b1 = self.to2ab() a2, b2 = other.to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) sa1, ca1, sa2, ca2, sdb, cdb = sincos2(a1, a2, db) x = ca2 * cdb + ca1 y = ca2 * sdb a = atan2(sa1 + sa2, hypot(x, y)) b = atan2(y, x) + b1 h = self._havg(other) if height is None else height return self.classof(degrees90(a), degrees180(b), height=h)
def toUtm8(latlon, lon=None, datum=None, Utm=Utm, falsed=True, name='', zone=None, **cmoff): '''Convert a lat-/longitude point to a UTM coordinate. @param latlon: Latitude (C{degrees}) or an (ellipsoidal) geodetic C{LatLon} point. @keyword lon: Optional longitude (C{degrees}) or C{None}. @keyword datum: Optional datum for this UTM coordinate, overriding B{C{latlon}}'s datum (C{Datum}). @keyword Utm: Optional (sub-)class to return the UTM coordinate (L{Utm}) or C{None}. @keyword falsed: False both easting and northing (C{bool}). @keyword name: Optional B{C{Utm}} name (C{str}). @keyword zone: Optional UTM zone to enforce (C{int} or C{str}). @keyword cmoff: DEPRECATED, use B{C{falsed}}. Offset longitude from the zone's central meridian (C{bool}). @return: The UTM coordinate (B{C{Utm}}) or a L{UtmUps8Tuple}C{(zone, hemipole, easting, northing, band, datum, convergence, scale)} if B{C{Utm}} is C{None} or not B{C{falsed}}. The C{hemipole} is the C{'N'|'S'} hemisphere. @raise TypeError: If B{C{latlon}} is not ellipsoidal. @raise RangeError: If B{C{lat}} outside the valid UTM bands or if B{C{lat}} or B{C{lon}} outside the valid range and L{rangerrors} set to C{True}. @raise UTMError: Invalid B{C{zone}}. @raise ValueError: If B{C{lon}} value is missing or if B{C{latlon}} is invalid. @note: Implements Karney’s method, using 8-th order Krüger series, giving results accurate to 5 nm (or better) for distances up to 3900 km from the central meridian. @example: >>> p = LatLon(48.8582, 2.2945) # 31 N 448251.8 5411932.7 >>> u = toUtm(p) # 31 N 448252 5411933 >>> p = LatLon(13.4125, 103.8667) # 48 N 377302.4 1483034.8 >>> u = toUtm(p) # 48 N 377302 1483035 ''' z, B, lat, lon, d, f, name = _to7zBlldfn(latlon, lon, datum, falsed, name, zone, UTMError, **cmoff) E = d.ellipsoid a, b = radians(lat), radians(lon) # easting, northing: Karney 2011 Eq 7-14, 29, 35 sb, cb = sincos2(b) T = tan(a) T12 = hypot1(T) S = sinh(E.e * atanh(E.e * T / T12)) T_ = T * hypot1(S) - S * T12 H = hypot(T_, cb) y = atan2(T_, cb) # ξ' ksi x = asinh(sb / H) # η' eta A0 = E.A * getattr(Utm, '_scale0', _K0) # Utm is class or None K = _Kseries(E.AlphaKs, x, y) # Krüger series y = K.ys(y) * A0 # ξ x = K.xs(x) * A0 # η # convergence: Karney 2011 Eq 23, 24 p_ = K.ps(1) q_ = K.qs(0) c = degrees(atan(T_ / hypot1(T_) * tan(b)) + atan2(q_, p_)) # scale: Karney 2011 Eq 25 k = E.e2s(sin(a)) * T12 / H * (A0 / E.a * hypot(p_, q_)) t = z, lat, x, y, B, d, c, k, f return _toXtm8(Utm, t, name, latlon, EPS)
def toLatLon(self, LatLon=None, eps=EPS, unfalse=True): '''Convert this UTM coordinate to an (ellipsoidal) geodetic point. @keyword LatLon: Optional, ellipsoidal (sub-)class to return the point (C{LatLon}) or C{None}. @keyword eps: Optional convergence limit, L{EPS} or above (C{float}). @keyword unfalse: Unfalse B{C{easting}} and B{C{northing}} if falsed (C{bool}). @return: This UTM coordinate as (B{C{LatLon}}) or a L{LatLonDatum5Tuple}C{(lat, lon, datum, convergence, scale)} if B{C{LatLon}} is C{None}. @raise TypeError: If B{C{LatLon}} is not ellipsoidal. @raise UTMError: Invalid meridional radius or H-value. @example: >>> u = Utm(31, 'N', 448251.795, 5411932.678) >>> from pygeodesy import ellipsoidalVincenty as eV >>> ll = u.toLatLon(eV.LatLon) # 48°51′29.52″N, 002°17′40.20″E ''' if eps < EPS: eps = EPS # less doesn't converge if self._latlon and self._latlon_args == (eps, unfalse): return self._latlon5(LatLon) E = self.datum.ellipsoid # XXX vs LatLon.datum.ellipsoid x, y = self.to2en(falsed=not unfalse) # from Karney 2011 Eq 15-22, 36 A0 = self.scale0 * E.A if A0 < EPS: raise self._Error('%s invalid: %r' % ('meridional', E.A)) x /= A0 # η eta y /= A0 # ξ ksi K = _Kseries(E.BetaKs, x, y) # Krüger series y = -K.ys(-y) # ξ' x = -K.xs(-x) # η' shx = sinh(x) sy, cy = sincos2(y) H = hypot(shx, cy) if H < EPS: raise self._Error('%s invalid: %r' % ('H', H)) T = t0 = sy / H # τʹ S = Fsum(T) q = 1.0 / E.e12 P = 7 # -/+ toggle trips d = 1.0 + eps while abs(d) > eps and P > 0: p = -d # previous d, toggled h = hypot1(T) s = sinh(E.e * atanh(E.e * T / h)) t = T * hypot1(s) - s * h d = (t0 - t) / hypot1(t) * ((q + T**2) / h) T, d = S.fsum2_(d) # τi, (τi - τi-1) if d == p: # catch -/+ toggling of d P -= 1 # else: # P = 0 a = atan(T) # lat b = atan2(shx, cy) if unfalse and self.falsed: b += radians(_cmlon(self.zone)) ll = _LLEB(degrees90(a), degrees180(b), datum=self.datum, name=self.name) # convergence: Karney 2011 Eq 26, 27 p = -K.ps(-1) q = K.qs(0) ll._convergence = degrees(atan(tan(y) * tanh(x)) + atan2(q, p)) # scale: Karney 2011 Eq 28 ll._scale = E.e2s(sin(a)) * hypot1(T) * H * (A0 / E.a / hypot(p, q)) self._latlon_to(ll, eps, unfalse) return self._latlon5(LatLon)
def toOsgr(latlon, lon=None, datum=Datums.WGS84, Osgr=Osgr, name=''): '''Convert a lat-/longitude point to an OSGR coordinate. @param latlon: Latitude (C{degrees}) or an (ellipsoidal) geodetic C{LatLon} point. @keyword lon: Optional longitude in degrees (scalar or C{None}). @keyword datum: Optional datum to convert (C{Datum}). @keyword Osgr: Optional (sub-)class to return the OSGR coordinate (L{Osgr}) or C{None}. @keyword name: Optional I{Osgr} name (C{str}). @return: The OSGR coordinate (L{Osgr}) or 2-tuple (easting, northing) if I{Osgr} is C{None}. @raise TypeError: Non-ellipsoidal I{latlon} or I{datum} conversion failed. @raise ValueError: Invalid I{latlon} or I{lon}. @example: >>> p = LatLon(52.65798, 1.71605) >>> r = toOsgr(p) # TG 51409 13177 >>> # for conversion of (historical) OSGB36 lat-/longitude: >>> r = toOsgr(52.65757, 1.71791, datum=Datums.OSGB36) ''' if not isinstance(latlon, _LLEB): # XXX fix failing _LLEB.convertDatum() latlon = _LLEB(*parseDMS2(latlon, lon), datum=datum) elif lon is not None: raise ValueError('%s not %s: %r' % ('lon', None, lon)) elif not name: # use latlon.name name = _nameof(latlon) or name # PYCHOK no effect E = _OSGB36.ellipsoid ll = _ll2datum(latlon, _OSGB36, 'latlon') a, b = map1(radians, ll.lat, ll.lon) sa, ca = sincos2(a) s = E.e2s2(sa) v = E.a * _F0 / sqrt(s) # nu r = s / E.e12 # nu / rho == v / (v * E.e12 / s) x2 = r - 1 # η2 ta = tan(a) ca3, ca5 = fpowers(ca, 5, 3) # PYCHOK false! ta2, ta4 = fpowers(ta, 4, 2) # PYCHOK false! vsa = v * sa I4 = (E.b * _F0 * _M(E.Mabcd, a) + _N0, (vsa / 2) * ca, (vsa / 24) * ca3 * fsum_(5, -ta2, 9 * x2), (vsa / 720) * ca5 * fsum_(61, ta4, -58 * ta2)) V4 = (_E0, (v * ca), (v / 6) * ca3 * (r - ta2), (v / 120) * ca5 * fdot( (-18, 1, 14, -58), ta2, 5 + ta4, x2, ta2 * x2)) d, d2, d3, d4, d5, d6 = fpowers(b - _B0, 6) # PYCHOK false! n = fdot(I4, 1, d2, d4, d6) e = fdot(V4, 1, d, d3, d5) return (e, n) if Osgr is None else _xnamed(Osgr(e, n), name)
def toLatLon(self, LatLon=None, datum=Datums.WGS84): '''Convert this OSGR coordinate to an (ellipsoidal) geodetic point. I{Note formulation implemented here due to Thomas, Redfearn, etc. is as published by OS, but is inferior to Krüger as used by e.g. Karney 2011.} @keyword LatLon: Optional ellipsoidal (sub-)class to return the point (C{LatLon}) or C{None}. @keyword datum: Optional datum to use (C{Datum}). @return: The geodetic point (I{LatLon}) or 3-tuple (C{degrees90}, C{degrees180}, I{datum}) if I{LatLon} is C{None}. @raise TypeError: If I{LatLon} is not ellipsoidal or if I{datum} conversion failed. @example: >>> from pygeodesy import ellipsoidalVincenty as eV >>> g = Osgr(651409.903, 313177.270) >>> p = g.toLatLon(eV.LatLon) # 52°39′28.723″N, 001°42′57.787″E >>> # to obtain (historical) OSGB36 lat-/longitude point >>> p = g.toLatLon(eV.LatLon, datum=Datums.OSGB36) # 52°39′27.253″N, 001°43′04.518″E ''' if self._latlon: return self._latlon3(LatLon, datum) E = _OSGB36.ellipsoid # Airy130 a_F0 = E.a * _F0 b_F0 = E.b * _F0 e, n = self._easting, self._northing n_N0 = n - _N0 a, M = _A0, 0 sa = Fsum(a) while True: t = n_N0 - M if t < _10um: break a = sa.fsum_(t / a_F0) M = b_F0 * _M(E.Mabcd, a) sa, ca = sincos2(a) s = E.e2s2(sa) v = a_F0 / sqrt(s) # nu r = v * E.e12 / s # rho vr = v / r # == s / E.e12 x2 = vr - 1 # η2 ta = tan(a) v3, v5, v7 = fpowers(v, 7, 3) # PYCHOK false! ta2, ta4, ta6 = fpowers(ta**2, 3) # PYCHOK false! tar = ta / r V4 = (a, tar / (2 * v), tar / (24 * v3) * fdot( (1, 3, -9), 5 + x2, ta2, ta2 * x2), tar / (720 * v5) * fdot( (61, 90, 45), 1, ta2, ta4)) csa = 1.0 / ca X5 = (_B0, csa / v, csa / (6 * v3) * fsum_(vr, ta, ta), csa / (120 * v5) * fdot( (5, 28, 24), 1, ta2, ta4), csa / (5040 * v7) * fdot( (61, 662, 1320, 720), ta, ta2, ta4, ta6)) d, d2, d3, d4, d5, d6, d7 = fpowers(e - _E0, 7) # PYCHOK false! a = fdot(V4, 1, -d2, d4, -d6) b = fdot(X5, 1, d, -d3, d5, -d7) self._latlon = _LLEB(degrees90(a), degrees180(b), datum=_OSGB36, name=self.name) return self._latlon3(LatLon, datum)
def _zetaInv0(self, psi, lam): '''Starting point for C{zetaInv}. @return: 3-Tuple C{(u, v, trip)}. @see: C{bool TMExact::zetainv0(real psi, real lam, # radians real &u, real &v)}. ''' trip = False if (psi < -self._e_PI_4 and lam > self._1_e2_PI_2 and psi < lam - self._1_e_PI_2): # N.B. this branch is normally not taken because psi < 0 # is converted psi > 0 by Forward. # # There's a log singularity at w = w0 = Eu.K() + i * Ev.K(), # corresponding to the south pole, where we have, approximately # # psi = _e + i * pi/2 - _e * atanh(cos(i * (w - w0)/(1 + _mu/2))) # # Inverting this gives: h = sinh(1 - psi / self._e) a = (PI_2 - lam) / self._e s, c = sincos2(a) u = self._Eu_K - asinh(s / hypot(c, h)) * self._mu_2_1 v = self._Ev_K - atan2(c, h) * self._mu_2_1 elif (psi < self._e_PI_2 and lam > self._1_e2_PI_2): # At w = w0 = i * Ev.K(), we have # # zeta = zeta0 = i * (1 - _e) * pi/2 # zeta' = zeta'' = 0 # # including the next term in the Taylor series gives: # # zeta = zeta0 - (_mv * _e) / 3 * (w - w0)^3 # # When inverting this, we map arg(w - w0) = [-90, 0] to # arg(zeta - zeta0) = [-90, 180] d = lam - self._1_e_PI_2 r = hypot(psi, d) # Error using this guess is about 0.21 * (rad/e)^(5/3) trip = r < self._e_taytol_ # atan2(dlam-psi, psi+dlam) + 45d gives arg(zeta - zeta0) # in range [-135, 225). Subtracting 180 (since multiplier # is negative) makes range [-315, 45). Multiplying by 1/3 # (for cube root) gives range [-105, 15). In particular # the range [-90, 180] in zeta space maps to [-90, 0] in # w space as required. r = cbrt(r * self._3_mv_e) a = atan2(d - psi, psi + d) / 3.0 - PI_4 s, c = sincos2(a) u = r * c v = r * s + self._Ev_K else: # Use spherical TM, Lee 12.6 -- writing C{atanh(sin(lam) / # cosh(psi)) = asinh(sin(lam) / hypot(cos(lam), sinh(psi)))}. # This takes care of the log singularity at C{zeta = Eu.K()}, # corresponding to the north pole. s, c = sincos2(lam) h, r = sinh(psi), self._Eu_K / PI_2 # But scale to put 90, 0 on the right place u = r * atan2(h, c) v = r * asinh(s / hypot(c, h)) return u, v, trip
def _inverse(self, other, azis, wrap): '''(INTERNAL) Inverse Vincenty method. @raise TypeError: The other point is not L{LatLon}. @raise ValueError: If this and the I{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 I{other} point are near-antipodal or coincide. ''' 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 _ in range(self._iterations): ll_ = ll sll, cll = sincos2(ll) ss = hypot(c2 * sll, c1s2 - s1c2 * cll) if ss < EPS: # coincident, ... d = 0.0 # like Karney, ... if azis: # return zeros d = d, 0, 0 return d 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 # <http://GitHub.com/ChrisVeness/geodesy/blob/master/latlon-vincenty.js> # omitted and applied only after failure to converge, see footnote under # Inverse at <http://WikiPedia.org/wiki/Vincenty's_formulae> # elif abs(ll) > PI and self.isantipodeTo(other, eps=self._epsilon): # raise VincentyError('%r antipodal to %r' % (self, other)) else: t = 'antipodal ' if self.isantipodeTo(other, eps=self._epsilon) else '' raise VincentyError('no convergence, %r %sto %r' % (self, t, other)) 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 = degrees360(atan2(c2 * sll, c1s2 - s1c2 * cll)) r = degrees360(atan2(c1 * sll, -s1c2 + c1s2 * cll)) d = d, f, r return d
def intersection(start1, end1, start2, end2, height=None, wrap=False, LatLon=LatLon): '''Compute the intersection point of two paths both defined by two points or a start point and bearing from North. @param start1: Start point of the first path (L{LatLon}). @param end1: End point ofthe first path (L{LatLon}) or the initial bearing at the first start point (compass C{degrees360}). @param start2: Start point of the second path (L{LatLon}). @param end2: End point of the second path (L{LatLon}) or the initial bearing at the second start point (compass C{degrees360}). @keyword height: Optional height for the intersection point, overriding the mean height (C{meter}). @keyword wrap: Wrap and unroll longitudes (C{bool}). @keyword LatLon: Optional (sub-)class to return the intersection point (L{LatLon}) or C{None}. @return: The intersection point (B{C{LatLon}}) or a L{LatLon3Tuple}C{(lat, lon, height)} if B{C{LatLon}} is C{None}. An alternate intersection point might be the L{antipode} to the returned result. @raise TypeError: A B{C{start}} or B{C{end}} point not L{LatLon}. @raise ValueError: Intersection is ambiguous or infinite or the paths are parallel, coincident or null. @example: >>> p = LatLon(51.8853, 0.2545) >>> s = LatLon(49.0034, 2.5735) >>> i = intersection(p, 108.547, s, 32.435) # '50.9078°N, 004.5084°E' ''' _Trll.others(start1, name='start1') _Trll.others(start2, name='start2') hs = [start1.height, start2.height] a1, b1 = start1.to2ab() a2, b2 = start2.to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) r12 = haversine_(a2, a1, db) if abs(r12) < EPS: # [nearly] coincident points a, b = map1(degrees, favg(a1, a2), favg(b1, b2)) # see <https://www.EdWilliams.org/avform.htm#Intersection> elif isscalar(end1) and isscalar(end2): # both bearings sa1, ca1, sa2, ca2, sr12, cr12 = sincos2(a1, a2, r12) x1, x2 = (sr12 * ca1), (sr12 * ca2) if abs(x1) < EPS or abs(x2) < EPS: raise ValueError('intersection %s: %r vs %r' % ('parallel', (start1, end1), (start2, end2))) # handle domain error for equivalent longitudes, # see also functions asin_safe and acos_safe at # <https://www.EdWilliams.org/avform.htm#Math> t1, t2 = map1(acos1, (sa2 - sa1 * cr12) / x1, (sa1 - sa2 * cr12) / x2) if sin(db) > 0: t12, t21 = t1, PI2 - t2 else: t12, t21 = PI2 - t1, t2 t13, t23 = map1(radiansPI2, end1, end2) x1, x2 = map1( wrapPI, t13 - t12, # angle 2-1-3 t21 - t23) # angle 1-2-3 sx1, cx1, sx2, cx2 = sincos2(x1, x2) if sx1 == 0 and sx2 == 0: # max(abs(sx1), abs(sx2)) < EPS raise ValueError('intersection %s: %r vs %r' % ('infinite', (start1, end1), (start2, end2))) sx3 = sx1 * sx2 # if sx3 < 0: # raise ValueError('intersection %s: %r vs %r' % ('ambiguous', # (start1, end1), (start2, end2))) x3 = acos1(cr12 * sx3 - cx2 * cx1) r13 = atan2(sr12 * sx3, cx2 + cx1 * cos(x3)) a, b = _destination2(a1, b1, r13, t13) # choose antipode for opposing bearings if _xb(a1, b1, end1, a, b, wrap) < 0 or \ _xb(a2, b2, end2, a, b, wrap) < 0: a, b = antipode(a, b) else: # end point(s) or bearing(s) x1, d1 = _x3d2(start1, end1, wrap, '1', hs) x2, d2 = _x3d2(start2, end2, wrap, '2', hs) x = x1.cross(x2) if x.length < EPS: # [nearly] colinear or parallel paths raise ValueError('intersection %s: %r vs %r' % ('colinear', (start1, end1), (start2, end2))) a, b = x.to2ll() # choose intersection similar to sphericalNvector d1 = _xdot(d1, a1, b1, a, b, wrap) d2 = _xdot(d2, a2, b2, a, b, wrap) if (d1 < 0 and d2 > 0) or (d1 > 0 and d2 < 0): a, b = antipode(a, b) h = fmean(hs) if height is None else height r = LatLon3Tuple(a, b, h) if LatLon is None else \ LatLon(a, b, height=h) return _xnamed(r, intersection.__name__)
def _mdef(self, lat): '''(INTERNAL) Compute m(lat). ''' s, c = sincos2(lat) s *= self._e return c / sqrt(1 - s**2)
def toUtm8(latlon, lon=None, datum=None, Utm=Utm, cmoff=True, name='', zone=None): '''Convert a lat-/longitude point to a UTM coordinate. @param latlon: Latitude (C{degrees}) or an (ellipsoidal) geodetic C{LatLon} point. @keyword lon: Optional longitude (C{degrees}) or C{None}. @keyword datum: Optional datum for this UTM coordinate, overriding I{latlon}'s datum (C{Datum}). @keyword Utm: Optional (sub-)class to return the UTM coordinate (L{Utm}) or C{None}. @keyword cmoff: Offset longitude from zone's central meridian, apply false easting and false northing (C{bool}). @keyword name: Optional I{Utm} name (C{str}). @keyword zone: Optional zone to enforce (C{int} or C{str}). @return: The UTM coordinate (L{Utm}) or a 8-tuple (C{zone, hemisphere, easting, northing, band, datum, convergence, scale}) if I{Utm} is C{None} or I{cmoff} is C{False}. @raise TypeError: If I{latlon} is not ellipsoidal. @raise RangeError: If I{lat} outside the valid UTM bands or if I{lat} or I{lon} outside the valid range and I{rangerrrors} set to C{True}. @raise UTMError: Invlid I{zone}. @raise ValueError: If I{lon} value is missing or if I{latlon} is invalid. @note: Implements Karney’s method, using 8-th order Krüger series, giving results accurate to 5 nm (or better) for distances up to 3900 km from the central meridian. @example: >>> p = LatLon(48.8582, 2.2945) # 31 N 448251.8 5411932.7 >>> u = toUtm(p) # 31 N 448252 5411933 >>> p = LatLon(13.4125, 103.8667) # 48 N 377302.4 1483034.8 >>> u = toUtm(p) # 48 N 377302 1483035 ''' lat, lon, d, name = _to4lldn(latlon, lon, datum, name) z, B, lat, lon = _to3zBll(lat, lon, cmoff=cmoff) if zone: # re-zone for UTM r, _, _ = _to3zBhp(zone, band=B) if r != z: if not _UTM_ZONE_MIN <= r <= _UTM_ZONE_MAX: raise UTMError('%s invalid: %r' % ('zone', zone)) if cmoff: # re-offset from central meridian lon += _cmlon(z) - _cmlon(r) z = r E = d.ellipsoid a, b = radians(lat), radians(lon) # easting, northing: Karney 2011 Eq 7-14, 29, 35 sb, cb = sincos2(b) T = tan(a) T12 = hypot1(T) S = sinh(E.e * atanh(E.e * T / T12)) T_ = T * hypot1(S) - S * T12 H = hypot(T_, cb) y = atan2(T_, cb) # ξ' ksi x = asinh(sb / H) # η' eta A0 = _K0 * E.A Ks = _Kseries(E.AlphaKs, x, y) # Krüger series y = Ks.ys(y) * A0 # ξ x = Ks.xs(x) * A0 # η if cmoff: # Charles Karney, "Test data for the transverse Mercator projection (2009)" # <http://GeographicLib.SourceForge.io/html/transversemercator.html> # and <http://Zenodo.org/record/32470#.W4LEJS2ZON8> x += _FalseEasting # make x relative to false easting if y < 0: y += _FalseNorthing # y relative to false northing in S # convergence: Karney 2011 Eq 23, 24 p_ = Ks.ps(1) q_ = Ks.qs(0) c = degrees(atan(T_ / hypot1(T_) * tan(b)) + atan2(q_, p_)) # scale: Karney 2011 Eq 25 s = E.e2s(sin(a)) * T12 / H * (A0 / E.a * hypot(p_, q_)) h = _hemi(a) if Utm is None or not cmoff: r = z, h, x, y, B, d, c, s else: r = _xnamed(Utm(z, h, x, y, band=B, datum=d, convergence=c, scale=s), name) return r
def _sncndn3(self, phi): '''(INTERNAL) Helper for C{.fEinv} and C{._fXf}. ''' sn, cn = sincos2(phi) return sn, cn, self.fDelta(sn, cn)
def toLatLon(self, LatLon=None, eps=EPS, unfalse=True): '''Convert this UTM coordinate to an (ellipsoidal) geodetic point. @keyword LatLon: Optional, ellipsoidal (sub-)class to return the point (C{LatLon}) or C{None}. @keyword eps: Optional convergence limit, L{EPS} or above (C{float}). @keyword unfalse: Unfalse I{easting} and I{northing} if falsed (C{bool}). @return: This UTM coordinate as (I{LatLon}) or 5-tuple (lat, lon, datum, convergence, scale) if I{LatLon} is C{None}. @raise TypeError: If I{LatLon} is not ellipsoidal. @raise UTMError: Invalid meridional radius or H-value. @example: >>> u = Utm(31, 'N', 448251.795, 5411932.678) >>> from pygeodesy import ellipsoidalVincenty as eV >>> ll = u.toLatLon(eV.LatLon) # 48°51′29.52″N, 002°17′40.20″E ''' if eps < EPS: eps = EPS # less doesn't converge if self._latlon and self._latlon_eps == eps: return self._latlon5(LatLon) E = self._datum.ellipsoid # XXX vs LatLon.datum.ellipsoid x = self._easting y = self._northing if unfalse and self._falsed: x -= _FalseEasting # relative to central meridian if self._hemisphere == 'S': # relative to equator y -= _FalseNorthing # from Karney 2011 Eq 15-22, 36 A0 = _K0 * E.A if A0 < EPS: raise UTMError('%s invalid: %r' % ('meridional', E.A)) x /= A0 # η eta y /= A0 # ξ ksi Ks = _Kseries(E.BetaKs, x, y) # Krüger series y = -Ks.ys(-y) # ξ' x = -Ks.xs(-x) # η' shx = sinh(x) sy, cy = sincos2(y) H = hypot(shx, cy) if H < EPS: raise UTMError('%s invalid: %r' % ('H', H)) d = 1.0 + eps q = 1.0 / E.e12 T = t0 = sy / H # τʹ sd = Fsum(T) while abs(d) > eps: h = hypot1(T) s = sinh(E.e * atanh(E.e * T / h)) t = T * hypot1(s) - s * h d = (t0 - t) / hypot1(t) * ((q + T**2) / h) T, d = sd.fsum2_(d) # τi, (τi - τi-1) a = atan(T) # lat b = atan2(shx, cy) + radians(_cmlon(self._zone)) ll = _LLEB(degrees90(a), degrees180(b), datum=self._datum, name=self.name) # convergence: Karney 2011 Eq 26, 27 p = -Ks.ps(-1) q = Ks.qs(0) ll._convergence = degrees(atan(tan(y) * tanh(x)) + atan2(q, p)) # scale: Karney 2011 Eq 28 ll._scale = E.e2s(sin(a)) * hypot1(T) * H * (A0 / E.a / hypot(p, q)) self._latlon, self._latlon_eps = ll, eps return self._latlon5(LatLon)