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 (degrees90, degrees180, 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) ca, sa = cos(a), sin(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 toNvector(self, datum=Datums.WGS84): '''Convert this cartesian to an (ellipsoidal) n-vector. @keyword datum: Optional datum to use (L{Datum}). @return: The ellipsoidal n-vector (L{Nvector}). @raise ValueError: The I{Cartesian} at origin. @example: >>> from ellipsoidalNvector import LatLon >>> c = Cartesian(3980581, 97, 4966825) >>> n = c.toNvector() # (0.62282, 0.000002, 0.78237, +0.24) ''' if self._Nv is None or datum != self._Nv.datum: E = datum.ellipsoid x, y, z = self.to3xyz() # Kenneth Gade eqn 23 p = (x**2 + y**2) * E.a2_ q = (z**2 * E.e12) * E.a2_ r = fsum_(p, q, -E.e4) / 6 s = (p * q * E.e4) / (4 * r**3) t = cbrt(fsum_(1, s, sqrt(s * (2 + s)))) u = r * fsum_(1, t, 1 / t) v = sqrt(u**2 + E.e4 * q) w = E.e2 * fsum_(u, v, -q) / (2 * v) k = sqrt(fsum_(u, v, w**2)) - w if abs(k) < EPS: raise ValueError('%s: %r' % ('origin', self)) e = k / (k + E.e2) d = e * hypot(x, y) t = hypot(d, z) if t < EPS: raise ValueError('%s: %r' % ('origin', self)) h = fsum_(k, E.e2, -1) / k * t s = e / t self._Nv = Nvector(x * s, y * s, z / t, h=h, datum=datum, name=self.name) return self._Nv
def _r3(a, f): '''(INTERNAL) Reduced cos, sin, tan. ''' t = (1 - f) * tan(radians(a)) c = 1 / hypot(1, t) s = t * c return c, s, t
def to3lld(self, datum=None): '''Convert this L{Lcc} to a geodetic lat- and longitude. @keyword datum: Optional datum to use, otherwise use this I{Lcc}'s conic.datum (C{Datum}). @return: 3-Tuple (lat, lon, datum) in (C{degrees90}, {degrees180}, I{datum}). @raise TypeError: If I{datum} is not ellipsoidal. ''' c = self.conic if datum: c = c.toDatum(datum) e = self.easting - c._E0 n = c._r0 - self.northing + c._N0 r_ = copysign(hypot(e, n), c._n) t_ = pow(r_ / c._aF, c._n_) x = c._xdef(t_) # XXX c._lon0 while True: p, x = x, c._xdef(t_ * c._pdef(x)) if abs(x - p) < 1e-9: # XXX EPS too small? break # x, y == lon, lat a = degrees90(x) b = degrees180((atan(e / n) + c._opt3) * c._n_ + c._lon0) return a, b, c.datum
def rhumbDistanceTo(self, other, radius=R_M): '''Return the distance from this to an other point along a rhumb (loxodrome) line. @param other: The other point (spherical LatLon). @keyword radius: Optional mean radius of earth (scalar, default meter). @return: Distance (in the same units as radius). @raise TypeError: The other point is not spherical. @example: >>> p = LatLon(51.127, 1.338) >>> q = LatLon(50.964, 1.853) >>> d = p.rhumbDistanceTo(q) # 403100 ''' # see <http://www.edwilliams.org/avform.htm#Rhumb> da, db, dp = self._rhumb3(other) # on Mercator projection, longitude distances shrink # by latitude; the 'stretch factor' q becomes ill- # conditioned along E-W line (0/0); use an empirical # tolerance to avoid it if abs(dp) > EPS: q = da / dp else: a, _ = self.to2ab() q = cos(a) return float(radius) * hypot(da, q * db)
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 to2ll(self): '''Convert this vector to (geodetic) lat- and longitude. @return: 2-Tuple (lat, lon) in (degrees90, degrees180). @example: >>> v = Vector3d(0.500, 0.500, 0.707) >>> a, b = v.to2ll() # 45.0, 45.0 ''' a = atan2(self.z, hypot(self.x, self.y)) b = atan2(self.y, self.x) return degrees90(a), degrees180(b)
def to2ab(self): '''Convert this vector to (geodetic) lat- and longitude. @return: 2-Tuple (lat, lon) in (C{radians}, C{radians}). @example: >>> v = Vector3d(0.500, 0.500, 0.707) >>> a, b = v.to2ab() # 0.785323, 0.785398 ''' a = atan2(self.z, hypot(self.x, self.y)) b = atan2(self.y, self.x) return a, b
def to2ab(self): '''Convert this vector to (geodetic) lat- and longitude. @return: A L{PhiLam2Tuple}C{(phi, lambda)}. @example: >>> v = Vector3d(0.500, 0.500, 0.707) >>> a, b = v.to2ab() # 0.785323, 0.785398 ''' a = atan2(self.z, hypot(self.x, self.y)) b = atan2(self.y, self.x) return self._xnamed(PhiLam2Tuple(a, b))
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 toLatLon(self, LatLon=None, unfalse=True): '''Convert this UPS coordinate to an (ellipsoidal) geodetic point. @keyword LatLon: Optional, ellipsoidal (sub-)class to return the point (C{LatLon}) or C{None}. @keyword unfalse: Unfalse I{easting} and I{northing} if falsed (C{bool}). @return: This UPS coordinate as (I{LatLon}) or 5-tuple (C{lat, lon, datum, convergence, scale}) if I{LatLon} is C{None}. @raise TypeError: If I{LatLon} is not ellipsoidal. @raise UPSError: Invalid meridional radius or H-value. ''' if self._latlon: return self._latlon5(LatLon) E = self.datum.ellipsoid # XXX vs LatLon.datum.ellipsoid f = self.falsed if unfalse else 0 x = self.easting - f y = self.northing - f r = hypot(x, y) t = (r / (2 * self.scale0 * E.a / E.es_c)) if r > 0 else EPS**2 t = E.es_tauf((1 / t - t) * 0.5) if self.pole == 'N': a, b, c = atan(t), atan2(x, -y), 1 else: a, b, c = -atan(t), atan2(x, y), -1 a, b = degrees90(a), degrees180(b) if not self._band: self._band = _Band(a, b) if not self._hemisphere: self._hemisphere = _hemi(a) ll = _LLEB(a, b, datum=self._datum, name=self.name) ll._convergence = b * c # gamma ll._scale = _scale(E, r, t) if r > 0 else self.scale0 self._latlon = ll return self._latlon5(LatLon)
def equirectangular(lat1, lon1, lat2, lon2, radius=R_M, **options): '''Compute the distance between two points using the U{Equirectangular Approximation / Projection <http://www.movable-type.co.uk/scripts/latlong.html>}. See function L{equirectangular_} for more details, the available I{options} and errors raised. @param lat1: Start latitude (degrees). @param lon1: Start longitude (degrees). @param lat2: End latitude (degrees). @param lon2: End longitude (degrees). @keyword radius: Optional, mean earth radius (meter). @keyword options: Optional keyword arguments for function L{equirectangular_}. @return: Distance (meter, same units as I{radius}). @see: U{Local, Flat Earth<http://www.edwilliams.org/avform.htm#flat>}, method L{Ellipsoid.distance2} or function L{haversine} for more accurate and/or larger distances. ''' _, dy, dx, _ = equirectangular_(lat1, lon1, lat2, lon2, **options) return radians(hypot(dx, dy)) * radius
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 toUtm(latlon, lon=None, datum=None, Utm=Utm, name='', cmoff=True): '''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 use for the UTM coordinate (L{Utm}) or C{None}. @keyword name: Optional I{Utm} name (C{str}). @keyword cmoff: Offset longitude from zone's central meridian, apply false easting and false northing (C{bool}). @return: The UTM coordinate (L{Utm}) or a 6-tuple (zone, easting, northing, band, 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} is outside the valid UTM bands or if I{lat} or I{lon} outside the valid range and I{rangerrrors} set to C{True}. @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 ''' try: lat, lon = latlon.lat, latlon.lon if not isinstance(latlon, _LLEB): raise TypeError('%s not %s: %r' % ('latlon', 'ellipsoidal', latlon)) if not name: # use latlon.name name = _nameof(latlon) or name # PYCHOK no effect d = datum or latlon.datum except AttributeError: lat, lon = parseDMS2(latlon, lon) d = datum or Datums.WGS84 E = d.ellipsoid z, B, a, b = _toZBab4(lat, lon, cmoff) # easting, northing: Karney 2011 Eq 7-14, 29, 35 cb, sb, tb = cos(b), sin(b), tan(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: # C.F.F. 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_) * tb) + atan2(q_, p_)) # scale: Karney 2011 Eq 25 s = E.e2s(sin(a)) * T12 / H * (A0 / E.a * hypot(p_, q_)) if cmoff and Utm is not None: h = 'S' if a < 0 else 'N' # hemisphere return _xnamed( Utm(z, h, x, y, band=B, datum=d, convergence=c, scale=s), name) else: # zone, easting, northing, band, convergence and scale return z, x, y, B, c, s
def toLatLon(self, LatLon=None): '''Convert this UTM coordinate to an (ellipsoidal) geodetic point. @keyword LatLon: Optional, ellipsoidal (sub-)class to use for the point (C{LatLon}) or C{None}. @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 self._latlon: return self._latlon5(LatLon) E = self._datum.ellipsoid # XXX vs LatLon.datum.ellipsoid x = self._easting - _FalseEasting # relative to central meridian y = self._northing if self._hemi == '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) cy, sy = cos(y), sin(y) H = hypot(shx, cy) if H < EPS: raise UTMError('%s invalid: %r' % ('H', H)) T = t0 = sy / H # τʹ q = 1.0 / E.e12 d = 1 sd = Fsum(T) # toggles on +/-1.12e-16 eg. 31 N 400000 5000000 while abs(d) > EPS: # 1e-12 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 = sd.fsum_(d) # τi 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 = ll return self._latlon5(LatLon)
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 toUtm(latlon, lon=None, datum=None, Utm=Utm): '''Convert a lat-/longitude point to a UTM coordinate. @note: Implements Karney’s method, using 6-th order Krüger series, giving results accurate to 5 nm for distances up to 3900 km from the central meridian. @param latlon: Latitude (degrees) or an (ellipsoidal) geodetic I{LatLon} point. @keyword lon: Optional longitude (degrees or None). @keyword datum: Optional datum for this UTM coordinate, overriding latlon's datum (I{Datum}). @keyword Utm: Optional I{Utm} class to usefor the UTM coordinate (L{Utm}). @return: The UTM coordinate (L{Utm}). @raise TypeError: If I{latlon} is not ellipsoidal. @raise ValueError: If I{lon} value is missing, if I{latlon} is not scalar or I{latlon} is outside the valid UTM bands. @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 ''' try: lat, lon = latlon.lat, latlon.lon if not isinstance(latlon, _eLLb): raise TypeError('%s not %s: %r' % ('latlon', 'ellipsoidal', latlon)) d = datum or latlon.datum except AttributeError: lat, lon = parseDMS2(latlon, lon) d = datum or Datums.WGS84 E = d.ellipsoid z, B, a, b = _toZBll(lat, lon) h = 'S' if a < 0 else 'N' # hemisphere # easting, northing: Karney 2011 Eq 7-14, 29, 35 cb, sb, tb = cos(b), sin(b), tan(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 A6 = _K6s(E.Alpha6, x, y) # 6th-order Krüger series, 1-origin y = A6.ys(y) * A0 # ξ x = A6.xs(x) * A0 # η 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_ = A6.ps(1) q_ = A6.qs(0) c = degrees(atan(T_ / hypot1(T_) * tb) + atan2(q_, p_)) # scale: Karney 2011 Eq 25 s = E.e2s(sin(a)) * T12 / H * (A0 / E.a * hypot(p_, q_)) return Utm(z, h, x, y, band=B, datum=d, convergence=c, scale=s)
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): cll, sll, ll_ = cos(ll), sin(ll), ll ss = hypot(c2 * sll, c1s2 - s1c2 * cll) if ss < EPS: raise VincentyError('%r coincides with %r' % (self, other)) 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://en.wikipedia.org/wiki/Vincenty's_formulae> # elif abs(ll) > PI and self.isantipode(other, eps=self._epsilon): # raise VincentyError('%r antipodal to %r' % (self, other)) else: t = 'antipodal ' if self.isantipode(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 cll, sll = cos(ll), sin(ll) f = degrees360(atan2(c2 * sll, c1s2 - s1c2 * cll)) r = degrees360(atan2(c1 * sll, -s1c2 + c1s2 * cll)) d = d, f, r return d
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 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 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)