def initialBearingTo(self, other, **unused): '''Compute the initial bearing (forward azimuth) from this to an other point. @param other: The other point (L{LatLon}). @param unused: Optional keyword argument B{C{wrap}} ignored. @return: Initial bearing (compass C{degrees360}). @raise Crosserror: This point coincides with the B{C{other}} point or the C{NorthPole}, provided L{crosserrors} is C{True}. @raise TypeError: The B{C{other}} point is not L{LatLon}. @example: >>> p1 = LatLon(52.205, 0.119) >>> p2 = LatLon(48.857, 2.351) >>> b = p1.initialBearingTo(p2) # 156.2 @JSname: I{bearingTo}. ''' self.others(other, name='other') # see <https://MathForum.org/library/drmath/view/55417.html> n = self.toNvector() # gc1 = self.greatCircleTo(other) gc1 = n.cross(other.toNvector(), raiser='points') # .unit() # gc2 = self.greatCircleTo(NorthPole) gc2 = n.cross(NorthPole, raiser='pole') # .unit() return degrees360(gc1.angleTo(gc2, vSign=n))
def compassAngle(lat1, lon1, lat2, lon2, adjust=True, wrap=False): '''Return the angle from North for the direction vector M{(lon2 - lon1, lat2 - lat1)} between two points. Suitable only for short, non-near-polar vectors up to a few hundred Km or Miles. Use function L{bearing} for longer vectors. @arg lat1: From latitude (C{degrees}). @arg lon1: From longitude (C{degrees}). @arg lat2: To latitude (C{degrees}). @arg lon2: To longitude (C{degrees}). @kwarg adjust: Adjust the longitudinal delta by the cosine of the mean latitude (C{bool}). @kwarg wrap: Wrap and L{unroll180} longitudes (C{bool}). @return: Compass angle from North (C{degrees360}). @note: Courtesy Martin Schultz. @see: U{Local, flat earth approximation <https://www.EdWilliams.org/avform.htm#flat>}. ''' d_lon, _ = unroll180(lon1, lon2, wrap=wrap) if adjust: # scale delta lon d_lon *= _scaled(lat1, lat2) return degrees360(atan2(d_lon, lat2 - lat1))
def _direct(self, distance, bearing, llr, height=None): '''(INTERNAL) Direct Vincenty method. @raise TypeError: The B{C{other}} point is not L{LatLon}. @raise ValueError: If this and the B{C{other}} point's L{Datum} ellipsoids are not compatible. @raise VincentyError: Vincenty fails to converge for the current L{LatLon.epsilon} and L{LatLon.iterations} limit. ''' E = self.ellipsoid() c1, s1, t1 = _r3(self.lat, E.f) i = radians(bearing) # initial bearing (forward azimuth) si, ci = sincos2(i) s12 = atan2(t1, ci) * 2 sa = c1 * si c2a = 1 - sa**2 if c2a < EPS: c2a = 0 A, B = 1, 0 else: # e22 == (a / b)**2 - 1 A, B = _p2(c2a * E.e22) s = d = distance / (E.b * A) for self._iteration in range(self._iterations): ss, cs = sincos2(s) c2sm = cos(s12 + s) s_, s = s, d + _ds(B, cs, ss, c2sm) if abs(s - s_) < self._epsilon: break else: raise VincentyError(_no_convergence_, txt=repr(self)) # self.toRepr() t = s1 * ss - c1 * cs * ci # final bearing (reverse azimuth +/- 180) r = degrees360(atan2(sa, -t)) if llr: # destination latitude in [-90, 90) a = degrees90( atan2(s1 * cs + c1 * ss * ci, (1 - E.f) * hypot(sa, t))) # destination longitude in [-180, 180) b = degrees180( atan2(ss * si, c1 * cs - s1 * ss * ci) - _dl(E.f, c2a, sa, s, cs, ss, c2sm) + radians(self.lon)) h = self.height if height is None else height d = self.classof(a, b, height=h, datum=self.datum) else: d = None return Destination2Tuple(d, r)
def forward(self, lat, lon, lon0=0, name=NN): '''Convert a geodetic location to east- and northing. @arg lat: Latitude of the location (C{degrees}). @arg lon: Longitude of the location (C{degrees}). @kwarg lon0: Optional central meridian longitude (C{degrees}). @kwarg name: Optional name for the location (C{str}). @return: 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 value of B{C{lat}} should be in the range C{[-90..90] degrees}. The returned values C{x} and C{y} will be large but finite for points projecting to infinity, i.e. one or both of the poles. ''' E = self.datum.ellipsoid s = self._sign k0 = self._k0 n0 = self._n0 nrho0 = self._nrho0 txi0 = self._txi0 sa, ca = sincos2d(_Lat_(lat) * s) ca = max(_EPSX, ca) ta = sa / ca _, sxi, txi = self._cstxif3(ta) dq = self._qZ * _Dsn(txi, txi0, sxi, self._sxi0) * (txi - txi0) drho = -E.a * dq / (sqrt(self._m02 - n0 * dq) + self._m0) lon = _Lon_(lon) if lon0: lon, _ = _diff182(_Lon_(lon0, name=_lon0_), lon) b = radians(lon) th = self._k02n0 * b sth, cth = sincos2(th) # XXX sin, cos if n0: x = sth / n0 y = (1 - cth if cth < 0 else sth**2 / (1 + cth)) / n0 else: x = self._k02 * b y = 0 t = nrho0 + n0 * drho x = t * x / k0 y = s * (nrho0 * y - drho * cth) / k0 g = degrees360(s * th) if t: k0 *= t * hypot1(E.b_a * ta) / E.a t = Albers7Tuple(x, y, lat, lon, g, k0, self.datum) return _xnamed(t, name or self.name)
def rhumbBearingTo(self, other): '''Return the initial bearing (forward azimuth) from this to an other point along a rhumb (loxodrome) line. @param other: The other point (spherical C{LatLon}). @return: Initial bearing (compass C{degrees360}). @raise TypeError: The I{other} point is not spherical. @example: >>> p = LatLon(51.127, 1.338) >>> q = LatLon(50.964, 1.853) >>> b = p.rhumbBearingTo(q) # 116.7 ''' _, db, dp = self._rhumb3(other) return degrees360(atan2(db, dp))
def nearestOn3(self, points, closed=False, radius=R_M, height=None): '''Locate the point on a polygon (with great circle arcs joining consecutive points) closest to this point. If this 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 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}), the C{distance} between this and the C{closest} point in C{meter}, same units as B{C{radius}} and the C{angle} from this to the C{closest} point in compass C{degrees360}. @raise TypeError: Some B{C{points}} are not C{LatLon}. @raise ValueError: No B{C{points}}. ''' n, points = self.points2(points, closed=closed) i, m = _imdex2(closed, n) c = p2 = points[i] r = self.distanceTo(c, radius=1) # force radians for i in range(m, n): p1, p2 = p2, points[i] p = self.nearestOn(p1, p2, height=height) t = self.distanceTo(p, radius=1) # force radians if t < r: c, r = p, t return NearestOn3Tuple(c, r * Radius(radius), degrees360(r))
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 bearing(self): '''Get the bearing of this NED vector (compass C{degrees360}). ''' if self._bearing is None: self._bearing = degrees360(atan2(self.east, self.north)) return self._bearing
def _inverse(self, other, azis, wrap): '''(INTERNAL) Inverse Vincenty method. @raise TypeError: The B{C{other}} point is not L{LatLon}. @raise ValueError: If this and the B{C{other}} point's L{Datum} ellipsoids are not compatible. @raise VincentyError: Vincenty fails to converge for the current L{LatLon.epsilon} and L{LatLon.iterations} limit and/or if this and the B{C{other}} point are coincident or near-antipodal. ''' E = self.ellipsoids(other) c1, s1, _ = _r3(self.lat, E.f) c2, s2, _ = _r3(other.lat, E.f) c1c2, s1c2 = c1 * c2, s1 * c2 c1s2, s1s2 = c1 * s2, s1 * s2 dl, _ = unroll180(self.lon, other.lon, wrap=wrap) ll = dl = radians(dl) for _ in range(self._iterations): ll_ = ll sll, cll = sincos2(ll) ss = hypot(c2 * sll, c1s2 - s1c2 * cll) if ss < EPS: # coincident or antipodal, ... if self.isantipodeTo(other, eps=self._epsilon): raise VincentyError('%s, %r %sto %r' % ('ambiguous', self, 'antipodal ', other)) d = 0.0 # like Karney, ... if azis: # return zeros d = d, 0, 0 return d cs = s1s2 + c1c2 * cll s = atan2(ss, cs) sa = c1c2 * sll / ss c2a = 1 - sa**2 if abs(c2a) < EPS: c2a = 0 # equatorial line ll = dl + E.f * sa * s else: c2sm = cs - 2 * s1s2 / c2a ll = dl + _dl(E.f, c2a, sa, s, cs, ss, c2sm) if abs(ll - ll_) < self._epsilon: break # # omitted and applied only after failure to converge below, see footnote # # under Inverse at <https://WikiPedia.org/wiki/Vincenty's_formulae> # # <https://GitHub.com/ChrisVeness/geodesy/blob/master/latlon-vincenty.js> # elif abs(ll) > PI and self.isantipodeTo(other, eps=self._epsilon): # raise VincentyError('%s, %r %sto %r' % ('ambiguous', self, # 'antipodal ', other)) else: t = 'antipodal ' if self.isantipodeTo(other, eps=self._epsilon) else '' raise VincentyError('%s, %r %sto %r' % ('no convergence', self, t, other)) if c2a: # e22 == (a / b)**2 - 1 A, B = _p2(c2a * E.e22) s = A * (s - _ds(B, cs, ss, c2sm)) b = E.b # if self.height or other.height: # b += self._havg(other) d = b * s if azis: # forward and reverse azimuth sll, cll = sincos2(ll) f = degrees360(atan2(c2 * sll, c1s2 - s1c2 * cll)) r = degrees360(atan2(c1 * sll, -s1c2 + c1s2 * cll)) d = d, f, r return d