def _intersects2(c1, rad1, c2, rad2, radius=R_M, # in .ellipsoidalBase._intersects2 height=None, wrap=True, too_d=None, LatLon=LatLon, **LatLon_kwds): # (INTERNAL) Intersect two spherical circles, see L{intersections2} # above, separated to allow callers to embellish any exceptions def _dest3(bearing, h): a, b = _destination2(a1, b1, r1, bearing) return _latlon3(degrees90(a), degrees180(b), h, intersections2, LatLon, **LatLon_kwds) r1, r2, f = _rads3(rad1, rad2, radius) if f: # swapped c1, c2 = c2, c1 # PYCHOK swap a1, b1 = c1.philam a2, b2 = c2.philam db, b2 = unrollPI(b1, b2, wrap=wrap) d = vincentys_(a2, a1, db) # radians if d < max(r1 - r2, EPS): raise ValueError(_near_concentric_) x = fsum_(r1, r2, -d) # overlap if x > EPS: sd, cd, sr1, cr1, _, cr2 = sincos2(d, r1, r2) x = sd * sr1 if abs(x) < EPS: raise ValueError(_invalid_) x = acos1((cr2 - cd * cr1) / x) # 0 <= x <= PI elif x < 0: t = (d * radius) if too_d is None else too_d raise ValueError(_too_distant_fmt_ % (t,)) if height is None: # "radical height" f = _radical2(d, r1, r2).ratio h = Height(favg(c1.height, c2.height, f=f)) else: h = Height(height) b = bearing_(a1, b1, a2, b2, final=False, wrap=wrap) if x < _EPS_I2: # externally ... r = _dest3(b, h) elif x > _PI_EPS_I2: # internally ... r = _dest3(b + PI, h) else: return _dest3(b + x, h), _dest3(b - x, h) return r, r # ... abutting circles
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 __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 _triangulate(point1, bearing1, point2, bearing2, height=None, **LatLon_LatLon_kwds): # (INTERNAL)Locate a point given two known points and initial # bearings from those points, see LatLon.triangulate above def _gc(p, b, _i_): n = p.toNvector() de = NorthPole.cross(n, raiser=_pole_).unit() # east vector @ n dn = n.cross(de) # north vector @ n s, c = sincos2d(Bearing(b, name=_bearing_ + _i_)) dest = de.times(s) dnct = dn.times(c) d = dnct.plus(dest) # direction vector @ n return n.cross(d) # great circle point + bearing if point1.isequalTo(point2, EPS): raise _ValueError(points=point2, txt=_coincident_) gc1 = _gc(point1, bearing1, _1_) # great circle p1 + b1 gc2 = _gc(point2, bearing2, _2_) # great circle p2 + b2 n = gc1.cross(gc2, raiser=_points_) # n-vector of intersection point h = point1._havg(point2) if height is None else Height(height) kwds = _xkwds(LatLon_LatLon_kwds, height=h) return n.toLatLon(**kwds) # Nvector(n.x, n.y, n.z).toLatLon(...)
def toLatLon(self, height=None, LatLon=None, datum=None, **LatLon_kwds): '''Convert this n-vector to an C{Nvector}-based geodetic point. @kwarg height: Optional height, overriding this n-vector's height (C{meter}). @kwarg LatLon: Optional class to return the geodetic point (L{LatLon}) or C{None}. @kwarg datum: Optional, spherical datum (C{Datum}). @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if C{B{LatLon}=None}. @return: The geodetic point (L{LatLon}) or if B{C{LatLon}} is 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{LatLon}}. @raise ValueError: Invalid B{C{height}}. @example: >>> v = Nvector(0.5, 0.5, 0.7071) >>> p = v.toLatLon() # 45.0°N, 45.0°E ''' # use self.Cartesian(Cartesian=None) if h == self.h and # d == self.datum, for better accuracy of the height h = self.h if height is None else Height(height) r = self.Ecef(datum or self.datum).forward(self.latlon, height=h, M=True) if LatLon is not None: # class or .classof r = LatLon(r.lat, r.lon, r.height, datum=r.datum, **LatLon_kwds) return self._xnamed(r)
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 toNed(distance, bearing, elevation, Ned=Ned, name=NN): '''Create an NED vector from distance, bearing and elevation (in local coordinate system). @arg distance: NED vector length (C{meter}). @arg bearing: NED vector bearing (compass C{degrees360}). @arg elevation: NED vector elevation from local coordinate frame horizontal (C{degrees}). @kwarg Ned: Optional class to return the NED (L{Ned}) or C{None}. @kwarg name: Optional name (C{str}). @return: An NED vector equivalent to this B{C{distance}}, B{C{bearing}} and B{C{elevation}} (L{Ned}) or if B{C{Ned=None}}, an L{Ned3Tuple}C{(north, east, down)}. @raise ValueError: Invalid B{C{distance}}, B{C{bearing}} or B{C{elevation}}. @JSname: I{fromDistanceBearingElevation}. ''' d = Distance(distance) sb, cb, se, ce = sincos2d(Bearing(bearing), Height(elevation=elevation)) n = cb * d * ce e = sb * d * ce d *= se r = Ned3Tuple(n, e, -d) if Ned is None else \ Ned(n, e, -d) return _xnamed(r, name)
def _intermediateTo(p1, p2, fraction, height, wrap): # (INTERNAL) Helper for C{ellipsoidalKarney.LatLon.intermediateTo} # and C{ellipsoidalVincenty.LatLon.intermediateTo}. t = p1.distanceTo3(p2, wrap=wrap) f = Scalar(fraction=fraction) h = p1._havg(p2, f=f) if height is None else Height(height) return p1.destination(t.distance * f, t.initial, height=h)
def toLatLon(self, LatLon=None, height=None, **LatLon_kwds): '''Convert this L{Css} to an (ellipsoidal) geodetic point. @kwarg LatLon: Optional, ellipsoidal class to return the geodetic point (C{LatLon}) or C{None}. @kwarg height: Optional height for the point, overriding the default height (C{meter}). @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}, a L{LatLon4Tuple}C{(lat, lon, height, datum)}. @raise TypeError: If B{C{LatLon}} or B{C{datum}} is not ellipsoidal or invalid B{C{height}} or B{C{LatLon_kwds}}. ''' if LatLon: _xsubclassof(_LLEB, LatLon=LatLon) lat, lon = self.latlon d = self.cs0.datum h = self.height if height is None else Height(height) r = LatLon4Tuple(lat, lon, h, d) if LatLon is None else \ LatLon(lat, lon, height=h, datum=d, **LatLon_kwds) return self._xnamed(r)
def __init__(self, e, n, h=0, cs0=_CassiniSoldner0, name=NN): '''New L{Css} Cassini-Soldner position. @arg e: Easting (C{meter}). @arg n: Northing (C{meter}). @kwarg h: Optional height (C{meter}). @kwarg cs0: Optional, the Cassini-Soldner projection (L{CassiniSoldner}). @kwarg name: Optional name (C{str}). @return: The Cassini-Soldner location (L{Css}). @raise CSSError: If B{C{e}} or B{C{n}} is invalid. @raise ImportError: Package U{GeographicLib<https://PyPI.org/ project/geographiclib>} missing. @raise TypeError: If B{C{cs0}} is not L{CassiniSoldner}. @raise ValueError: Invalid B{C{h}}. @example: >>> cs = Css(448251, 5411932.0001) ''' self._cs0 = _CassiniSoldner(cs0) self._easting = Easting(e, Error=CSSError) self._northing = Northing(n, Error=CSSError) if h: self._height = Height(h, name=_h_) if name: self.name = name
def intermediateChordTo(self, other, fraction, height=None): '''Locate the point projected from the point at given fraction on a straight line (chord) between this and an other point. @arg other: The other point (L{LatLon}). @arg fraction: Fraction between both points (float, between 0.0 for this and 1.0 for the other point). @kwarg height: Optional height at the intermediate point, overriding the fractional height (C{meter}). @return: Intermediate point (L{LatLon}). @raise TypeError: The B{C{other}} point is not L{LatLon}. @example: >>> p = LatLon(52.205, 0.119) >>> q = LatLon(48.857, 2.351) >>> i = p.intermediateChordTo(q, 0.25) # 51.3723°N, 000.7072°E @JSname: I{intermediatePointOnChordTo}, I{intermediatePointDirectlyTo}. ''' self.others(other) f = Scalar(fraction=fraction) i = other.toNvector().times(f).plus( self.toNvector().times(1 - f)) # i = other.toNvector() * f + \ # self.toNvector() * (1 - f)) h = self._havg(other, f=f) if height is None else Height(height) return i.toLatLon(height=h, LatLon=self.classof) # Nvector(i.x, i.y, i.z).toLatLon(...)
def meanOf(points, height=None, LatLon=LatLon, **LatLon_kwds): '''Compute the geographic mean of several points. @arg points: Points to be averaged (L{LatLon}[]). @kwarg height: Optional height at mean point, overriding the mean height (C{meter}). @kwarg LatLon: Optional class to return the mean point (L{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if B{C{LatLon=None}}. @return: Point at geographic mean and height (B{C{LatLon}}) or a L{LatLon3Tuple}C{(lat, lon, height)} if B{C{LatLon}} is C{None}. @raise TypeError: Some B{C{points}} are not L{LatLon}. @raise ValueError: No B{C{points}} or invalid B{C{height}}. ''' # geographic mean n, points = _Trll.points2(points, closed=False) m = sumOf(points[i]._N_vector for i in range(n)) lat, lon = m._N_vector.latlon if height is None: h = fmean(points[i].height for i in range(n)) else: h = Height(height) return _latlon3(lat, lon, h, meanOf, LatLon, **LatLon_kwds)
def destination(self, distance, bearing, radius=R_M, height=None): '''Locate the destination from this point after having travelled the given distance on the given initial bearing. @arg distance: Distance travelled (C{meter}, same units as B{C{radius}}). @arg bearing: Bearing from this point (compass C{degrees360}). @kwarg radius: Mean earth radius (C{meter}). @kwarg height: Optional height at destination (C{meter}, same units a B{C{radius}}). @return: Destination point (L{LatLon}). @raise ValueError: Invalid B{C{distance}}, B{C{bearing}}, B{C{radius}} or B{C{height}}. @example: >>> p1 = LatLon(51.4778, -0.0015) >>> p2 = p1.destination(7794, 300.7) >>> p2.toStr() # '51.5135°N, 000.0983°W' @JSname: I{destinationPoint}. ''' a, b = self.philam r, t = _angular(distance, radius), Bearing_(bearing) a, b = _destination2(a, b, r, t) h = self.height if height is None else Height(height) return self.classof(degrees90(a), degrees180(b), height=h)
def intermediateTo(self, other, fraction, height=None, wrap=False): '''Locate the point at given fraction between this and an other point. @arg other: The other point (L{LatLon}). @arg fraction: Fraction between both points (float, between 0.0 for this and 1.0 for the other point). @kwarg height: Optional height, overriding the fractional height (C{meter}). @kwarg wrap: Wrap and unroll longitudes (C{bool}). @return: Intermediate point (L{LatLon}). @raise TypeError: The B{C{other}} point is not L{LatLon}. @raise ValueError: Invalid B{C{fraction}} or B{C{height}}. @example: >>> p1 = LatLon(52.205, 0.119) >>> p2 = LatLon(48.857, 2.351) >>> p = p1.intermediateTo(p2, 0.25) # 51.3721°N, 000.7073°E @JSname: I{intermediatePointTo}. ''' self.others(other) f = Scalar(fraction, name='fraction') a1, b1 = self.philam a2, b2 = other.philam db, b2 = unrollPI(b1, b2, wrap=wrap) r = haversine_(a2, a1, db) sr = sin(r) if abs(sr) > EPS: sa1, ca1, sa2, ca2, \ sb1, cb1, sb2, cb2 = sincos2(a1, a2, b1, b2) A = sin((1 - f) * r) / sr B = sin(f * r) / sr x = A * ca1 * cb1 + B * ca2 * cb2 y = A * ca1 * sb1 + B * ca2 * sb2 z = A * sa1 + B * sa2 a = atan2(z, hypot(x, y)) b = atan2(y, x) else: # points too close a = favg(a1, a2, f=f) b = favg(b1, b2, f=f) if height is None: h = self._havg(other, f=f) else: h = Height(height) return self.classof(degrees90(a), degrees180(b), height=h)
def __new__(cls, cll, precision=3, name=NN): '''New L{Georef} from an other L{Georef} instance or georef C{str} or from a C{LatLon} instance or lat-/longitude C{str}. @arg cll: Cell or location (L{Georef} or C{str}, C{LatLon} or C{str}). @kwarg precision: Optional, the desired georef resolution and length (C{int} 0..11), see function L{wgrs.encode} for more details. @kwarg name: Optional name (C{str}). @return: New L{Georef}. @raise RangeError: Invalid B{C{cll}} lat- or longitude. @raise TypeError: Invalid B{C{cll}}. @raise WGRSError: INValid or non-alphanumeric B{C{cll}}. ''' h = None if isinstance(cll, Georef): g, p = _2geostr2(str(cll)) self = Str.__new__(cls, g) self._latlon = LatLon2Tuple(*cll._latlon) self._precision = p # cll._precision if cll._name: self._name = cll._name elif isstr(cll): if ',' in cll: lat, lon, h = _2fllh(*parse3llh(cll, height=None)) g = encode(lat, lon, precision=precision, height=h) # PYCHOK false self = Str.__new__(cls, g) self._latlon = LatLon2Tuple(lat, lon) else: self = Str.__new__(cls, cll.upper()) self._decode() else: # assume LatLon try: lat, lon, h = _2fllh(cll.lat, cll.lon) h = getattr(cll, _height_, h) except AttributeError: raise _xStrError(Georef, cll=cll) # Error=WGRSError g = encode(lat, lon, precision=precision, height=h) # PYCHOK false self = Str.__new__(cls, g) self._latlon = LatLon2Tuple(lat, lon) if h not in (None, MISSING): self._height = Height(h) if self._precision is None: self._precision = _2Precision(precision) if name: self.name = name return self
def to3Tuple(self, height): '''Extend this L{LatLon2Tuple} to a L{LatLon3Tuple}. @arg height: The height to add (C{scalar}). @return: A L{LatLon3Tuple}C{(lat, lon, height)}. @raise ValueError: Invalid B{C{height}}. ''' from pygeodesy.units import Height return self._xtend(LatLon3Tuple, Height(height))
def to3Tuple(self, height): '''Extend this L{PhiLam2Tuple} to a L{PhiLam3Tuple}. @arg height: The height to add (C{scalar}). @return: A L{PhiLam3Tuple}C{(phi, lam, height)}. @raise ValueError: Invalid B{C{height}}. ''' from pygeodesy.units import Height return self._xtend(PhiLam3Tuple, Height(height))
def to4Tuple(self, h): '''Extend this L{Vector3Tuple} to a L{Vector4Tuple}. @arg h: The height to add (C{scalar}). @return: A L{Vector4Tuple}C{(x, y, z, h)}. @raise ValueError: Invalid B{C{h}}. ''' from pygeodesy.units import Height return self._xtend(Vector4Tuple, Height(h, name=_h_))
def h(self, h): '''Set the height above surface. @arg h: New height (C{meter}). @raise TypeError: If B{C{h}} invalid. @raise VectorError: If B{C{h}} invalid. ''' h = Height(h=h, Error=VectorError) self._update(h != self._h, '_latlon', '_philam') self._h = h
def height(self, height): '''Set the height. @arg height: New height (C{meter}). @raise TypeError: Invalid B{C{height}} C{type}. @raise ValueError: Invalid B{C{height}}. ''' h = Height(height) self._update(h != self._height) self._height = h
def toCartesian(self, h=None, Cartesian=None, datum=None, **Cartesian_kwds): '''Convert this n-vector to C{Nvector}-based cartesian (ECEF) coordinates. @kwarg h: Optional height, overriding this n-vector's height (C{meter}). @kwarg Cartesian: Optional class to return the (ECEF) coordinates (L{Cartesian}). @kwarg datum: Optional, spherical datum (C{Datum}). @kwarg Cartesian_kwds: Optional, additional B{C{Cartesian}} keyword arguments, ignored if B{C{Cartesian=None}}. @return: The cartesian (ECEF) coordinates (B{C{Cartesian}}) or if B{C{Cartesian}} 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{Cartesian}}. @raise ValueError: Invalid B{C{h}}. @example: >>> v = Nvector(0.5, 0.5, 0.7071) >>> c = v.toCartesian() # [3194434, 3194434, 4487327] >>> p = c.toLatLon() # 45.0°N, 45.0°E ''' x, y, z = self.x, self.y, self.z h = self.h if h is None else Height(h=h) d = datum or self.datum E = d.ellipsoid # Kenneth Gade eqn (22) n = E.b / hypot_(x * E.a_b, y * E.a_b, z) r = h + n * E.a_b**2 c = self.Ecef(d).reverse(x * r, y * r, z * (n + h), M=True) if Cartesian is not None: # class or .classof c = Cartesian(c, **Cartesian_kwds) return self._xnamed(c)
def rhumbMidpointTo(self, other, height=None): '''Return the (loxodromic) midpoint between this and an other point. @arg other: The other point (spherical LatLon). @kwarg height: Optional height, overriding the mean height (C{meter}). @return: The midpoint (spherical C{LatLon}). @raise TypeError: The I{other} point is not spherical. @raise ValueError: Invalid B{C{height}}. @example: >>> p = LatLon(51.127, 1.338) >>> q = LatLon(50.964, 1.853) >>> m = p.rhumb_midpointTo(q) >>> m.toStr() # '51.0455°N, 001.5957°E' ''' self.others(other) # see <https://MathForum.org/library/drmath/view/51822.html> a1, b1 = self.philam a2, b2 = other.philam if abs(b2 - b1) > PI: b1 += PI2 # crossing anti-meridian a3 = favg(a1, a2) b3 = favg(b1, b2) f1 = tanPI_2_2(a1) if abs(f1) > EPS: f2 = tanPI_2_2(a2) f = f2 / f1 if abs(f) > EPS: f = log(f) if abs(f) > EPS: f3 = tanPI_2_2(a3) b3 = fsum_(b1 * log(f2), -b2 * log(f1), (b2 - b1) * log(f3)) / f h = self._havg(other) if height is None else Height(height) return self.classof(degrees90(a3), degrees180(b3), height=h)
def rhumbDestination(self, distance, bearing, radius=R_M, height=None): '''Return the destination point having travelled along a rhumb (loxodrome) line from this point the given distance on the given bearing. @arg distance: Distance travelled (C{meter}, same units as I{radius}). @arg bearing: Bearing from this point (compass C{degrees360}). @kwarg radius: Mean earth radius (C{meter}). @kwarg height: Optional height, overriding the default height (C{meter}, same unit as I{radius}). @return: The destination point (spherical C{LatLon}). @raise ValueError: Invalid B{C{distance}}, B{C{bearing}}, B{C{radius}} or B{C{height}}. @example: >>> p = LatLon(51.127, 1.338) >>> q = p.rhumbDestination(40300, 116.7) # 50.9642°N, 001.8530°E @JSname: I{rhumbDestinationPoint} ''' r = _angular(distance, radius) a1, b1 = self.philam sb, cb = sincos2(Bearing_(bearing)) da = r * cb a2 = a1 + da # normalize latitude if past pole if a2 > PI_2: a2 = PI - a2 elif a2 < -PI_2: a2 = -PI - a2 dp = log(tanPI_2_2(a2) / tanPI_2_2(a1)) # E-W course becomes ill-conditioned with 0/0 q = (da / dp) if abs(dp) > EPS else cos(a1) b2 = (b1 + r * sb / q) if abs(q) > EPS else b1 h = self.height if height is None else Height(height) return self.classof(degrees90(a2), degrees180(b2), height=h)
def toLatLon(self, LatLon=None, datum=None, height=None, **LatLon_kwds): '''Convert this L{Lcc} to an (ellipsoidal) geodetic point. @kwarg LatLon: Optional, ellipsoidal class to return the geodetic point (C{LatLon}) or C{None}. @kwarg datum: Optional datum to use, otherwise use this B{C{Lcc}}'s conic.datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}). @kwarg height: Optional height for the point, overriding the default height (C{meter}). @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, unused if C{B{LatLon}=None}. @return: The point (B{C{LatLon}}) or a L{LatLon4Tuple}C{(lat, lon, height, datum)} if B{C{LatLon}} is C{None}. @raise TypeError: If B{C{LatLon}} or B{C{datum}} is not ellipsoidal or not valid. ''' if LatLon: _xsubclassof(_LLEB, LatLon=LatLon) c = self.conic if datum not in (None, c.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._lam0 while True: p, x = x, c._xdef(t_ * c._pdef(x)) if abs(x - p) < 1e-9: # XXX EPS too small? break lat = degrees90(x) lon = degrees180((atan(e / n) + c._opt3) * c._n_ + c._lam0) h = self.height if height is None else Height(height) r = _LatLon4Tuple(lat, lon, h, c.datum, LatLon, LatLon_kwds) return self._xnamed(r)
def midpointTo(self, other, height=None, wrap=False): '''Find the midpoint between this and an other point. @arg other: The other point (L{LatLon}). @kwarg height: Optional height for midpoint, overriding the mean height (C{meter}). @kwarg wrap: Wrap and unroll longitudes (C{bool}). @return: Midpoint (L{LatLon}). @raise TypeError: The B{C{other}} point is not L{LatLon}. @raise ValueError: Invalid B{C{height}}. @example: >>> p1 = LatLon(52.205, 0.119) >>> p2 = LatLon(48.857, 2.351) >>> m = p1.midpointTo(p2) # '50.5363°N, 001.2746°E' ''' self.others(other) # see <https://MathForum.org/library/drmath/view/51822.html> a1, b1 = self.philam a2, b2 = other.philam db, b2 = unrollPI(b1, b2, wrap=wrap) sa1, ca1, sa2, ca2, sdb, cdb = sincos2(a1, a2, db) x = ca2 * cdb + ca1 y = ca2 * sdb a = atan2(sa1 + sa2, hypot(x, y)) b = atan2(y, x) + b1 if height is None: h = self._havg(other) else: h = Height(height) return self.classof(degrees90(a), degrees180(b), height=h)
def toCss(latlon, cs0=_CassiniSoldner0, height=None, Css=Css, name=NN): '''Convert an (ellipsoidal) geodetic point to a Cassini-Soldner location. @arg latlon: Ellipsoidal point (C{LatLon} or L{LatLon4Tuple}). @kwarg cs0: Optional, the Cassini-Soldner projection to use (L{CassiniSoldner}). @kwarg height: Optional height for the point, overriding the default height (C{meter}). @kwarg Css: Optional class to return the location (L{Css}) or C{None}. @kwarg name: Optional B{C{Css}} name (C{str}). @return: The Cassini-Soldner location (B{C{Css}}) or an L{EasNor3Tuple}C{(easting, northing, height)} if B{C{Css}} is C{None}. @raise CSSError: Ellipsoidal mismatch of B{C{latlon}} and B{C{cs0}}. @raise ImportError: Package U{GeographicLib<https://PyPI.org/ project/geographiclib>} missing. @raise TypeError: If B{C{latlon}} is not ellipsoidal. ''' _xinstanceof(_LLEB, LatLon4Tuple, latlon=latlon) cs = _CassiniSoldner(cs0) cs._datumatch(latlon) c = cs.forward4(latlon.lat, latlon.lon) h = latlon.height if height is None else Height(height) if Css is None: r = EasNor3Tuple(c.easting, c.northing, h) else: r = Css(c.easting, c.northing, h=h, cs0=cs) r._latlon = LatLon2Tuple(latlon.lat, latlon.lon) r._azi, r._rk = c.azimuth, c.reciprocal return _xnamed(r, name or nameof(latlon))
def horizon(height, radius=R_M, refraction=False): '''Determine the distance to the horizon from a given altitude above the (spherical) earth. @arg height: Altitude (C{meter} or same units as B{C{radius}}). @kwarg radius: Optional mean earth radius (C{meter}). @kwarg refraction: Consider atmospheric refraction (C{bool}). @return: Distance (C{meter}, same units as B{C{height}} and B{C{radius}}). @raise ValueError: Invalid B{C{height}} or B{C{radius}}. @see: U{Distance to horizon<https://www.EdWilliams.org/avform.htm#Horizon>}. ''' h, r = Height(height), Radius(radius) if min(h, r) < 0: raise InvalidError(height=height, radius=radius) if refraction: d2 = 2.415750694528 * h * r # 2.0 / 0.8279 else: d2 = h * fsum_(r, r, h) return sqrt(d2)
def intermediateTo(self, other, fraction, height=None): '''Locate the point at a given fraction between this and an other point. @arg other: The other point (L{LatLon}). @arg fraction: Fraction between both points (float, between 0.0 for this and 1.0 for the other point). @kwarg height: Optional height at the intermediate point, overriding the fractional height (C{meter}). @return: Intermediate point (L{LatLon}). @raise TypeError: The B{C{other}} point is not L{LatLon}. @raise Valuerror: Points coincide or invalid B{C{height}}. @example: >>> p = LatLon(52.205, 0.119) >>> q = LatLon(48.857, 2.351) >>> i = p.intermediateTo(q, 0.25) # 51.3721°N, 000.7074°E @JSname: I{intermediatePointTo}. ''' q = self.others(other).toNvector() p = self.toNvector() f = Scalar(fraction=fraction) x = p.cross(q, raiser=_points_) d = x.unit().cross(p) # unit(p × q) × p # angular distance α, tan(α) = |p × q| / p ⋅ q s, c = sincos2(atan2(x.length, p.dot(q)) * f) # interpolated i = p.times(c).plus(d.times(s)) # p * cosα + d * sinα h = self._havg(other, f=f) if height is None else Height(height) return i.toLatLon(height=h, LatLon=self.classof) # Nvector(i.x, i.y, i.z).toLatLon(...)
def _h2m(kft, name): return Height(ft2m(kft * _1000_0), name=name, Error=WGRSError)
def intersection(start1, end1, start2, end2, height=None, wrap=False, LatLon=LatLon, **LatLon_kwds): '''Compute the intersection point of two paths both defined by two points or a start point and bearing from North. @arg start1: Start point of the first path (L{LatLon}). @arg end1: End point ofthe first path (L{LatLon}) or the initial bearing at the first start point (compass C{degrees360}). @arg start2: Start point of the second path (L{LatLon}). @arg end2: End point of the second path (L{LatLon}) or the initial bearing at the second start point (compass C{degrees360}). @kwarg height: Optional height for the intersection point, overriding the mean height (C{meter}). @kwarg wrap: Wrap and unroll longitudes (C{bool}). @kwarg LatLon: Optional class to return the intersection point (L{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if B{C{LatLon=None}}. @return: The intersection point (B{C{LatLon}}) or a L{LatLon3Tuple}C{(lat, lon, height)} if B{C{LatLon}} is C{None}. An alternate intersection point might be the L{antipode} to the returned result. @raise TypeError: A B{C{start}} or B{C{end}} point not L{LatLon}. @raise ValueError: Intersection is ambiguous or infinite or the paths are parallel, coincident or null or invalid B{C{height}}. @example: >>> p = LatLon(51.8853, 0.2545) >>> s = LatLon(49.0034, 2.5735) >>> i = intersection(p, 108.547, s, 32.435) # '50.9078°N, 004.5084°E' ''' _Trll.others(start1, name='start1') _Trll.others(start2, name='start2') hs = [start1.height, start2.height] a1, b1 = start1.philam a2, b2 = start2.philam db, b2 = unrollPI(b1, b2, wrap=wrap) r12 = haversine_(a2, a1, db) if abs(r12) < EPS: # [nearly] coincident points a, b = favg(a1, a2), favg(b1, b2) # see <https://www.EdWilliams.org/avform.htm#Intersection> elif isscalar(end1) and isscalar(end2): # both bearings sa1, ca1, sa2, ca2, sr12, cr12 = sincos2(a1, a2, r12) x1, x2 = (sr12 * ca1), (sr12 * ca2) if abs(x1) < EPS or abs(x2) < EPS: raise ValueError('intersection %s: %r vs %r' % ('parallel', (start1, end1), (start2, end2))) # handle domain error for equivalent longitudes, # see also functions asin_safe and acos_safe at # <https://www.EdWilliams.org/avform.htm#Math> t1, t2 = map1(acos1, (sa2 - sa1 * cr12) / x1, (sa1 - sa2 * cr12) / x2) if sin(db) > 0: t12, t21 = t1, PI2 - t2 else: t12, t21 = PI2 - t1, t2 t13, t23 = map1(radiansPI2, end1, end2) x1, x2 = map1( wrapPI, t13 - t12, # angle 2-1-3 t21 - t23) # angle 1-2-3 sx1, cx1, sx2, cx2 = sincos2(x1, x2) if sx1 == 0 and sx2 == 0: # max(abs(sx1), abs(sx2)) < EPS raise ValueError('intersection %s: %r vs %r' % ('infinite', (start1, end1), (start2, end2))) sx3 = sx1 * sx2 # if sx3 < 0: # raise ValueError('intersection %s: %r vs %r' % ('ambiguous', # (start1, end1), (start2, end2))) x3 = acos1(cr12 * sx3 - cx2 * cx1) r13 = atan2(sr12 * sx3, cx2 + cx1 * cos(x3)) a, b = _destination2(a1, b1, r13, t13) # choose antipode for opposing bearings if _xb(a1, b1, end1, a, b, wrap) < 0 or \ _xb(a2, b2, end2, a, b, wrap) < 0: a, b = antipode_(a, b) # PYCHOK PhiLam2Tuple else: # end point(s) or bearing(s) x1, d1 = _x3d2(start1, end1, wrap, '1', hs) x2, d2 = _x3d2(start2, end2, wrap, '2', hs) x = x1.cross(x2) if x.length < EPS: # [nearly] colinear or parallel paths raise ValueError('intersection %s: %r vs %r' % ('colinear', (start1, end1), (start2, end2))) a, b = x.philam # choose intersection similar to sphericalNvector d1 = _xdot(d1, a1, b1, a, b, wrap) if d1: d2 = _xdot(d2, a2, b2, a, b, wrap) if (d2 < 0 and d1 > 0) or (d2 > 0 and d1 < 0): a, b = antipode_(a, b) # PYCHOK PhiLam2Tuple h = fmean(hs) if height is None else Height(height) return _latlon3(degrees90(a), degrees180(b), h, intersection, LatLon, **LatLon_kwds)