def latlon(self, latlonh): '''Set the lat- and longitude and optionally the height. @param latlonh: New lat-, longitude and height (2- or 3-tuple of C{degrees} and C{meter}). @raise TypeError: Height of I{latlonh} not C{scalar} or I{latlonh} not C{list} or C{tuple}. @raise ValueError: Invalid I{latlonh} or M{len(latlonh)}. @see: Function L{parse3llh} to parse a I{latlonh} string into a 3-tuple (lat, lon, h). ''' if not isinstance(latlonh, (list, tuple)): raise TypeError('%s invalid: %r' % ('latlonh', latlonh)) if len(latlonh) == 3: h = scalar(latlonh[2], None, name='latlonh') elif len(latlonh) != 2: raise ValueError('%s invalid: %r' % ('latlonh', latlonh)) else: h = self._height lat, lon = parseDMS2(latlonh[0], latlonh[1]) self._update(lat != self._lat or lon != self._lon or h != self._height) self._lat, self._lon, self._height = lat, lon, h
def upsZoneBand5(lat, lon, strict=True): '''Return the UTM/UPS zone number, (polar) Band letter, pole and clipped lat- and longitude for a given location. @param lat: Latitude in degrees (C{scalar} or C{str}). @param lon: Longitude in degrees (C{scalar} or C{str}). @keyword strict: Restrict B{C{lat}} to UPS ranges (C{bool}). @return: A L{UtmUpsLatLon5Tuple}C{(zone, band, hemipole, lat, lon)} where C{hemipole} is the C{'N'|'S'} pole, the UPS projection top/center. @raise RangeError: If B{C{strict}} and B{C{lat}} in the UTM and not the UPS range or if B{C{lat}} or B{C{lon}} outside the valid range and L{rangerrors} set to C{True}. @raise ValueError: Invalid B{C{lat}} or B{C{lon}}. ''' z, lat, lon = _to3zll(*parseDMS2(lat, lon)) if lat < _UPS_LAT_MIN: # includes 30' overlap z, B, p = _UPS_ZONE, _Band(lat, lon), 'S' elif lat > _UPS_LAT_MAX: # includes 30' overlap z, B, p = _UPS_ZONE, _Band(lat, lon), 'N' elif strict: x = '%s [%s, %s]' % ('range', _UPS_LAT_MIN, _UPS_LAT_MAX) raise RangeError('%s inside UTM %s: %s' % ('lat', x, degDMS(lat))) else: B, p = '', _hemi(lat) return UtmUpsLatLon5Tuple(z, B, p, lat, lon)
def __init__(self, lat, lon, height=0, name=''): '''New C{LatLon}. @param lat: Latitude (C{degrees} or DMS C{str} with N or S suffix). @param lon: Longitude (C{degrees} or DMS C{str} with E or W suffix). @keyword height: Optional height (C{meter} above or below the earth surface). @keyword name: Optional name (C{str}). @return: New instance (C{LatLon}). @raise RangeError: Value of I{lat} or I{lon} outside the valid range and I{rangerrrors} set to C{True}. @raise ValueError: Invalid I{lat} or I{lon}. @example: >>> p = LatLon(50.06632, -5.71475) >>> q = LatLon('50°03′59″N', """005°42'53"W""") ''' self._lat, self._lon = parseDMS2(lat, lon) if height: # elevation self._height = scalar(height, None, name='height') if name: self.name = name
def upsZoneBand5(lat, lon, strict=True): '''Return the UTM/UPS zone number, (polar) Band letter, pole and clipped lat- and longitude for a given location. @param lat: Latitude in degrees (C{scalar} or C{str}). @param lon: Longitude in degrees (C{scalar} or C{str}). @keyword strict: Restrict I{lat} to UPS ranges (C{bool}). @return: 5-Tuple (C{zone, Band, hemisphere, lat, lon}) as (C{int, str, 'N'|'S', degrees90, degrees180}) where C{zone} is always C{0} for UPS and polar C{Band} is C{""} or C{'A'|'B'|'Y'|'Z'}. @raise RangeError: If I{strict} and I{lat} in UTM and not UPS range or if I{lat} or I{lon} outside the valid range and I{rangerrrors} set to C{True}. @raise ValueError: Invalid I{lat} or I{lon}. ''' z, lat, lon = _to3zll(*parseDMS2(lat, lon)) if lat < _UPS_LAT_MIN: # includes 30' overlap return _UPS_ZONE, _Band(lat, lon), 'S', lat, lon elif lat > _UPS_LAT_MAX: # includes 30' overlap return _UPS_ZONE, _Band(lat, lon), 'N', lat, lon elif strict: x = '%s [%s, %s]' % ('range', _UPS_LAT_MIN, _UPS_LAT_MAX) raise RangeError('%s inside UTM %s: %s' % ('lat', x, degDMS(lat))) return z, '', _hemi(lat), lat, lon
def toOsgr(latlon, lon=None, datum=Datums.WGS84, Osgr=Osgr): '''Convert a lat-/longitude point to an OSGR coordinate. @param latlon: Latitude (degrees) or an (ellipsoidal) geodetic I{LatLon} point. @keyword lon: Optional longitude in degrees (scalar or None). @keyword datum: Optional datum to convert (I{Datum}). @keyword Osgr: Optional Osgr class to use for the OSGR coordinate (L{Osgr}). @return: The OSGR coordinate (L{Osgr}). @raise TypeError: If I{latlon} is not ellipsoidal or if 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, _eLLb): # XXX fix failing _eLLb.convertDatum() latlon = _eLLb(*parseDMS2(latlon, lon), datum=datum) elif lon is not None: raise ValueError('%s not %s: %r' % ('lon', None, lon)) E = _OSGB36.ellipsoid ll = _ll2datum(latlon, _OSGB36, 'latlon') a, b = map1(radians, ll.lat, ll.lon) ca, sa, ta = cos(a), sin(a), tan(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 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 Osgr(e, n)
def _to4lldn(latlon, lon, datum, name): '''(INTERNAL) Return 4-tuple (C{lat, lon, datum, name}). ''' try: # if lon is not None: # raise AttributeError lat, lon = latlon.lat, latlon.lon if not isinstance(latlon, _LLEB): 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 return lat, lon, d, (name or nameof(latlon))
def utmZoneBand2(lat, lon): '''Return the UTM zone number and UTM Band letter for a location. @param lat: Latitude (C{degrees}) or string. @param lon: Longitude (C{degrees}) or string. @return: 2-Tuple (zone, Band) as (int, string). @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: Invalid I{lat} or I{lon}. ''' return _toZBab4(*parseDMS2(lat, lon))[:2]
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 to return the WM coordinate (L{Wm}) or C{None}. @keyword name: Optional name (C{str}). @return: The WM coordinate (B{C{Wm}}) or an L{EasNorRadius3Tuple}C{(easting, northing, radius)} if B{C{Wm}} is C{None}. @raise ValueError: If B{C{lon}} value is missing, if B{C{latlon}} is not scalar, if B{C{latlon}} is beyond the valid WM range and L{rangerrors} is set to C{True} or if B{C{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, _LLEB): r = latlon.datum.ellipsoid.a e = latlon.datum.ellipsoid.e if not name: # use latlon.name name = nameof(latlon) 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 r = EasNorRadius3Tuple(e, n, r) if Wm is None else \ Wm(e, n, radius=r) return _xnamed(r, name)
def toWm(latlon, lon=None, radius=R_MA, Wm=Wm): '''Convert a lat-/longitude point to a WM coordinate. @param latlon: Latitude (degrees) or an (ellipsoidal or spherical) geodetic I{LatLon} point. @keyword lon: Optional longitude (degrees or None). @keyword radius: Optional earth radius (meter). @keyword Wm: Optional Wm class for the WM coordinate (L{Wm}). @return: The WM coordinate (L{Wm}). @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 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 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) return Wm(r * radians(lon), r * y, radius=r)
def utmZoneBand5(lat, lon, cmoff=False): '''Return the UTM zone number, Band letter, hemisphere and (clipped) lat- and longitude for a given location. @param lat: Latitude in degrees (C{scalar} or C{str}). @param lon: Longitude in degrees (C{scalar} or C{str}). @keyword cmoff: Offset longitude from the zone's central meridian (C{bool}). @return: A L{UtmUpsLatLon5Tuple}C{(zone, band, hemipole, lat, lon)} where C{hemipole} is the C{'N'|'S'} UTM hemisphere. @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 ValueError: Invalid B{C{lat}} or B{C{lon}}. ''' lat, lon = parseDMS2(lat, lon) z, B, lat, lon = _to3zBll(lat, lon, cmoff=cmoff) return UtmUpsLatLon5Tuple(z, B, _hemi(lat), lat, lon)
def utmZoneBand5(lat, lon, cmoff=False): '''Return the UTM zone number, Band letter, hemisphere and clipped lat- and longitude for a given location. @param lat: Latitude in degrees (C{scalar} or C{str}). @param lon: Longitude in degrees (C{scalar} or C{str}). @keyword cmoff: Offset longitude from zone's central meridian (C{bool}). @return: 5-Tuple (C{zone, Band, hemisphere, lat, lon}) as (C{int, str, 'N'|'S', degrees90, degrees180}) where C{zone} is C{1..60} and C{Band} is C{'C'|'D'..'W'|'X'} for UTM. @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 ValueError: Invalid I{lat} or I{lon}. ''' lat, lon = parseDMS2(lat, lon) z, B, lat, lon = _to3zBll(lat, lon, cmoff=cmoff) return z, B, _hemi(lat), lat, lon
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 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 _2fll(lat, lon, *unused): '''(INTERNAL) Convert lat, lon to 2-tuple of floats. ''' return parseDMS2(lat, lon)
def _2fll(lat, lon, *unused): '''(INTERNAL) Convert lat, lon. ''' return parseDMS2(lat, lon)
def _2fllh(lat, lon, height=None): '''(INTERNAL) Convert lat, lon, height. ''' return parseDMS2(lat, lon) + (height,)
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)