def _datum_setter(self, datum): '''(INTERNAL) Set the datum. ''' d = datum or getattr(self._model[0], _datum_, datum) if d and d != self.datum: # PYCHOK no cover _xinstanceof(Datum, datum=d) self._datum = d
def flatLocal_(phi2, phi1, lam21, datum=Datums.WGS84): '''Compute the I{angular} distance between two (ellipsoidal) points using the U{ellipsoidal Earth to plane projection<https://WikiPedia.org/ wiki/Geographical_distance#Ellipsoidal_Earth_projected_to_a_plane>} aka U{Hubeny<https://www.OVG.AT/de/vgi/files/pdf/3781/>} formula. @arg phi2: End latitude (C{radians}). @arg phi1: Start latitude (C{radians}). @arg lam21: Longitudinal delta, M{end-start} (C{radians}). @kwarg datum: Ellipsoidal datum to use (L{Datum}). @return: Angular distance (C{radians}). @raise TypeError: Invalid B{C{datum}}. @note: The meridional and prime_vertical radii of curvature are taken and scaled I{at the mean latitude}. @see: Functions L{flatLocal}/L{hubeny}, L{cosineAndoyerLambert_}, L{cosineForsytheAndoyerLambert_}, L{cosineLaw_}, L{flatPolar_}, L{equirectangular_}, L{euclidean_}, L{haversine_}, L{thomas_} and L{vincentys_} and U{local, flat earth approximation <https://www.EdWilliams.org/avform.htm#flat>}. ''' _xinstanceof(Datum, datum=datum) m, n = datum.ellipsoid.roc2_((phi2 + phi1) * 0.5, scaled=True) return hypot(m * (phi2 - phi1), n * lam21)
def __init__(self, easting, northing, band='', datum=None, falsed=True, convergence=None, scale=None): '''(INTERNAL) New L{UtmUpsBase}. ''' E = self._Error if not E: notOverloaded(self, '_Error') self._easting = Easting(easting, Error=E) self._northing = Northing(northing, Error=E) if band: _xinstanceof(str, band=band) self._band = band if datum: _xinstanceof(Datum, datum=datum) if datum != self._datum: self._datum = datum if not falsed: self._falsed = False if convergence is not self._convergence: self._convergence = Scalar(convergence, name='convergence', Error=E) if scale is not self._scale: self._scale = Scalar(scale, name='scale', Error=E)
def convertRefFrame(self, reframe2, reframe, epoch=None): '''Convert this cartesian point from one to an other reference frame. @arg reframe2: Reference frame to convert I{to} (L{RefFrame}). @arg reframe: Reference frame to convert I{from} (L{RefFrame}). @kwarg epoch: Optional epoch to observe for B{C{reframe}}, a fractional calendar year (C{scalar}). @return: The converted point (C{Cartesian}) or this point if conversion is C{nil}. @raise TRFError: No conversion available from B{C{reframe}} to B{C{reframe2}}. @raise TypeError: B{C{reframe2}} or B{C{reframe}} not a L{RefFrame} or B{C{epoch}} not C{scalar}. ''' _xinstanceof(RefFrame, reframe2=reframe2, reframe=reframe) c, d = self, self.datum for t in _reframeTransforms( reframe2, reframe, reframe.epoch if epoch is None else _2epoch(epoch)): c = c._applyHelmert(t, False, datum=d) return c
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 flatLocal_(phi2, phi1, lam21, datum=Datums.WGS84): '''Compute the distance between two (ellipsoidal) points using the U{ellipsoidal Earth to plane projection <https://WikiPedia.org/wiki/Geographical_distance#Ellipsoidal_Earth_projected_to_a_plane>} fromula. @arg phi2: End latitude (C{radians}). @arg phi1: Start latitude (C{radians}). @arg lam21: Longitudinal delta, M{end-start} (C{radians}). @kwarg datum: Optional, (ellipsoidal) datum to use (L{Datum}). @return: Distance (C{meter}, same units as the B{C{datum}}'s ellipsoid axes). @raise TypeError: Invalid B{C{datum}}. @note: The meridional and prime_vertical radii of curvature are taken and scaled at the mean latitude. @see: Functions L{flatLocal}, L{cosineLaw_}, L{flatPolar_}, L{equirectangular_}, L{euclidean_}, L{haversine_} and L{vincentys_} and U{local, flat earth approximation <https://www.edwilliams.org/avform.htm#flat>}. ''' _xinstanceof(Datum, datum=datum) m, n = datum.ellipsoid.roc2_((phi2 + phi1) * 0.5, scaled=True) return hypot(m * (phi2 - phi1), n * lam21)
def destinationNed(self, delta): '''Calculate the destination point using the supplied NED delta from this point. @arg delta: Delta from this to the other point in the local tangent plane (LTP) of this point (L{Ned}). @return: Destination point (L{Cartesian}). @raise TypeError: If B{C{delta}} is not L{Ned}. @example: >>> a = LatLon(49.66618, 3.45063) >>> delta = toNed(116807.681, 222.493, -0.5245) # [N:-86126, E:-78900, D:1069] >>> b = a.destinationNed(delta) # 48.88667°N, 002.37472°E @JSname: I{destinationPoint}. ''' _xinstanceof(Ned, delta=delta) n, e, d = self._rotation3() # convert NED delta to standard coordinate frame of n-vector dn = delta.ned # rotate dn to get delta in cartesian (ECEF) coordinate # reference frame using the rotation matrix column vectors dc = Cartesian(fdot(dn, n.x, e.x, d.x), fdot(dn, n.y, e.y, d.y), fdot(dn, n.z, e.z, d.z)) # apply (cartesian) delta to this Cartesian to # obtain destination point as cartesian v = self.toCartesian().plus(dc) # the plus() gives a plain vector return v.toLatLon(datum=self.datum, LatLon=self.classof) # Cartesian(v.x, v.y, v.z).toLatLon(...)
def __init__(self, easting, northing, band=NN, datum=None, falsed=True, convergence=None, scale=None): '''(INTERNAL) New L{UtmUpsBase}. ''' E = self._Error if not E: notOverloaded(self, '_Error') self._easting = Easting(easting, Error=E) self._northing = Northing(northing, Error=E) if band: _xinstanceof(str, band=band) self._band = band if datum not in (None, self._datum): self._datum = _ellipsoidal_datum(datum) # XXX name=band if not falsed: self._falsed = False if convergence is not self._convergence: self._convergence = Scalar(convergence, name=_convergence_, Error=E) if scale is not self._scale: self._scale = Scalar(scale, name=_scale_, Error=E)
def convertDatum(self, datum2, datum=None): '''Convert this cartesian from one datum to an other. @arg datum2: Datum to convert I{to} (L{Datum}). @kwarg datum: Datum to convert I{from} (L{Datum}). @return: The converted point (C{Cartesian}). @raise TypeError: B{C{datum2}} or B{C{datum}} invalid. ''' _xinstanceof(Datum, datum2=datum2) if datum not in (None, self.datum): c = self.convertDatum(datum) else: c = self i, d = False, c.datum if d == datum2: return c.copy() if c is self else c elif d == Datums.WGS84: d = datum2 # convert from WGS84 to datum2 elif datum2 == Datums.WGS84: i = True # convert to WGS84 by inverse transform else: # neither datum2 nor c.datum is WGS84, invert to WGS84 first c = c._applyHelmert(d.transform, True, datum=Datums.WGS84) d = datum2 return c._applyHelmert(d.transform, i, datum=datum2)
def latlon2(self, datum=None): '''Convert this WM coordinate to a lat- and longitude. @kwarg datum: Optional ellipsoidal datum (C{Datum}). @return: A L{LatLon2Tuple}C{(lat, lon)}. @raise TypeError: 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: _xinstanceof(Datum, datum=datum) E = datum.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 toLatLon(self, datum=None, LatLon=None, **LatLon_kwds): '''Convert this cartesian to a geodetic (lat-/longitude) point. @kwarg datum: Optional datum (L{Datum}) or C{None}. @kwarg LatLon: Optional class to return the geodetic point (C{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if B{C{LatLon=None}}. @return: The geodetic point (B{C{LatLon}}) or if B{C{LatLon}} is C{None}, an L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with C{C} and C{M} if available. @raise TypeError: Invalid B{C{datum}} or B{C{LatLon_kwds}}. ''' if datum in (None, self.datum): r = self.toEcef() else: _xinstanceof(Datum, datum=datum) c = self.convertDatum(datum) r = c.Ecef(c.datum).reverse(c, M=True) if LatLon is not None: # class or .classof r = LatLon(r.lat, r.lon, **_xkwds(LatLon_kwds, datum=r.datum, height=r.height)) _datum_datum(r.datum, datum or self.datum) return self._xnamed(r)
def __init__(self, x, y=None, z=None, h=0, ll=None, datum=None, name=NN): '''New n-vector normal to the earth's surface. @arg x: An C{Nvector}, L{Vector3Tuple}, L{Vector4Tuple} or the C{X} coordinate (C{scalar}). @arg y: The C{Y} coordinate (C{scalar}) if B{C{x}} C{scalar}. @arg z: The C{Z} coordinate (C{scalar}) if B{C{x}} C{scalar}. @kwarg h: Optional height above surface (C{meter}). @kwarg ll: Optional, original latlon (C{LatLon}). @kwarg datum: Optional, I{pass-thru} datum (L{Datum}). @kwarg name: Optional name (C{str}). @raise TypeError: Non-scalar B{C{x}}, B{C{y}} or B{C{z}} coordinate or B{C{x}} not an C{Nvector}, L{Vector3Tuple} or L{Vector4Tuple} or invalid B{C{datum}}. @example: >>> from pygeodesy.sphericalNvector import Nvector >>> v = Nvector(0.5, 0.5, 0.7071, 1) >>> v.toLatLon() # 45.0°N, 045.0°E, +1.00m ''' x, y, z, h, d, n = _xyzhdn6(x, y, z, h, datum, ll) Vector3d.__init__(self, x, y, z, ll=ll, name=name or n) if h: self.h = h if d not in (None, self._datum): _xinstanceof(Datum, datum=d) self._datum = d # pass-thru
def nearestOn3(point, points, closed=False, radius=R_M, height=None): '''Locate the point on a polygon (with great circle arcs joining consecutive points) closest to an other point. If the given point is within the extent of any great circle arc, the closest point is on that arc. Otherwise, the closest is the nearest of the arc's end points. @arg point: The other, reference point (L{LatLon}). @arg points: The polygon points (L{LatLon}[]). @kwarg closed: Optionally, close the polygon (C{bool}). @kwarg radius: Mean earth radius (C{meter}). @kwarg height: Optional height, overriding the mean height for a point within the arc (C{meter}). @return: A L{NearestOn3Tuple}C{(closest, distance, angle)} of the C{closest} point (L{LatLon}) on the polygon, the C{distance} and the C{angle} between the C{closest} and the given B{C{point}}. The C{distance} is in C{meter}, same units as B{C{radius}}, the C{angle} is in compass C{degrees360}. @raise PointsError: Insufficient number of B{C{points}}. @raise TypeError: Some B{C{points}} or B{C{point}} not C{LatLon}. ''' _xinstanceof(LatLon, point=point) return point.nearestOn3(points, closed=closed, radius=radius, height=height)
def _datum_setter(self, datum, knots): '''(INTERNAL) Set the datum. ''' d = datum or getattr(knots[0], _datum_, datum) if d and d != self.datum: _xinstanceof(Datum, datum=d) self._datum = d
def __init__(self, e, n, h=0, conic=Conics.WRF_Lb, name=''): '''New L{Lcc} Lamber conformal conic position. @arg e: Easting (C{meter}). @arg n: Northing (C{meter}). @kwarg h: Optional height (C{meter}). @kwarg conic: Optional, the conic projection (L{Conic}). @kwarg name: Optional name (C{str}). @return: The Lambert location (L{Lcc}). @raise LCCError: Invalid B{C{h}} or invalid or negative B{C{e}} or B{C{n}}. @raise TypeError: If B{C{conic}} is not L{Conic}. @example: >>> lb = Lcc(448251, 5411932.0001) ''' _xinstanceof(Conic, conic=conic) self._conic = conic self._easting = Easting(e, falsed=conic.E0 > 0, Error=LCCError) self._northing = Northing(n, falsed=conic.N0 > 0, Error=LCCError) if h: self._height = Height(h, name='h', Error=LCCError) if name: self.name = name
def toUtmUps(self, pole=''): '''Convert this C{LatLon} point to a UTM or UPS coordinate. @kwarg pole: Optional top/center of UPS (stereographic) projection (C{str}, 'N[orth]' or 'S[outh]'). @return: The UTM or UPS coordinate (L{Utm} or L{Ups}). @raise TypeError: Result in L{Utm} or L{Ups}. @see: Function L{toUtmUps}. ''' if self._utm: u = self._utm elif self._ups and (self._ups.pole == pole or not pole): u = self._ups else: from pygeodesy.utmups import toUtmUps8, Utm, Ups # PYCHOK recursive import u = toUtmUps8(self, datum=self.datum, Utm=Utm, Ups=Ups, pole=pole) if isinstance(u, Utm): self._utm = u elif isinstance(u, Ups): self._ups = u else: _xinstanceof(Utm, Ups, toUtmUps8=u) return u
def __init__(self, latlon0, par1, par2=None, E0=0, N0=0, k0=1, opt3=0, name='', auth=''): '''New Lambert conformal conic projection. @arg latlon0: Origin with (ellipsoidal) datum (C{LatLon}). @arg par1: First standard parallel (C{degrees90}). @kwarg par2: Optional, second standard parallel (C{degrees90}). @kwarg E0: Optional, false easting (C{meter}). @kwarg N0: Optional, false northing (C{meter}). @kwarg k0: Optional scale factor (C{scalar}). @kwarg opt3: Optional meridian (C{degrees180}). @kwarg name: Optional name of the conic (C{str}). @kwarg auth: Optional authentication authority (C{str}). @return: A Lambert projection (L{Conic}). @raise TypeError: Non-ellipsoidal B{C{latlon0}}. @raise ValueError: Invalid B{C{par1}}, B{C{par2}}, B{C{E0}}, B{C{N0}}, B{C{k0}} or B{C{opt3}}. @example: >>> from pygeodesy import Conic, Datums, ellipsoidalNvector >>> ll0 = ellipsoidalNvector.LatLon(23, -96, datum=Datums.NAD27) >>> Snyder = Conic(ll0, 33, 45, E0=0, N0=0, name='Snyder') ''' if latlon0 is not None: _xinstanceof(_LLEB, latlon0=latlon0) self._phi0, self._lam0 = latlon0.philam self._par1 = Phi_(par1, name='par1') self._par2 = self._par1 if par2 is None else Phi_(par2, name='par2') if k0 != 1: self._k0 = Scalar_(k0, name='k0') if E0: self._E0 = Northing(E0, name='E0', falsed=True) if N0: self._N0 = Easting(N0, name='N0', falsed=True) if opt3: self._opt3 = Lam_(opt3, name='opt3') self.toDatum(latlon0.datum)._dup2(self) self._register(Conics, name) elif name: self._name = name if auth: self._auth = auth
def _toLatLon(self, lat, lon, LatLon, LatLon_kwds): '''(INTERNAL) Check B{C{LatLon}} and return an instance. ''' kwds = _xkwds(LatLon_kwds, datum=self.datum) r = LatLon(lat, lon, **kwds) # handle .classof B = _LLEB if self.datum.isEllipsoidal else _LLB _xinstanceof(B, LatLon=r) return r
def _ellipsoidal_datum(a_f, name=NN): '''(INTERNAL) Create a L{Datum} from an L{Ellipsoid} or L{Ellipsoid2} or C{a_f2Tuple}. ''' if isinstance(a_f, Datum): return a_f E, n = _En2(a_f, name) if not E: _xinstanceof(Datum, Ellipsoid, Ellipsoid2, a_f2Tuple, datum=a_f) return Datum(E, transform=Transforms.Identity, name=n)
def thomas_(phi2, phi1, lam21, datum=Datums.WGS84): '''Compute the I{angular} distance between two (ellipsoidal) points using U{Thomas'<https://apps.DTIC.mil/dtic/tr/fulltext/u2/703541.pdf>} formula. @arg phi2: End latitude (C{radians}). @arg phi1: Start latitude (C{radians}). @arg lam21: Longitudinal delta, M{end-start} (C{radians}). @kwarg datum: Ellipsoidal datum to use (L{Datum}). @return: Angular distance (C{radians}). @raise TypeError: Invalid B{C{datum}}. @see: Functions L{thomas}, L{cosineAndoyerLambert_}, L{cosineForsytheAndoyerLambert_}, L{cosineLaw_}, L{equirectangular_}, L{euclidean_}, L{flatLocal_}/L{hubeny_}, L{flatPolar_}, L{haversine_} and L{vincentys_} and U{Geodesy-PHP <https://GitHub.com/jtejido/geodesy-php/blob/master/src/Geodesy/ Distance/ThomasFormula.php>}. ''' _xinstanceof(Datum, datum=datum) s2, c2, s1, c1, _, c21 = sincos2(phi2, phi1, lam21) E = datum.ellipsoid if E.f and abs(c1) > EPS and abs(c2) > EPS: r1 = atan(E.b_a * s1 / c1) r2 = atan(E.b_a * s2 / c2) j = (r2 + r1) / 2.0 k = (r2 - r1) / 2.0 sj, cj, sk, ck, sl_2, _ = sincos2(j, k, lam21 / 2.0) h = fsum_(sk**2, (ck * sl_2)**2, -(sj * sl_2)**2) if EPS < abs(h) < EPS1: u = 1 / (1 - h) d = 2 * atan(sqrt(h * u)) # == acos(1 - 2 * h) sd, cd = sincos2(d) if abs(sd) > EPS: u = 2 * (sj * ck)**2 * u v = 2 * (sk * cj)**2 / h x = u + v y = u - v t = d / sd s = 4 * t**2 e = 2 * cd a = s * e b = 2 * d c = t - (a - e) / 2.0 s = fsum_(a * x, c * x**2, -b * y, -e * y**2, s * x * y) * E.f / 16.0 s = fsum_(t * x, -y, -s) * E.f / 4.0 return d - s * sd # fall back to cosineLaw_ return acos(s1 * s2 + c1 * c2 * c21)
def latlon0(self, latlon0): '''Set the center lat- and longitude (L{LatLon2Tuple}, ellipsoidal C{LatLon} or L{LatLon4Tuple}). @raise CSSError: Invalid B{C{latlon0}} or ellipsoidal mismatch of B{C{latlon0}} and this projection. ''' _xinstanceof(_LLEB, LatLon4Tuple, LatLon2Tuple, latlon0=latlon0) if hasattr(latlon0, _datum_): self._datumatch(latlon0) self.reset(latlon0.lat, latlon0.lon)
def exactTM(self, exactTM): '''Set the ETM projection (L{ExactTransverseMercator}). ''' _xinstanceof(ExactTransverseMercator, exactTM=exactTM) E = self.datum.ellipsoid if exactTM._E != E or exactTM.majoradius != E.a \ or exactTM.flattening != E.f: raise ETMError(repr(exactTM), txt=_incompatible(repr(E))) self._exactTM = exactTM self._scale0 = exactTM.k0
def _CassiniSoldner(cs0): '''(INTERNAL) Get/set default projection. ''' if cs0 is None: global _CassiniSoldner0 if _CassiniSoldner0 is None: _CassiniSoldner0 = CassiniSoldner(0, 0, name='Default') cs0 = _CassiniSoldner0 else: _xinstanceof(CassiniSoldner, cs0=cs0) return cs0
def latlon0(self, latlon0): '''Set the center lat- and longitude (C{LatLon}, L{LatLon2Tuple} or L{LatLon4Tuple}). @raise AzimuthalError: Invalid B{C{lat0}} or B{C{lon0}} or ellipsoidal mismatch of B{C{latlon0}} and this projection. ''' B = _LLEB if self.datum.isEllipsoidal else _LLB _xinstanceof(B, LatLon2Tuple, LatLon4Tuple, latlon0=latlon0) if hasattr(latlon0, _datum_): _datum_datum(self.datum, latlon0.datum, Error=AzimuthalError) self.reset(latlon0.lat, latlon0.lon)
def _ellipsoid(ellipsoid, name=NN): # in .trf '''(INTERNAL) Create an L{Ellipsoid} or L{Ellipsoid2} from L{datum} or C{a_f2Tuple}. ''' E, _ = _En2(ellipsoid, name) if not E: _xinstanceof(Ellipsoid, Ellipsoid2, a_f2Tuple, Datum, ellipsoid=ellipsoid) return E
def reframe(self, reframe): '''Set or clear this point's reference frame. @arg reframe: Reference frame (L{RefFrame}) or C{None}. @raise TypeError: The B{C{reframe}} is not a L{RefFrame}. ''' if reframe is not None: _xinstanceof(RefFrame, reframe=reframe) self._reframe = reframe elif self.reframe is not None: self._reframe = None
def to4Tuple(self, datum): '''Extend this L{LatLon3Tuple} to a L{LatLon4Tuple}. @arg datum: The datum to add (C{Datum}). @return: A L{LatLon4Tuple}C{(lat, lon, height, datum)}. @raise TypeError: If B{C{datum}} not a C{Datum}. ''' from pygeodesy.datum import Datum _xinstanceof(Datum, datum=datum) return self._xtend(LatLon4Tuple, datum)
def to4Tuple(self, datum): '''Extend this L{PhiLam3Tuple} to a L{PhiLam4Tuple}. @arg datum: The datum to add (C{Datum}). @return: A L{PhiLam4Tuple}C{(phi, lam, height, datum)}. @raise TypeError: If B{C{datum}} not a C{Datum}. ''' from pygeodesy.datums import Datum _xinstanceof(Datum, datum=datum) return self._xtend(PhiLam4Tuple, datum)
def datum(self, datum): '''Set this point's datum I{without conversion}. @arg datum: New datum (L{Datum}). @raise TypeError: If B{C{datum}} is not a L{Datum} or not spherical. ''' _xinstanceof(Datum, datum=datum) if not datum.isSpherical: raise _IsnotError(_spherical_, datum=datum) self._update(datum != self._datum) self._datum = datum
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 = map1(float, latlon.lat, latlon.lon) _xinstanceof(_LLEB, LatLonDatum5Tuple, latlon=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))