def toLatLon( self, **LatLon_height_datum_kwds): # PYCHOK height=None, LatLon=LatLon '''Convert this n-vector to an C{Nvector}-based geodetic point. @kwarg LatLon_height_datum_kwds: Optional L{LatLon}, B{C{height}}, B{C{datum}} and other keyword arguments, ignored if C{B{LatLon}=None}. Use B{C{LatLon=...}} to override this L{LatLon} class or set C{B{LatLon}=None}. @return: The geodetic point (L{LatLon}) or a L{LatLon3Tuple}C{(lat, lon, height)} if B{C{LatLon}} is C{None}. @raise TypeError: Invalid B{C{LatLon}}, B{C{height}}, B{C{datum}} or other B{C{LatLon_height_datum_kwds}}. @example: >>> v = Nvector(0.5, 0.5, 0.7071) >>> p = v.toLatLon() # 45.0°N, 45.0°E ''' kwds = _xkwds(LatLon_height_datum_kwds, height=self.h, LatLon=LatLon, datum=self.datum) return NvectorBase.toLatLon(self, **kwds) # class or .classof
def toLatLon(self, datum=None, LatLon=None, **LatLon_kwds): # see .ecef.Ecef9Tuple.convertDatum '''Convert this cartesian to a geodetic (lat-/longitude) point. @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}). @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 C{B{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}}. ''' d = self.datum if datum in (None, d): r = self.toEcef() else: c = self.convertDatum(datum) d = c.datum r = c.Ecef(d).reverse(c, M=True) if LatLon is not None: # class or .classof kwds = _xkwds(LatLon_kwds, datum=r.datum, height=r.height) r = LatLon(r.lat, r.lon, **kwds) _datum_datum(r.datum, d) return self._xnamed(r)
def toNvector(self, **Nvector_datum_kwds): # PYCHOK Datums.WGS84 '''Convert this cartesian to L{Nvector} components, I{including height}. @kwarg Nvector_datum_kwds: Optional L{Nvector}, B{C{datum}} and other keyword arguments, ignored if B{C{Nvector=None}}. Use B{C{Nvector=...}} to override this L{Nvector} class or specify B{C{Nvector=None}}. @return: The C{n-vector} components (L{Nvector}) or a L{Vector4Tuple}C{(x, y, z, h)} if B{C{Nvector=None}}. @raise TypeError: Invalid B{C{Nvector}}, B{C{datum}} or other B{C{Nvector_datum_kwds}}. @example: >>> from ellipsoidalNvector import LatLon >>> c = Cartesian(3980581, 97, 4966825) >>> n = c.toNvector() # (0.62282, 0.000002, 0.78237, +0.24) ''' kwds = _xkwds(Nvector_datum_kwds, Nvector=Nvector, datum=self.datum) return CartesianEllipsoidalBase.toNvector(self, **kwds)
def toLatLon(self, LatLon, datum=None, **LatLon_kwds): '''Convert this WM coordinate to a geodetic point. @arg LatLon: Ellipsoidal class to return the geodetic point (C{LatLon}). @kwarg datum: Optional datum for ellipsoidal or C{None} for spherical B{C{LatLon}} (C{Datum}). @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments. @return: Point of this WM coordinate (B{C{LatLon}}). @raise TypeError: If B{C{LatLon}} and B{C{datum}} are incompatible or if B{C{datum}} is invalid or not ellipsoidal. @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 ''' e = issubclassof(LatLon, _LLEB) if e and datum: kwds = _xkwds(LatLon_kwds, datum=datum) elif not (e or datum): # and LatLon kwds = LatLon_kwds datum = None else: raise _TypeError(LatLon=LatLon, datum=datum) r = self.latlon2(datum=datum) r = LatLon(r.lat, r.lon, **kwds) return self._xnamed(r)
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 reverse(self, easting, northing, LatLon=None, **LatLon_kwds): '''Convert a Cassini-Soldner location to (ellipsoidal) geodetic lat- and longitude. @arg easting: Easting of the location (C{meter}). @arg northing: Northing of the location (C{meter}). @kwarg LatLon: Optional, ellipsoidal class to return the geodetic location as (C{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional (C{LatLon}) keyword arguments, ignored if C{B{LatLon}=None}. @return: Geodetic location B{C{LatLon}} or if B{C{LatLon}} is C{None}, a L{LatLon2Tuple}C{(lat, lon)}. @raise CSSError: Ellipsoidal mismatch of B{C{LatLon}} and this projection. @raise TypeError: Invalid B{C{LatLon}} or B{C{LatLon_kwds}}. @see: Method L{CassiniSoldner.reverse4}, L{CassiniSoldner.forward} and L{CassiniSoldner.forward4}. ''' r = self.reverse4(easting, northing) if LatLon is None: r = LatLon2Tuple(r.lat, r.lon) # PYCHOK expected else: _xsubclassof(_LLEB, LatLon=LatLon) kwds = _xkwds(LatLon_kwds, datum=self.datum) r = LatLon(r.lat, r.lon, **kwds) # PYCHOK expected self._datumatch(r) return self._xnamed(r)
def toCartesian(self, **Cartesian_h_datum_kwds): # PYCHOK Cartesian=Cartesian '''Convert this n-vector to C{Nvector}-based cartesian (ECEF) coordinates. @kwarg Cartesian_h_datum_kwds: Optional L{Cartesian}, B{C{h}}, B{C{datum}} and other keyword arguments, ignored if B{C{Cartesian=None}}. Use B{C{Cartesian=...}} to override this L{Cartesian} class or specify B{C{Cartesian=None}}. @return: The cartesian point (L{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}}, B{C{h}}, B{C{datum}} or other B{C{Cartesian_h_datum_kwds}}. @example: >>> v = Nvector(0.5, 0.5, 0.7071) >>> c = v.toCartesian() # [3194434, 3194434, 4487327] >>> p = c.toLatLon() # 45.0°N, 45.0°E ''' kwds = _xkwds(Cartesian_h_datum_kwds, h=self.h, Cartesian=Cartesian, datum=self.datum) return NvectorBase.toCartesian(self, **kwds) # class or .classof
def toNvector(self, **Nvector_datum_kwds): # PYCHOK signature '''Convert this point to L{Nvector} components, I{including height}. @kwarg Nvector_datum_kwds: Optional L{Nvector}, B{C{datum}} or other keyword arguments, ignored if B{C{Nvector=None}}. Use B{C{Nvector=...}} to override this L{Nvector} class or specify B{C{Nvector=None}}. @return: The C{n-vector} components (L{Nvector}) or a L{Vector4Tuple}C{(x, y, z, h)} if B{C{Nvector}} is C{None}. @raise TypeError: Invalid B{C{Nvector}}, B{C{datum}} or other B{C{Nvector_datum_kwds}}. @example: >>> p = LatLon(45, 45) >>> n = p.toNvector() >>> n.toStr() # [0.50, 0.50, 0.70710] ''' kwds = _xkwds(Nvector_datum_kwds, Nvector=Nvector, datum=self.datum) return LatLonNvectorBase.toNvector(self, **kwds)
def _V_n(v, name, Vector, Vector_kwds): # return a named Vector instance if Vector is None: v = _xnamed(v, name) else: kwds = _xkwds(Vector_kwds, name=name) v = Vector(v.x, v.y, v.z, **kwds) return v
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 _LatLon4Tuple(lat, lon, height, datum, LatLon, LatLon_kwds): '''(INTERNAL) Return a L{LatLon4Tuple} or an B{C{LatLon}} instance. ''' if LatLon is None: r = LatLon4Tuple(lat, lon, height, datum) else: kwds = _xkwds(LatLon_kwds, datum=datum, height=height) r = LatLon(lat, lon, **kwds) return r
def __init__(self, *args, **kwds): if args: # args override kwds if len(args) != 1: t = unstr(self.classname, *args, **kwds) raise _ValueError(args=len(args), txt=t) kwds = _xkwds(dict(args[0]), **kwds) if _name_ in kwds: _Named.name.fset(self, kwds.pop(_name_)) # see _Named.name dict.__init__(self, kwds)
def _latlon5(self, LatLon, **LatLon_kwds): '''(INTERNAL) Convert cached LatLon ''' ll = self._latlon if LatLon is None: r = LatLonDatum5Tuple(ll.lat, ll.lon, ll.datum, ll.convergence, ll.scale) else: _xsubclassof(_LLEB, LatLon=LatLon) kwds = _xkwds(LatLon_kwds, datum=ll.datum) r = _xattrs(LatLon(ll.lat, ll.lon, **kwds), ll, '_convergence', '_scale') return _xnamed(r, ll.name)
def toWm(latlon, lon=None, radius=R_MA, Wm=Wm, name=NN, **Wm_kwds): '''Convert a lat-/longitude point to a WM coordinate. @arg latlon: Latitude (C{degrees}) or an (ellipsoidal or spherical) geodetic C{LatLon} point. @kwarg lon: Optional longitude (C{degrees} or C{None}). @kwarg radius: Optional earth radius (C{meter}). @kwarg Wm: Optional class to return the WM coordinate (L{Wm}) or C{None}. @kwarg name: Optional name (C{str}). @kwarg Wm_kwds: Optional, additional B{C{Wm}} keyword arguments, ignored if B{C{Wm=None}}. @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 ''' e, r = None, Radius(radius) 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 = clipDegrees(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 = r * radians(lon) n = r * y r = EasNorRadius3Tuple(e, n, r) if Wm is None else \ Wm(e, n, **_xkwds(Wm_kwds, radius=r)) return _xnamed(r, name)
def toLatLon(self, **LatLon_datum_kwds): # PYCHOK LatLon=LatLon '''Convert this cartesian to an C{Nvector}-based geodetic point. @kwarg LatLon_datum_kwds: Optional L{LatLon}, B{C{datum}} and other keyword arguments, ignored if C{B{LatLon}=None}. Use B{C{LatLon=...}} to override the L{LatLon} class or specify C{B{LatLon}=None}. @return: The geodetic point (L{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{LatLon_datum_kwds}}. ''' kwds = _xkwds(LatLon_datum_kwds, LatLon=LatLon, datum=self.datum) return CartesianSphericalBase.toLatLon(self, **kwds)
def toMgrs(utm, Mgrs=Mgrs, name=NN, **Mgrs_kwds): '''Convert a UTM coordinate to an MGRS grid reference. @arg utm: A UTM coordinate (L{Utm} or L{Etm}). @kwarg Mgrs: Optional class to return the MGRS grid reference (L{Mgrs}) or C{None}. @kwarg name: Optional B{C{Mgrs}} name (C{str}). @kwarg Mgrs_kwds: Optional, additional B{C{Mgrs}} keyword arguments, ignored if B{C{Mgrs=None}}. @return: The MGRS grid reference (B{L{Mgrs}}) or an L{Mgrs6Tuple}C{(zone, digraph, easting, northing, band, datum)} if B{L{Mgrs}} is C{None}. @raise TypeError: If B{C{utm}} is not L{Utm} nor L{Etm}. @raise MGRSError: Invalid B{C{utm}}. @example: >>> u = Utm(31, 'N', 448251, 5411932) >>> m = u.toMgrs() # 31U DQ 48251 11932 ''' _xinstanceof(Utm, utm=utm) # Utm, Etm e, n = utm.eastingnorthing2(falsed=True) # truncate east-/northing to within 100 km grid square # XXX add rounding to nm precision? E, e = divmod(e, _100km) N, n = divmod(n, _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])]) if Mgrs is None: r = Mgrs4Tuple(utm.zone, en, e, n).to6Tuple(utm.band, utm.datum) else: kwds = _xkwds(Mgrs_kwds, band=utm.band, datum=utm.datum) r = Mgrs(utm.zone, en, e, n, **kwds) return _xnamed(r, name or utm.name)
def toCartesian(self, **Cartesian_h_kwds): # PYCHOK Cartesian=Cartesian '''Convert this n-vector to C{Nvector}-based cartesian (ECEF) coordinates. @kwarg Cartesian_h_kwds: Optional L{Cartesian}, B{C{h}} and other keyword arguments, ignored if B{C{Cartesian=None}}. Use B{C{Cartesian=...}} to override this L{Cartesian} class or specify B{C{Cartesian=None}}. @return: The cartesian point (L{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}}, B{C{h}} or other B{C{Cartesian_h_kwds}}. ''' kwds = _xkwds(Cartesian_h_kwds, h=self.h, Cartesian=Cartesian) return NvectorBase.toCartesian(self, **kwds) # class or .classof
def toLatLon(self, **LatLon_height_kwds): # PYCHOK height=None, LatLon=LatLon '''Convert this n-vector to an C{Nvector}-based geodetic point. @kwarg LatLon_height_kwds: Optional L{LatLon}, B{C{height}} and other keyword arguments, ignored if C{B{LatLon}=None}. Use B{C{LatLon=...}} to override this L{LatLon} class or specify C{B{LatLon}=None}. @return: The geodetic point (L{LatLon}) or if B{C{LatLon}} is C{None}, a L{LatLon3Tuple}C{(lat, lon, height)}. @raise TypeError: Invalid B{C{LatLon}}, B{C{height}} or other B{C{LatLon_height_kwds}}. @raise ValueError: Invalid B{C{height}}. ''' kwds = _xkwds(LatLon_height_kwds, height=self.h, LatLon=LatLon) return NvectorBase.toLatLon(self, **kwds) # class or .classof
def toNvector(self, **Nvector_datum_kwds): # PYCHOK Datums.WGS84 '''Convert this cartesian to L{Nvector} components, I{including height}. @kwarg Nvector_datum_kwds: Optional L{Nvector}, B{C{datum}} and other keyword arguments, ignored if B{C{Nvector=None}}. Use B{C{Nvector=...}} to override the L{Nvector} class or specify B{C{Nvector=None}}. @return: The C{n-vector} components (L{Nvector}) or a L{Vector4Tuple}C{(x, y, z, h)} if B{C{Nvector}} is C{None}. @raise TypeError: Invalid B{C{Nvector_datum_kwds}}. ''' # ll = CartesianBase.toLatLon(self, LatLon=LatLon, # datum=datum or self.datum) # kwds = _xkwds(kwds, Nvector=Nvector) # return ll.toNvector(**kwds) kwds = _xkwds(Nvector_datum_kwds, Nvector=Nvector, datum=self.datum) return CartesianSphericalBase.toNvector(self, **kwds)
def toCartesian( self, **Cartesian_datum_kwds): # PYCHOK Cartesian=Cartesian, datum=None '''Convert this point to C{Karney}-based cartesian (ECEF) coordinates. @kwarg Cartesian_datum_kwds: Optional L{Cartesian}, B{C{datum}} and other keyword arguments, ignored if B{C{Cartesian=None}}. Use B{C{Cartesian=...}} to override this L{Cartesian} class or set B{C{Cartesian=None}}. @return: The cartesian (ECEF) coordinates (L{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}}, B{C{datum}} or other B{C{Cartesian_datum_kwds}}. ''' kwds = _xkwds(Cartesian_datum_kwds, Cartesian=Cartesian, datum=self.datum) return LatLonEllipsoidalBase.toCartesian(self, **kwds)
def meanOf(points, height=None, LatLon=LatLon, **LatLon_kwds): '''Compute the geographic mean of the supplied points. @arg points: Array of points to be averaged (L{LatLon}[]). @kwarg height: Optional height, overriding the mean height (C{meter}). @kwarg LatLon: Optional class to return the mean point (L{LatLon}). @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if C{B{LatLon}=None}. @return: Point at geographic mean and mean height (B{C{LatLon}}). @raise PointsError: Insufficient number of B{C{points}}. @raise TypeError: Some B{C{points}} are not C{LatLon}. ''' n, points = _Nvll.points2(points, closed=False) # geographic mean m = sumOf(points[i]._N_vector for i in range(n)) kwds = _xkwds(LatLon_kwds, height=height, LatLon=LatLon) return m.toLatLon(**kwds)
def meanOf(points, datum=Datums.WGS84, height=None, LatLon=LatLon, **LatLon_kwds): '''Compute the geographic mean of several points. @arg points: Points to be averaged (L{LatLon}[]). @kwarg datum: Optional datum to use (L{Datum}). @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 C{B{LatLon}=None}. @return: Geographic mean point and mean height (B{C{LatLon}}) or a L{LatLon3Tuple}C{(lat, lon, height)} if B{C{LatLon}} is C{None}. @raise ValueError: Insufficient number of B{C{points}}. ''' _, points = _Nvll.points2(points, closed=False) # geographic mean m = sumOf(p._N_vector for p in points) lat, lon, h = m._N_vector.latlonheight if height is not None: h = height if LatLon is None: r = LatLon3Tuple(lat, lon, h) else: kwds = _xkwds(LatLon_kwds, height=h, datum=datum) r = LatLon(lat, lon, **kwds) return _xnamed(r, meanOf.__name__)
def reverse(self, x, y, lon0=0, name=NN, LatLon=None, **LatLon_kwds): '''Convert an east- and northing location to geodetic lat- and longitude. @arg x: Easting of the location (C{meter}). @arg y: Northing of the location (C{meter}). @kwarg lon0: Optional central meridian longitude (C{degrees}). @kwarg name: Optional name for the location (C{str}). @kwarg LatLon: Class to use (C{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if C{B{LatLon}=None}. @return: The geodetic (C{LatLon}) or if B{C{LatLon}} is C{None} an L{Albers7Tuple}C{(x, y, lat, lon, gamma, scale, datum)}. @note: The origin latitude is returned by C{property lat0}. No false easting or northing is added. The returned value of C{lon} is in the range C{[-180..180] degrees} and C{lat} is in the range C{[-90..90] degrees}. If the given B{C{x}} or B{C{y}} point is outside the valid projected space the nearest pole is returned. ''' x = Meter(x=x) y = Meter(y=y) k0 = self._k0 n0 = self._n0 k0n0 = self._k0n0 nrho0 = self._nrho0 txi0 = self._txi0 y_ = self._sign * y nx = k0n0 * x ny = k0n0 * y_ y1 = nrho0 - ny drho = den = nrho0 + hypot(nx, y1) # 0 implies origin with polar aspect if den: drho = fsum_(x * nx, -_2_0 * y_ * nrho0, y_ * ny) * k0 / den # dsxia = scxi0 * dsxi dsxia = -self._scxi0 * (_2_0 * nrho0 + n0 * drho) * drho / self._qZa2 t = _1_0 - dsxia * (_2_0 * txi0 + dsxia) txi = (txi0 + dsxia) / (sqrt(t) if t > _EPS__4 else _EPS__2) ta = self._tanf(txi) lat = atand(ta * self._sign) th = atan2(nx, y1) lon = degrees(th / self._k02n0 if n0 else x / (y1 * k0)) if lon0: lon += _norm180(lon0) lon = _norm180(lon) if LatLon is None: g = degrees360(self._sign * th) if den: k0 = self._azik(nrho0 + n0 * drho, ta) r = Albers7Tuple(x, y, lat, lon, g, k0, self.datum) else: kwds = _xkwds(LatLon_kwds, datum=self.datum) r = LatLon(lat, lon, **kwds) return self._xnamed(r, name=name)
def _trilaterate(point1, distance1, point2, distance2, point3, distance3, radius=R_M, height=None, useZ=False, **LatLon_LatLon_kwds): # (INTERNAL) Locate a point at given distances from # three other points, see LatLon.triangulate above def _nd2(p, d, r, _i_, *qs): # .toNvector and angular distance squared for q in qs: if p.isequalTo(q, EPS): raise _ValueError(points=p, txt=_coincident_) return p.toNvector(), (Scalar(d, name=_distance_ + _i_) / r)**2 r = Radius_(radius) n1, r12 = _nd2(point1, distance1, r, _1_) n2, r22 = _nd2(point2, distance2, r, _2_, point1) n3, r32 = _nd2(point3, distance3, r, _3_, point1, point2) # the following uses x,y coordinate system with origin at n1, x axis n1->n2 y = n3.minus(n1) x = n2.minus(n1) z = None d = x.length # distance n1->n2 if d > EPS_2: # and y.length > EPS_2: X = x.unit() # unit vector in x direction n1->n2 i = X.dot(y) # signed magnitude of x component of n1->n3 Y = y.minus(X.times(i)).unit() # unit vector in y direction j = Y.dot(y) # signed magnitude of y component of n1->n3 if abs(j) > EPS_2: # courtesy Carlos Freitas <https://GitHub.com/mrJean1/PyGeodesy/issues/33> x = fsum_(r12, -r22, d**2) / (2 * d) # n1->intersection x- and ... y = fsum_(r12, -r32, i**2, j**2, -2 * x * i) / ( 2 * j) # ... y-component # courtesy AleixDev <https://GitHub.com/mrJean1/PyGeodesy/issues/43> z = fsum_(max(r12, r22, r32), -(x**2), -(y**2)) # XXX not just r12! if z > EPS: n = n1.plus(X.times(x)).plus(Y.times(y)) if useZ: # include Z component Z = X.cross(Y) # unit vector perpendicular to plane n = n.plus(Z.times(sqrt(z))) if height is None: h = fidw((point1.height, point2.height, point3.height), map1(fabs, distance1, distance2, distance3)) else: h = Height(height) kwds = _xkwds(LatLon_LatLon_kwds, height=h) return n.toLatLon(** kwds) # Nvector(n.x, n.y, n.z).toLatLon(...) # no intersection, d < EPS_2 or abs(j) < EPS_2 or z < EPS t = NN(_no_, _intersection_, _SPACE_) raise IntersectionError(point1=point1, distance1=distance1, point2=point2, distance2=distance2, point3=point3, distance3=distance3, txt=unstr(t, z=z, useZ=useZ))
def intersection(start1, end1, start2, end2, height=None, LatLon=LatLon, **LatLon_kwds): '''Locate the intersection of two paths each defined by two points or by a start point and an initial bearing. @arg start1: Start point of the first path (L{LatLon}). @arg end1: End point of the 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 at the intersection point, overriding the mean height (C{meter}). @kwarg LatLon: Optional class to return the intersection point (L{LatLon}). @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if C{B{LatLon}=None}. @return: The intersection point (B{C{LatLon}}) or 3-tuple (C{degrees90}, C{degrees180}, height) if B{C{LatLon}} is C{None} or C{None} if no unique intersection exists. @raise TypeError: If B{C{start*}} or B{C{end*}} is not L{LatLon}. @raise ValueError: Intersection is ambiguous or infinite or the paths are parallel, coincident or null. @example: >>> p = LatLon(51.8853, 0.2545) >>> q = LatLon(49.0034, 2.5735) >>> i = intersection(p, 108.55, q, 32.44) # 50.9076°N, 004.5086°E ''' _Nvll.others(start1=start1) _Nvll.others(start2=start2) # If gc1 and gc2 are great circles through start and end points # (or defined by start point and bearing), then the candidate # intersections are simply gc1 × gc2 and gc2 × gc1. Most of the # work is deciding the correct intersection point to select! If # bearing is given, that determines the intersection, but if both # paths are defined by start/end points, take closer intersection. gc1, s1, e1 = _Nvll._gc3(start1, end1, 'end1') gc2, s2, e2 = _Nvll._gc3(start2, end2, 'end2') hs = start1.height, start2.height # there are two (antipodal) candidate intersection # points ... we have to choose the one to return i1 = gc1.cross(gc2, raiser=_paths_) # postpone computing i2 until needed # i2 = gc2.cross(gc1, raiser=_paths_) # selection of intersection point depends on how # paths are defined (by bearings or endpoints) if e1 and e2: # endpoint+endpoint d = sumOf((s1, s2, e1, e2)).dot(i1) hs += end1.height, end2.height elif e1 and not e2: # endpoint+bearing # gc2 x v2 . i1 +ve means v2 bearing points to i1 d = gc2.cross(s2).dot(i1) hs += end1.height, elif e2 and not e1: # bearing+endpoint # gc1 x v1 . i1 +ve means v1 bearing points to i1 d = gc1.cross(s1).dot(i1) hs += end2.height, else: # bearing+bearing # if gc x v . i1 is +ve, initial bearing is # towards i1, otherwise towards antipodal i2 d1 = gc1.cross(s1).dot(i1) # +ve means p1 bearing points to i1 d2 = gc2.cross(s2).dot(i1) # +ve means p2 bearing points to i1 if d1 > 0 and d2 > 0: d = 1 # both point to i1 elif d1 < 0 and d2 < 0: d = -1 # both point to i2 else: # d1, d2 opposite signs # intersection is at further-away intersection # point, take opposite intersection from mid- # point of v1 and v2 [is this always true?] d = neg(s1.plus(s2).dot(i1)) i = i1 if d > 0 else gc2.cross(gc1, raiser=_paths_) h = fmean(hs) if height is None else height kwds = _xkwds(LatLon_kwds, height=h, LatLon=LatLon) return i.toLatLon(**kwds) # Nvector(i.x, i.y, i.z).toLatLon(...)