def equirectangular(lat1, lon1, lat2, lon2, radius=R_M, **options): '''Compute the distance between two points using the U{Equirectangular Approximation / Projection <https://www.Movable-Type.co.UK/scripts/latlong.html#equirectangular>}. @arg lat1: Start latitude (C{degrees}). @arg lon1: Start longitude (C{degrees}). @arg lat2: End latitude (C{degrees}). @arg lon2: End longitude (C{degrees}). @kwarg radius: Mean earth radius (C{meter}). @kwarg options: Optional keyword arguments for function L{equirectangular_}. @return: Distance (C{meter}, same units as B{C{radius}}). @see: Function L{equirectangular_} for more details, the available B{C{options}}, errors, restrictions and other, approximate or accurate distance functions. ''' _, dy, dx, _ = equirectangular_(Lat(lat1, name='lat1'), Lon(lon1, name='lon1'), Lat(lat2, name='lat2'), Lon(lon2, name='lon2'), **options) # PYCHOK Distance4Tuple return degrees2m(hypot(dx, dy), radius=radius)
def elevation2(lat, lon, timeout=2.0): '''Get the geoid elevation at an C{NAD83} to C{NAVD88} location. @arg lat: Latitude (C{degrees}). @arg lon: Longitude (C{degrees}). @kwarg timeout: Optional, query timeout (seconds). @return: An L{Elevation2Tuple}C{(elevation, data_source)} or (C{None, "error"}) in case of errors. @raise ValueError: Invalid B{C{timeout}}. @note: The returned C{elevation} is C{None} if B{C{lat}} or B{C{lon}} is invalid or outside the C{Conterminous US (CONUS)}, if conversion failed or if the query timed out. The C{error} is the C{HTTP-, IO-, SSL-, Type-, URL-} or C{ValueError} as a string (C{str}). @see: U{USGS National Map<https://NationalMap.gov/epqs>}, the U{FAQ<https://www.USGS.gov/faqs/what-are-projection- horizontal-and-vertical-datum-units-and-resolution-3dep-standard-dems>}, U{geoid.py<https://Gist.GitHub.com/pyRobShrk>}, module L{geoids}, classes L{GeoidG2012B}, L{GeoidKarney} and L{GeoidPGM}. ''' try: x = _qURL( 'https://NED.USGS.gov/epqs/pqs.php', # https://NationalMap.gov/epqs/pqs.php ( 'x=%.6F' % (Lat(lon), ), 'y=%.6F' % (Lat(lat), ), 'units=Meters', # Feet 'output=xml'), timeout=Scalar(timeout, name='timeout')) if x[:6] == '<?xml ': e = _xml('Elevation', x) try: e = float(e) if -1000000 < e < 1000000: return Elevation2Tuple(e, _xml('Data_Source', x)) e = 'non-CONUS %.2F' % (e, ) except ValueError: pass else: e = 'no XML "%s"' % (clips(x, limit=128, white=' '), ) except (HTTPError, IOError, TypeError, ValueError) as x: e = repr(x) return Elevation2Tuple(None, _error(elevation2, lat, lon, e))
def __init__(self, lat, lon, height=0, name=NN): '''New C{LatLon}. @arg lat: Latitude (C{degrees} or DMS C{str} with N or S suffix). @arg lon: Longitude (C{degrees} or DMS C{str} with E or W suffix). @kwarg height: Optional height (C{meter} above or below the earth surface). @kwarg name: Optional name (C{str}). @return: New instance (C{LatLon}). @raise RangeError: Value of B{C{lat}} or B{C{lon}} outside the valid range and C{rangerrors} set to C{True}. @raise UnitError: Invalid B{C{lat}}, B{C{lon}} or B{C{height}}. @example: >>> p = LatLon(50.06632, -5.71475) >>> q = LatLon('50°03′59″N', """005°42'53"W""") ''' self._lat = Lat(lat) # parseDMS2(lat, lon) self._lon = Lon(lon) # PYCHOK LatLon2Tuple if height: # elevation self._height = Height(height) if name: self.name = name
def latlon(self, latlonh): '''Set the lat- and longitude and optionally the height. @arg latlonh: New lat-, longitude and height (2- or 3-tuple of C{degrees} and C{meter}). @raise TypeError: Height of B{C{latlonh}} not C{scalar} or B{C{latlonh}} not C{list} or C{tuple}. @raise ValueError: Invalid B{C{latlonh}} or M{len(latlonh)}. @see: Function L{parse3llh} to parse a B{C{latlonh}} string into a 3-tuple (lat, lon, h). ''' _xinstanceof(list, tuple, latlonh=latlonh) if len(latlonh) == 3: h = Height(latlonh[2], name=Fmt.SQUARE(latlonh=2)) elif len(latlonh) != 2: raise _ValueError(latlonh=latlonh) else: h = self._height lat = Lat(latlonh[0]) # parseDMS2(latlonh[0], latlonh[1]) lon = Lon(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 latlon2(self, datum=None): '''Convert this WM coordinate to a lat- and longitude. @kwarg datum: Optional, ellipsoidal datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}) or C{None}. @return: A L{LatLon2Tuple}C{(lat, lon)}. @raise TypeError: Invalid or non-ellipsoidal B{C{datum}}. @see: Method C{toLatLon}. ''' r = self.radius x = self._x / r y = 2 * atan(exp(self._y / r)) - PI_2 if datum is not None: E = _ellipsoidal_datum(datum, name=self.name).ellipsoid if not E.isEllipsoidal: raise _IsnotError(_ellipsoidal_, datum=datum) # <https://Earth-Info.NGA.mil/GandG/wgs84/web_mercator/ # %28U%29%20NGA_SIG_0011_1.0.0_WEBMERC.pdf> y = y / r if E.e: y -= E.e * atanh(E.e * tanh(y)) # == E.es_atanh(tanh(y)) y *= E.a x *= E.a / r r = LatLon2Tuple(Lat(degrees90(y)), Lon(degrees180(x))) return self._xnamed(r)
def _to3zBll(lat, lon, cmoff=True): '''(INTERNAL) Return zone, Band and lat- and (central) longitude in degrees. @arg lat: Latitude (C{degrees}). @arg lon: Longitude (C{degrees}). @kwarg cmoff: Offset B{C{lon}} from zone's central meridian. @return: 4-Tuple (zone, Band, lat, lon). ''' z, lat, lon = _to3zll(lat, lon) # in .utmupsBase if _UTM_LAT_MIN > lat or lat >= _UTM_LAT_MAX: # [-80, 84) like Veness t = ' '.join((_outside_, _UTM_, _range_, '[%s,' % (_UTM_LAT_MIN,), '%s)' % (_UTM_LAT_MAX,))) raise RangeError(lat=degDMS(lat), txt=t) B = _Bands[int(lat + 80) >> 3] x = lon - _cmlon(z) # z before Norway/Svaldbard if abs(x) > _UTM_ZONE_OFF_MAX: t = ' '.join((_outside_, _UTM_, _zone_, str(z), 'by', degDMS(x, prec=6))) raise RangeError(lon=degDMS(lon), txt=t) if B == 'X': # and 0 <= int(lon) < 42: z = int(lon + 183) // 6 + 1 x = {32: 9, 34: 21, 36: 33}.get(z, None) if x: # Svalbard z += 1 if lon >= x else -1 elif B == 'V' and z == 31 and lon >= 3: z += 1 # SouthWestern Norway if cmoff: # lon off central meridian lon -= _cmlon(z) # z after Norway/Svaldbard return Zone(z), Band(B), Lat(lat), Lon(lon)
def lat(self, lat): '''Set the latitude. @arg lat: New latitude (C{str[N|S]} or C{degrees}). @raise ValueError: Invalid B{C{lat}}. ''' lat = Lat(lat) # parseDMS(lat, suffix=_NS_, clip=90) self._update(lat != self._lat) self._lat = lat
def decode3(garef, center=True): '''Decode a C{garef} to lat-, longitude and precision. @arg garef: To be decoded (L{Garef} or C{str}). @kwarg center: If C{True} the center, otherwise the south-west, lower-left corner (C{bool}). @return: A L{LatLonPrec3Tuple}C{(lat, lon, precision)}. @raise GARSError: Invalid B{C{garef}}, INValid, non-alphanumeric or bad length B{C{garef}}. ''' def _Error(i): return GARSError(garef=_item_sq(repr(garef), i)) def _ll(chars, g, i, j, lo, hi): ll, b = 0, len(chars) for i in range(i, j): d = chars.find(g[i]) if d < 0: raise _Error(i) ll = ll * b + d if ll < lo or ll > hi: raise _Error(j) return ll def _ll2(lon, lat, g, i, m): d = _Digits.find(g[i]) if d < 1 or d > m * m: raise _Error(i) d, r = divmod(d - 1, m) lon = lon * m + r lat = lat * m + (m - 1 - d) return lon, lat g, precision = _2garstr2(garef) lon = _ll(_Digits, g, 0, _LonLen, 1, 720) + _LonOrig_M1_1 lat = _ll(_Letters, g, _LonLen, _MinLen, 0, 359) + _LatOrig_M1 if precision > 0: lon, lat = _ll2(lon, lat, g, _MinLen, _M2) if precision > 1: lon, lat = _ll2(lon, lat, g, _MinLen + 1, _M3) if center: # ll = (ll * 2 + 1) / 2 lon += 0.5 lat += 0.5 r = _Resolutions[precision] # == 1.0 / unit r = LatLonPrec3Tuple(Lat(lat * r, Error=GARSError), Lon(lon * r, Error=GARSError), precision) return _xnamed(r, nameof(garef))
def decode3(georef, center=True): '''Decode a C{georef} to lat-, longitude and precision. @arg georef: To be decoded (L{Georef} or C{str}). @kwarg center: If C{True} the center, otherwise the south-west, lower-left corner (C{bool}). @return: A L{LatLonPrec3Tuple}C{(lat, lon, precision)}. @raise WGRSError: Invalid B{C{georef}}, INValid, non-alphanumeric or odd length B{C{georef}}. ''' def _digit(ll, g, i, m): d = _Digits.find(g[i]) if d < 0 or d >= m: raise _Error(i) return ll * m + d def _Error(i): return WGRSError(Fmt.SQUARE(georef=i), georef) def _index(chars, g, i): k = chars.find(g[i]) if k < 0: raise _Error(i) return k g, precision = _2geostr2(georef) lon = _index(_LonTile, g, 0) + _LonOrig_Tile lat = _index(_LatTile, g, 1) + _LatOrig_Tile u = _1_0 if precision > 0: lon = lon * _Tile + _index(_DegChar, g, 2) lat = lat * _Tile + _index(_DegChar, g, 3) m, p = 6, precision - 1 for i in range(_BaseLen, _BaseLen + p): lon = _digit(lon, g, i, m) lat = _digit(lat, g, i + p, m) u *= m m = _Base u *= _Tile if center: lon = lon * _2_0 + _1_0 lat = lat * _2_0 + _1_0 u *= _2_0 u = _Tile / u r = LatLonPrec3Tuple(Lat(lat * u, Error=WGRSError), Lon(lon * u, Error=WGRSError), precision) return _xnamed(r, nameof(georef))
def reset(self, lat0, lon0): '''Set or reset the center point of this Cassini-Soldner projection. @arg lat0: Center point latitude (C{degrees90}). @arg lon0: Center point longitude (C{degrees180}). ''' g, M = self.datum.ellipsoid._geodesic_Math2 self._meridian = m = g.Line(Lat(lat0, name=_lat0_), Lon(lon0, name=_lon0_), 0.0, g.STANDARD | g.DISTANCE_IN) self._latlon0 = LatLon2Tuple(m.lat1, m.lon1) s, c = M.sincosd(m.lat1) # == self.lat0 == self.LatitudeOrigin() self._sb0, self._cb0 = M.norm(s * (1.0 - g.f), c)
def geoidHeight2(lat, lon, model=0, timeout=2.0): '''Get the C{NAVD88} geoid height at an C{NAD83} location. @arg lat: Latitude (C{degrees}). @arg lon: Longitude (C{degrees}). @kwarg model: Optional, geoid model ID (C{int}). @kwarg timeout: Optional, query timeout (seconds). @return: An L{GeoidHeight2Tuple}C{(height, model_name)} or C{(None, "error"}) in case of errors. @raise ValueError: Invalid B{C{timeout}}. @note: The returned C{height} is C{None} if B{C{lat}} or B{C{lon}} is invalid or outside the C{Conterminous US (CONUS)}, if the B{C{model}} was invalid, if conversion failed or if the query timed out. The C{error} is the C{HTTP-, IO-, SSL-, Type-, URL-} or C{ValueError} as a string (C{str}). @see: U{NOAA National Geodetic Survey <https://www.NGS.NOAA.gov/INFO/geodesy.shtml>}, U{Geoid<https://www.NGS.NOAA.gov/web_services/geoid.shtml>}, U{USGS10mElev.py<https://Gist.GitHub.com/pyRobShrk>}, module L{geoids}, classes L{GeoidG2012B}, L{GeoidKarney} and L{GeoidPGM}. ''' try: j = _qURL('https://Geodesy.NOAA.gov/api/geoid/ght', ('lat=%.6F' % (Lat(lat), ), 'lon=%.6F' % (Lon(lon), ), 'model=%s' % (model, ) if model else ''), timeout=Scalar(timeout, name='timeout')) # PYCHOK 5 if j[:1] == '{' and j[-1:] == '}' and j.find('"error":') > 0: d = _json(j) if isinstance(d.get('error', 'N/A'), float): h = d.get('geoidHeight', None) if h is not None: m = _str(d.get('geoidModel', 'N/A')) return GeoidHeight2Tuple(h, m) e = 'geoidHeight' else: e = 'JSON' e = 'no %s "%s"' % (e, clips(j, limit=256, white=' ')) except (HTTPError, IOError, TypeError, ValueError) as x: e = repr(x) e = _error(geoidHeight2, lat, lon, e) return GeoidHeight2Tuple(None, e)
def rescale0(self, lat, scale0=_K0): '''Set the central scale factor for this UPS projection. @arg lat: Northern latitude (C{degrees}). @arg scale0: UPS k0 scale at B{C{lat}} latitude (C{scalar}). @raise RangeError: If B{C{lat}} outside the valid range and L{rangerrors} set to C{True}. @raise UPSError: Invalid B{C{scale}}. ''' s0 = Scalar_(scale0, Error=UPSError, name='scale0', low=EPS) # <= 1.003 or 1.0016? u = toUps8(abs(Lat(lat)), 0, datum=self.datum, Ups=_UpsK1) k = s0 / u.scale if self.scale0 != k: self._band = NN # force re-compute self._latlon = self._epsg = self._mgrs = self._utm = None self._scale0 = Scalar(k)
from pygeodesy.lazily import _ALL_LAZY from pygeodesy.named import _NamedBase, _NamedTuple, nameof, _xnamed from pygeodesy.namedTuples import LatLon2Tuple, PhiLam2Tuple from pygeodesy.streprs import Fmt, strs, _xzipairs from pygeodesy.units import Easting, Lam_, Lat, Lon, Northing, Phi_, \ Radius, Radius_ from pygeodesy.utily import degrees90, degrees180 from math import atan, atanh, exp, radians, sin, tanh __all__ = _ALL_LAZY.webmercator __version__ = '20.11.04' # _FalseEasting = 0 # false Easting (C{meter}) # _FalseNorthing = 0 # false Northing (C{meter}) _LatLimit = Lat(limit=85.051129) # latitudinal limit (C{degrees}) # _LonOrigin = 0 # longitude of natural origin (C{degrees}) class EasNorRadius3Tuple(_NamedTuple): '''3-Tuple C{(easting, northing, radius)}, all in C{meter}. ''' _Names_ = (_easting_, _northing_, _radius_) _Units_ = (Easting, Northing, Radius) class WebMercatorError(_ValueError): '''Web Mercator (WM) parser or L{Wm} issue. ''' pass
def _2fllh(lat, lon, height=None): '''(INTERNAL) Convert lat, lon, height. ''' # lat, lon = parseDMS2(lat, lon) return (Lat(lat, Error=WGRSError), Lon(lon, Error=WGRSError), height)
def _2fll(lat, lon, *unused): '''(INTERNAL) Convert lat, lon to 2-tuple of floats. ''' # lat, lon = parseDMS2(lat, lon) return (Lat(lat, Error=GeohashError), Lon(lon, Error=GeohashError))
def __new__(cls, z, B, h, lat, lon, Error=None): if Error is not None: lat = Lat(lat, Error=Error) lon = Lon(lon, Error=Error) return _NamedTuple.__new__(cls, z, B, h, lat, lon)
def __new__(cls, lat, lon, d, c, s): return _NamedTuple.__new__(cls, Lat(lat), Lon(lon), d, Scalar(c, name=_convergence_), Scalar(s, name=_scale_))
def _2fll(lat, lon, *unused): '''(INTERNAL) Convert lat, lon. ''' # lat, lon = parseDMS2(lat, lon) return (Lat(lat, Error=GARSError), Lon(lon, Error=GARSError))
def __new__(cls, lat, lon, azi, rk): return _NamedTuple.__new__(cls, Lat(lat, Error=CSSError), Lon(lon, Error=CSSError), Bearing(azi, Error=CSSError), Scalar(rk, Error=CSSError))