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. @param lat1: From latitude (C{degrees}). @param lon1: From longitude (C{degrees}). @param lat2: To latitude (C{degrees}). @param lon2: To longitude (C{degrees}). @keyword adjust: Adjust the longitudinal delta by the cosine of the mean latitude (C{bool}). @keyword 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 <http://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 _ 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('%s, %r' % ('no convergence', self)) 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 r = self.classof(a, b, height=h, datum=self.datum), r return r
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 nearestOn4(point, points, closed=False, wrap=False, **options): '''Locate the point on a path or polygon closest to an other point. If the given point is within the extent of a polygon edge, the closest point is on that edge, otherwise the closest point is the nearest of that edge's end points. Distances are approximated by function L{equirectangular_}, subject to the supplied I{options}. @param point: The other, reference point (C{LatLon}). @param points: The path or polygon points (C{LatLon}[]). @keyword closed: Optionally, close the path or polygon (C{bool}). @keyword wrap: Wrap and L{unroll180} longitudes and longitudinal delta (C{bool}) in function L{equirectangular_}. @keyword options: Other keyword arguments for function L{equirectangular_}. @return: 4-Tuple (lat, lon, I{distance}, I{angle}) all in C{degrees}. The I{distance} is the L{equirectangular_} distance between the closest and the reference I{point} in C{degrees}. The I{angle} from the reference I{point} to the closest point is in compass C{degrees360}, like function L{compassAngle}. @raise LimitError: Lat- and/or longitudinal delta exceeds I{limit}, see function L{equirectangular_}. @raise TypeError: Some I{points} are not C{LatLon}. @raise ValueError: Insufficient number of I{points}. @see: Function L{nearestOn3}. Use function L{degrees2m} to convert C{degrees} to C{meter}. ''' n, points = points2(points, closed=closed) def _d2yx(p2, p1, u, i): w = wrap if (not closed or i < (n - 1)) else False # equirectangular_ returns a 4-tuple (distance in # degrees squared, delta lat, delta lon, p2.lon # unroll/wrap); the previous p2.lon unroll/wrap # is also applied to the next edge's p1.lon return equirectangular_(p1.lat, p1.lon + u, p2.lat, p2.lon, wrap=w, **options) # point (x, y) on axis rotated ccw by angle a: # x' = y * sin(a) + x * cos(a) # y' = y * cos(a) - x * sin(a) # # distance (w) along and perpendicular (h) to # a line thru point (dx, dy) and the origin: # w = (y * dy + x * dx) / hypot(dx, dy) # h = (y * dx - x * dy) / hypot(dx, dy) # # closest point on that line thru (dx, dy): # xc = dx * w / hypot(dx, dy) # yc = dy * w / hypot(dx, dy) # or # xc = dx * f # yc = dy * f # with # f = w / hypot(dx, dy) # or # f = (y * dy + x * dx) / (dx**2 + dy**2) i, m = _imdex2(closed, n) p2 = c = points[i] u2 = u = 0 d, dy, dx, _ = _d2yx(p2, point, u2, i) for i in range(m, n): p1, u1, p2 = p2, u2, points[i] # iff wrapped, unroll lon1 (actually previous # lon2) like function unroll180/-PI would've d21, y21, x21, u2 = _d2yx(p2, p1, u1, i) if d21 > EPS: # distance point to p1, y01 and x01 inverted d2, y01, x01, _ = _d2yx(point, p1, u1, n) if d2 > EPS: w2 = y01 * y21 + x01 * x21 if w2 > 0: if w2 < d21: # closest is between p1 and p2, use # original delta's, not y21 and x21 f = w2 / d21 p1 = LatLon_(favg(p1.lat, p2.lat, f=f), favg(p1.lon, p2.lon + u2, f=f)) u1 = 0 else: # p2 is closest p1, u1 = p2, u2 d2, y01, x01, _ = _d2yx(point, p1, u1, n) if d2 < d: # p1 is closer, y01 and x01 inverted c, u, d, dy, dx = p1, u1, d2, -y01, -x01 return c.lat, c.lon + u, hypot(dx, dy), degrees360(atan2(dx, dy))
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 other point is not L{LatLon}. @raise ValueError: If this and the I{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 I{other} point are near-antipodal or coincide. ''' 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): cll, sll, ll_ = cos(ll), sin(ll), ll ss = hypot(c2 * sll, c1s2 - s1c2 * cll) if ss < EPS: # coincident, ... 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 # <http://GitHub.com/ChrisVeness/geodesy/blob/master/latlon-vincenty.js> # omitted and applied only after failure to converge, see footnote under # Inverse at <http://WikiPedia.org/wiki/Vincenty's_formulae> # elif abs(ll) > PI and self.isantipodeTo(other, eps=self._epsilon): # raise VincentyError('%r antipodal to %r' % (self, other)) else: t = 'antipodal ' if self.isantipodeTo(other, eps=self._epsilon) else '' raise VincentyError('no convergence, %r %sto %r' % (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 cll, sll = cos(ll), sin(ll) f = degrees360(atan2(c2 * sll, c1s2 - s1c2 * cll)) r = degrees360(atan2(c1 * sll, -s1c2 + c1s2 * cll)) d = d, f, r return d