def toLatLon(self, LatLon, datum=None): '''Convert this WM coordinate to a geodetic point. @param LatLon: Ellipsoidal (sub-)class to use for the point (C{LatLon}). @keyword datum: Optional datum for ellipsoidal or C{None} for spherical I{LatLon} (C{Datum}). @return: Point of this WM coordinate (I{LatLon}). @raise TypeError: If I{LatLon} and I{datum} are not compatible or if I{datum} is not ellipsoidal. @raise ValueError: Invalid I{radius}. @example: >>> w = Wm(448251.795, 5411932.678) >>> from pygeodesy import sphericalTrigonometry as sT >>> ll = w.toLatLon(sT.LatLon) # 43°39′11.58″N, 004°01′36.17″E ''' if issubclass(LatLon, _ELLB): if datum: return _xnamed(LatLon(*self.to2ll(datum=datum), datum=datum), self.name) elif datum is None: return _xnamed(LatLon(*self.to2ll(datum=datum)), self.name) raise TypeError('%r and %s %r' % (LatLon, 'datum', datum))
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 use for the Lambert location (L{Lcc}). @keyword name: Optional I{Lcc} name (C{str}). @return: The Lambert location (L{Lcc}) or 3-tuple (easting, northing, height) if I{Lcc} is C{None}. @raise TypeError: If I{latlon} is not ellipsoidal. ''' if not isinstance(latlon, _ELLB): raise TypeError('%s not %s: %r' % ('latlon', 'ellipsoidal', latlon)) c = conic.toDatum(latlon.datum) lat, lon = latlon.to2ab() r = c._rdef(c._tdef(lat)) t = c._n * (lon - c._lon0) - c._opt3 e = c._E0 + r * sin(t) n = c._N0 + c._r0 - r * cos(t) h = latlon.height if height is None else height return (e, n, h) if Lcc is None else _xnamed(Lcc(e, n, h=h, conic=c), name or _nameof(latlon))
def toUtm(self, Utm=Utm): '''Convert this MGRS grid reference to a UTM coordinate. @keyword Utm: Optional (sub-)class to return the UTM coordinate (L{Utm}) or C{None}. @return: The UTM coordinate (L{Utm}) or 4-tuple (C{zone, hemisphere, easting, northing}) if I{Utm} is C{None}. @example: >>> m = Mgrs('31U', 'DQ', 448251, 11932) >>> u = m.toUtm() # 31 N 448251 5411932 ''' # get northing of the band bottom, extended to # include entirety of bottom-most 100 km square n = toUtm(self._bandLat, 0, datum=self._datum).northing nb = int(n / _100km) * _100km e, n = self._en100k2m() # 100 km grid square row letters repeat every 2,000 km north; # add enough 2,000 km blocks to get into required band e += self._easting n += self._northing while n < nb: n += _2000km h = _hemi(self.bandLatitude) # if self._band < 'N' return (self.zone, h, e, n) if Utm is None else _xnamed( Utm(self.zone, h, e, n, band=self.band, datum=self.datum), self.name)
def parseUTM5(strUTM, datum=Datums.WGS84, Utm=Utm, name=''): '''Parse a string representing a UTM coordinate, consisting of I{"zone[band] hemisphere easting northing"}. @param strUTM: A UTM coordinate (C{str}). @keyword datum: Optional datum to use (L{Datum}). @keyword Utm: Optional (sub-)class to return the UTM coordinate (L{Utm}) or C{None}. @keyword name: Optional I{Utm} name (C{str}). @return: The UTM coordinate (L{Utm}) or 5-tuple (C{zone, hemisphere, easting, northing, band}) if I{Utm} is C{None}. @raise UTMError: Invalid I{strUTM}. @example: >>> u = parseUTM('31 N 448251 5411932') >>> u.toStr2() # [Z:31, H:N, E:448251, N:5411932] >>> u = parseUTM('31 N 448251.8 5411932.7') >>> u.toStr() # 31 N 448252 5411933 ''' try: z, h, e, n, B = _parseUTMUPS(strUTM) if _UTM_ZONE_MIN > z or z > _UTM_ZONE_MAX \ or (B and B not in _Bands): raise ValueError except ValueError: raise UTMError('%s invalid: %r' % ('strUTM', strUTM)) return (z, h, e, n, B) if Utm is None else _xnamed( Utm(z, h, e, n, band=B, datum=datum), name)
def parseWM(strWM, radius=R_MA, Wm=Wm, name=''): '''Parse a string representing a WM coordinate, consisting of easting, northing and an optional radius. @param strWM: A WM coordinate (C{str}). @keyword radius: Optional earth radius (C{meter}). @keyword Wm: Optional (sub-)class to use (L{Wm}) or C{None}. @keyword name: Optional name (C{str}). @return: The WM coordinate (L{Wm}) or 3-tuple (easting, northing, radius) if I{Wm} is C{None}. @raise ValueError: Invalid I{strWM}. @example: >>> u = parseWM('448251 5411932') >>> u.toStr2() # [E:448251, N:5411932] ''' w = strWM.strip().replace(',', ' ').split() try: if len(w) == 2: w += [radius] elif len(w) != 3: raise ValueError # caught below x, y, r = map(float, w) except (TypeError, ValueError): raise ValueError('%s invalid: %r' % ('strWM', strWM)) return (x, y, r) if Wm is None else _xnamed(Wm(x, y, radius=r), name)
def parseUPS5(strUPS, datum=Datums.WGS84, Ups=Ups, falsed=True, name=''): '''Parse a string representing a UPS coordinate, consisting of I{"[zone][band] pole easting northing"} where I{zone} is pseudo zone I{"00"|"0"|""} and I{band} is I{'A'|'B'|'Y'|'Z'|''}. @param strUPS: A UPS coordinate (C{str}). @keyword datum: Optional datum to use (L{Datum}). @keyword Ups: Optional (sub-)class to return the UPS coordinate (L{Ups}) or C{None}. @keyword falsed: Both I{easting} and I{northing} are falsed (C{bool}). @keyword name: Optional I{Ups} name (C{str}). @return: The UPS coordinate (L{Ups}) or 5-tuple (C{zone, pole, easting, northing, band}) if I{Ups} is C{None}. @raise UPSError: Invalid I{strUPS}. ''' try: u = strUPS.lstrip() if not u.startswith(_UPS_ZONE_STR): raise ValueError z, p, e, n, B = _parseUTMUPS(u) if z != _UPS_ZONE or (B and B not in _Bands): raise ValueError except (AttributeError, TypeError, ValueError): raise UPSError('%s invalid: %r' % ('strUPS', strUPS)) return (z, p, e, n, B) if Ups is None else _xnamed(Ups( z, p, e, n, band=B, falsed=falsed, datum=datum), name)
def _toLLh(self, LL, height, **kwds): '''(INTERNAL) Helper for I{subclass.toLatLon}. ''' a, b = Vector3d.to2ll(self) h = self.h if height is None else height return (a, b, h) if LL is None else _xnamed(LL(a, b, height=h, **kwds), self.name)
def _latlon5(self, LatLon): '''(INTERNAL) Convert cached LatLon ''' ll = self._latlon if LatLon is None: return ll.lat, ll.lon, ll.datum, ll.convergence, ll.scale elif issubclass(LatLon, _LLEB): return _xnamed(_xattrs(LatLon(ll.lat, ll.lon, datum=ll.datum), ll, '_convergence', '_scale'), ll.name) raise TypeError('%s not ellipsoidal: %r' % ('LatLon', LatLon))
def parseMGRS(strMGRS, datum=Datums.WGS84, Mgrs=Mgrs, name=''): '''Parse a string representing a MGRS grid reference, consisting of zoneBand, grid, easting and northing. @param strMGRS: MGRS grid reference (C{str}). @keyword datum: Optional datum to use (L{Datum}). @keyword Mgrs: Optional (sub-)class to return the MGRS grid reference (L{Mgrs}) or C{None}. @keyword name: Optional I{Mgrs} name (C{str}). @return: The MGRS grid reference (L{Mgrs}) or 4-tuple (zone, ENdigraph, easting, northing) if I{Mgrs} is C{None}. @raise ValueError: Invalid I{strMGRS}. @example: >>> m = parseMGRS('31U DQ 48251 11932') >>> str(m) # 31U DQ 48251 11932 >>> m = parseMGRS('31UDQ4825111932') >>> repr(m) # [Z:31U, G:DQ, E:48251, N:11932] ''' def _mg(cre, s): # return re.match groups m = cre.match(s) if not m: raise ValueError return m.groups() def _s2m(g): # e or n string to meter f = float(g) if f > 0: x = int(log10(f)) if 0 <= x < 4: # at least 5 digits f *= (10000, 1000, 100, 10)[x] return f m = tuple(strMGRS.strip().replace(',', ' ').split()) try: if len(m) == 1: # 01ABC1234512345' m = _mg(_MGRSre, m[0]) m = m[:2] + halfs2(m[2]) elif len(m) == 2: # 01ABC 1234512345' m = _mg(_GZDre, m[0]) + halfs2(m[1]) elif len(m) == 3: # 01ABC 12345 12345' m = _mg(_GZDre, m[0]) + m[1:] if len(m) != 4: # 01A BC 1234 12345 raise ValueError e, n = map(_s2m, m[2:]) except ValueError: raise ValueError('%s invalid: %r' % ('strMGRS', strMGRS)) z, EN = m[0], m[1].upper() return (z, EN, e, n) if Mgrs is None else _xnamed( Mgrs(z, EN, e, n, datum=datum), name)
def _latlon3(self, LatLon, datum): '''(INTERNAL) Convert cached LatLon ''' ll = self._latlon if LatLon is None: if datum and datum != ll.datum: raise TypeError('no %s.convertDatum: %r' % (LatLon, ll)) return ll.lat, ll.lon, ll.datum elif issubclass(LatLon, _LLEB): ll = _xnamed(LatLon(ll.lat, ll.lon, datum=ll.datum), ll.name) return _ll2datum(ll, datum, 'LatLon') raise TypeError('%s not ellipsoidal: %r' % ('LatLon', LatLon))
def toLatLon(self, LatLon, **kwds): '''Return (the center of) this garef cell as an instance of the supplied C{LatLon} class. @param LatLon: Class to use (C{LatLon}). @keyword kwds: Optional keyword arguments for I{LatLon}. @return: This garef location (I{LatLon}). @raise ValueError: Invalid I{LatLon}. ''' if LatLon is None: raise ValueError('%s invalid: %r' % ('LatLon', LatLon)) return _xnamed(LatLon(*self.latlon, **kwds), self.name)
def toWm(latlon, lon=None, radius=R_MA, Wm=Wm, name=''): '''Convert a lat-/longitude point to a WM coordinate. @param latlon: Latitude (C{degrees}) or an (ellipsoidal or spherical) geodetic C{LatLon} point. @keyword lon: Optional longitude (C{degrees} or C{None}). @keyword radius: Optional earth radius (C{meter}). @keyword Wm: Optional (sub-)class for the WM coordinate (L{Wm}) or C{None}. @keyword name: Optional name (C{str}). @return: The WM coordinate (L{Wm}) or 3-tuple (easting, northing, radius) if I{Wm} is C{None}. @raise ValueError: If I{lon} value is missing, if I{latlon} is not scalar, if I{latlon} is beyond the valid WM range and L{rangerrors} is set to C{True} or if I{radius} is invalid. @example: >>> p = LatLon(48.8582, 2.2945) # 448251.8 5411932.7 >>> w = toWm(p) # 448252 5411933 >>> p = LatLon(13.4125, 103.8667) # 377302.4 1483034.8 >>> w = toWm(p) # 377302 1483035 ''' r, e = radius, None try: lat, lon = latlon.lat, latlon.lon if isinstance(latlon, _ELLB): r = latlon.datum.ellipsoid.a e = latlon.datum.ellipsoid.e if not name: # use latlon.name name = _nameof(latlon) or name # PYCHOK no effect lat = clipDMS(lat, _LatLimit) except AttributeError: lat, lon = parseDMS2(latlon, lon, clipLat=_LatLimit) s = sin(radians(lat)) y = atanh(s) # == log(tan((90 + lat) / 2)) == log(tanPI_2_2(radians(lat))) if e: y -= e * atanh(e * s) e, n = r * radians(lon), r * y return (e, n, r) if Wm is None else _xnamed(Wm(e, n, radius=r), name)
def toLatLon(self, LatLon, **kwds): '''Return (the approximate center of) this geohash cell as an instance of the supplied C{LatLon} class. @param LatLon: Class to use (C{LatLon}). @keyword kwds: Optional keyword arguments for I{LatLon}. @return: This geohash location (I{LatLon}). @example: >>> from sphericalTrigonometry import LatLon >>> ll = Geohash('u120fxw').toLatLon(LatLon) >>> print(repr(ll)) # LatLon(52°12′17.9″N, 000°07′07.64″E) >>> print(ll) # 52.204971°N, 000.11879°E ''' if LatLon is None: raise ValueError('%s invalid: %r' % ('LatLon', LatLon)) return _xnamed(LatLon(*self.latlon, **kwds), self.name)
def toCartesian(self, Cartesian=Cartesian): '''Convert this n-vector to a cartesian point. @keyword Cartesian: Optional, cartesion (sub-)class to use for the point (L{Cartesian}). @return: Cartesian equivalent to this n-vector (L{Cartesian}). @example: >>> v = Nvector(0.5, 0.5, 0.7071) >>> c = v.toCartesian() # [3194434, 3194434, 4487327] >>> p = c.toLatLon() # 45.0°N, 45.0°E ''' E = self.datum.ellipsoid x, y, z, h = self.to4xyzh() # Kenneth Gade eqn (22) n = E.b / hypot3(x * E.a_b, y * E.a_b, z) r = h + n * E.a_b**2 return _xnamed(Cartesian(x * r, y * r, z * (n + h)), self.name)
def parseUTM(strUTM, datum=Datums.WGS84, Utm=Utm, name=''): '''Parse a string representing a UTM coordinate, consisting of zone, hemisphere, easting and northing. @param strUTM: A UTM coordinate (C{str}). @keyword datum: Optional datum to use (L{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}). @return: The UTM coordinate (L{Utm}) or 4-tuple (zone, hemisphere, easting, northing) if I{Utm} is C{None}. @raise UTMError: Invalid I{strUTM}. @example: >>> u = parseUTM('31 N 448251 5411932') >>> u.toStr2() # [Z:31, H:N, E:448251, N:5411932] >>> u = parseUTM('31 N 448251.8 5411932.7') >>> u.toStr() # 31 N 448252 5411933 ''' u = strUTM.strip().replace(',', ' ').split() try: if len(u) != 4: raise ValueError # caught below z, h = u[:2] if z.isdigit(): z = int(z) else: z = z.upper() e, n = map(float, u[2:]) except ValueError: raise UTMError('%s invalid: %r' % ('strUTM', strUTM)) return (z, h, e, n) if Utm is None else _xnamed(Utm(z, h, e, n, datum=datum), name)
def toCss(latlon, cs0=_CassiniSoldner0, height=None, Css=Css, name=''): '''Convert an (ellipsoidal) geodetic point to a Cassini-Soldner location. @param latlon: Ellipsoidal point (C{LatLon}). @keyword cs0: Optional, the Cassini-Soldner projection to use (L{CassiniSoldner}). @keyword height: Optional height for the point, overriding the default height (C{meter}). @keyword Css: Optional (sub-)class to return the location (L{Css}) or C{None}. @keyword name: Optional I{Css} name (C{str}). @return: The Cassini-Soldner location (L{Css}) or 3-tuple (C{easting, northing, height}) if I{Css} is C{None}. @raise ImportError: Package U{GeographicLib<http://PyPI.org/ project/geographiclib>} missing. @raise TypeError: If I{latlon} is not ellipsoidal. ''' if not isinstance(latlon, _LLEB): raise TypeError('%s not %s: %r' % ('latlon', 'ellipsoidal', latlon)) cs = _CassiniSoldner(cs0) C, E = cs.datum.ellipsoid, latlon.datum.ellipsoid if C.a != E.a or C.f != E.f: raise ValueError('%s mistmatch %r vs %r' % ('ellipsoidal', C, E)) e, n, z, rk = cs.forward4(latlon.lat, latlon.lon) h = latlon.height if height is None else height if Css is None: r = e, n, h else: r = _xnamed(Css(e, n, h=h, cs0=cs), name or _nameof(latlon)) r._latlon = latlon.lat, latlon.lon r._azi, r._rk = z, rk return r
def toMgrs(utm, Mgrs=Mgrs, name=''): '''Convert a UTM coordinate to an MGRS grid reference. @param utm: A UTM coordinate (L{Utm}). @keyword Mgrs: Optional (sub-)class to return the MGRS grid reference (L{Mgrs}). @return: The MGRS grid reference (L{Mgrs}). @keyword name: Optional I{Mgrs} name (C{str}). @raise TypeError: If I{utm} is not L{Utm}. @raise ValueError: Invalid I{utm}. @example: >>> u = Utm(31, 'N', 448251, 5411932) >>> m = u.toMgrs() # 31U DQ 48251 11932 ''' if not isinstance(utm, Utm): raise TypeError('%s not Utm: %s' % ('utm', type(utm))) # truncate east-/northing to within 100 km grid square # XXX add rounding to nm precision? E, e = divmod(utm.easting, _100km) N, n = divmod(utm.northing, _100km) # columns in zone 1 are A-H, zone 2 J-R, zone 3 S-Z, then # repeating every 3rd zone (note -1 because eastings start # at 166e3 due to 500km false origin) z = utm.zone - 1 en = ( _Le100k[z % 3][int(E) - 1] + # rows in even zones are A-V, in odd zones are F-E _Ln100k[z % 2][int(N) % len(_Ln100k[0])]) return _xnamed(Mgrs(utm.zone, en, e, n, band=utm.band, datum=utm.datum), name or utm.name)
def toLatLon(self, LatLon=None, height=None): '''Convert this L{Css} to an (ellipsoidal) geodetic point. @keyword LatLon: Optional, ellipsoidal (sub-)class to return the geodetic point (C{LatLon}) or C{None}. @keyword height: Optional height for the point, overriding the default height (C{meter}). @return: The point (I{LatLon}) or 4-tuple (C{degrees90}, C{degrees180}, height, datum) if I{LatLon} is C{None}. @raise TypeError: If I{LatLon} or I{datum} is not ellipsoidal. ''' if LatLon and not issubclass(LatLon, _LLEB): raise TypeError('%s not %s: %r' % ('LatLon', 'ellipsoidal', LatLon)) a, b = self.latlon d = self.cs0.datum h = self.height if height is None else height return (a, b, h, d) if LatLon is None else _xnamed( LatLon(a, b, height=h, datum=d), self.name)
def toLatLon(self, LatLon=None, datum=None, height=None): '''Convert this L{Lcc} to an (ellipsoidal) geodetic point. @keyword LatLon: Optional, ellipsoidal (sub-)class to use for the geodetic point (C{LatLon}) or C{None}. @keyword datum: Optional datum to use, otherwise use this I{Lcc}'s conic.datum (C{Datum}). @keyword height: Optional height for the point, overriding the default height (C{meter}). @return: The point (I{LatLon}) or 4-tuple (C{degrees90}, C{degrees180}, height, datum) if I{LatLon} is C{None}. @raise TypeError: If I{LatLon} or I{datum} is not ellipsoidal. ''' if LatLon and not issubclass(LatLon, _ELLB): raise TypeError('%s not %s: %r' % ('LatLon', 'ellipsoidal', LatLon)) a, b, d = self.to3lld(datum=datum) h = self.height if height is None else height return (a, b, h, d) if LatLon is None else _xnamed( LatLon(a, b, height=h, datum=d), self.name)
def _toLLhd(self, LL, datum): '''(INTERNAL) Helper for I{subclass.toLatLon}. ''' a, b, h = self.to3llh(datum) return (a, b, h) if LL is None else _xnamed( LL(a, b, height=h, datum=datum), self.name)
def toUps8(latlon, lon=None, datum=None, Ups=Ups, pole='', falsed=True, strict=True, name=''): '''Convert a lat-/longitude point to a UPS coordinate. @param latlon: Latitude (C{degrees}) or an (ellipsoidal) geodetic C{LatLon} point. @keyword lon: Optional longitude (C{degrees}) or C{None} if I{latlon} is a C{LatLon}. @keyword datum: Optional datum for this UPS coordinate, overriding I{latlon}'s datum (C{Datum}). @keyword Ups: Optional (sub-)class to return the UPS coordinate (L{Ups}) or C{None}. @keyword pole: Optional top/center of (stereographic) projection (C{str}, C{'N[orth]'} or C{'S[outh]'}). @keyword falsed: False both easting and northing (C{bool}). @keyword strict: Restrict I{lat} to UPS ranges (C{bool}). @keyword name: Optional I{Ups} name (C{str}). @return: The UPS coordinate (L{Ups}) or a 8-tuple (zone, hemisphere, easting, northing, band, datum, convergence, scale) if I{Ups} is C{None}. @raise RangeError: If I{strict} and I{lat} outside the valid UPS bands or if I{lat} or I{lon} outside the valid range and I{rangerrrors} set to C{True}. @raise TypeError: If I{latlon} is not ellipsoidal. @raise ValueError: If I{lon} value is missing or if I{latlon} is invalid. @see: Karney's C++ class U{UPS <http://GeographicLib.SourceForge.io/html/classGeographicLib_1_1UPS.html>}. ''' lat, lon, d, name = _to4lldn(latlon, lon, datum, name) z, B, p, lat, lon = upsZoneBand5(lat, lon, strict=strict) p = str(pole or p)[:1] N = p in 'Nn' E = d.ellipsoid A = abs(lat - 90) < _TOL t = tan(radians(lat if N else -lat)) T = E.es_taupf(t) r = hypot1(T) + abs(T) if T >= 0: r = 0 if A else 1 / r k0 = getattr(Ups, '_scale0', _K0) # Ups is class or None r *= 2 * k0 * E.a / E.es_c k = k0 if A else _scale(E, r, t) c = lon # [-180, 180) from .upsZoneBand5 x, y = sincos2d(c) x *= r y *= r if N: y = -y else: c = -c if falsed: x += _Falsing y += _Falsing if Ups is None: r = z, p, x, y, B, d, c, k else: if z != _UPS_ZONE and not strict: z = _UPS_ZONE # ignore UTM zone r = _xnamed(Ups(z, p, x, y, band=B, datum=d, convergence=c, scale=k, falsed=falsed), name) if hasattr(Ups, '_hemisphere'): r._hemisphere = _hemi(lat) return r
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 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 parseOSGR(strOSGR, Osgr=Osgr, name=''): '''Parse an OSGR coordinate string to an Osgr instance. Accepts standard OS Grid References like 'SU 387 148', with or without whitespace separators, from 2- up to 10-digit references (1 m × 1 m square), or fully numeric, comma-separated references in metres, for example '438700,114800'. @param strOSGR: An OSGR coordinate (C{str}). @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 the 2-tuple (easting, northing) if I{Osgr} is C{None}. @raise ValueError: Invalid I{strOSGR}. @example: >>> g = parseOSGR('TG 51409 13177') >>> str(g) # TG 51409 13177 >>> g = parseOSGR('TG5140913177') >>> str(g) # TG 51409 13177 >>> g = parseOSGR('TG51409 13177') >>> str(g) # TG 51409 13177 >>> g = parseOSGR('651409,313177') >>> str(g) # TG 51409 13177 >>> g.toStr(prec=0) # 651409,313177 ''' def _c2i(G): g = ord(G.upper()) - ord('A') if g > 7: g -= 1 return g def _s2f(g): return float(g.strip()) def _s2i(G, g): g += '00000' # std to meter return int(str(G) + g[:5]) s = strOSGR.strip() try: g = s.split(',') if len(g) == 2: # "easting,northing" if len(s) < 13: raise ValueError # caught below e, n = map(_s2f, g) else: # "GR easting northing" g, s = s[:2], s[2:].strip() e, n = map(_c2i, g) n, m = divmod(n, 5) E = ((e - 2) % 5) * 5 + m N = 19 - (e // 5) * 5 - n if 0 > E or E > 6 or \ 0 > N or N > 12: raise ValueError # caught below g = s.split() if len(g) == 1: # no whitespace e, n = halfs2(s) elif len(g) == 2: e, n = g else: raise ValueError # caught below e = _s2i(E, e) n = _s2i(N, n) except ValueError: raise ValueError('%s invalid: %r' % ('strOSGR', strOSGR)) return (e, n) if Osgr is None else _xnamed(Osgr(e, n), name)
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