def crossingParallels(self, other, lat, wrap=False): '''Return the pair of meridians at which a great circle defined by this and an other point crosses the given latitude. @param other: The other point defining great circle (L{LatLon}). @param lat: Latitude at the crossing (C{degrees}). @keyword wrap: Wrap and unroll longitudes (C{bool}). @return: 2-Tuple C{(lon1, lon2)}, both in C{degrees180} or C{None} if the great circle doesn't reach B{C{lat}}. ''' self.others(other) a1, b1 = self.to2ab() a2, b2 = other.to2ab() a = radians(lat) db, b2 = unrollPI(b1, b2, wrap=wrap) sa, ca, sa1, ca1, \ sa2, ca2, sdb, cdb = sincos2(a, a1, a2, db) x = sa1 * ca2 * ca * sdb y = sa1 * ca2 * ca * cdb - ca1 * sa2 * ca z = ca1 * ca2 * sa * sdb h = hypot(x, y) if h < EPS or abs(z) > h: return None # great circle doesn't reach latitude m = atan2(-y, x) + b1 # longitude at max latitude d = acos1(z / h) # delta longitude to intersections return degrees180(m - d), degrees180(m + d)
def distanceTo(self, other, radius=R_M, wrap=False): '''Compute the distance from this to an other point. @param other: The other point (L{LatLon}). @keyword radius: Mean earth radius (C{meter}). @keyword wrap: Wrap and unroll longitudes (C{bool}). @return: Distance between this and the B{C{other}} point (C{meter}, same units as B{C{radius}}). @raise TypeError: The B{C{other}} point is not L{LatLon}. @example: >>> p1 = LatLon(52.205, 0.119) >>> p2 = LatLon(48.857, 2.351); >>> d = p1.distanceTo(p2) # 404300 ''' self.others(other) a1, b1 = self.to2ab() a2, b2 = other.to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) r = haversine_(a2, a1, db) return r * float(radius)
def _x3d2(start, end, wrap, n, hs): # see <https://www.EdWilliams.org/intersect.htm> (5) ff a1, b1 = start.philam if isscalar(end): # bearing, make a point a2, b2 = _destination2(a1, b1, PI_4, radians(end)) else: # must be a point _Trll.others(end, name=_end_ + n) hs.append(end.height) a2, b2 = end.philam db, b2 = unrollPI(b1, b2, wrap=wrap) if max(abs(db), abs(a2 - a1)) < EPS: raise IntersectionError(start=start, end=end, txt='null path' + n) # note, in EdWilliams.org/avform.htm W is + and E is - b21, b12 = db * 0.5, -(b1 + b2) * 0.5 sb21, cb21, sb12, cb12, \ sa21, _, sa12, _ = sincos2(b21, b12, a1 - a2, a1 + a2) x = _Nvector(sa21 * sb12 * cb21 - sa12 * cb12 * sb21, sa21 * cb12 * cb21 + sa12 * sb12 * sb21, cos(a1) * cos(a2) * sin(db)) # ll=start return x.unit(), (db, (a2 - a1)) # negated d
def bearing_(phi1, lam1, phi2, lam2, final=False, wrap=False): '''Compute the initial or final bearing (forward or reverse azimuth) between a (spherical) start and end point. @arg phi1: Start latitude (C{radians}). @arg lam1: Start longitude (C{radians}). @arg phi2: End latitude (C{radians}). @arg lam2: End longitude (C{radians}). @kwarg final: Return final bearing if C{True}, initial otherwise (C{bool}). @kwarg wrap: Wrap and L{unrollPI} longitudes (C{bool}). @return: Initial or final bearing (compass C{radiansPI2}) or zero if start and end point coincide. ''' if final: phi1, lam1, phi2, lam2 = phi2, lam2, phi1, lam1 r = PI2 + PI else: r = PI2 db, _ = unrollPI(lam1, lam2, wrap=wrap) sa1, ca1, sa2, ca2, sdb, cdb = sincos2(phi1, phi2, db) # see <https://MathForum.org/library/drmath/view/55417.html> x = ca1 * sa2 - sa1 * ca2 * cdb y = sdb * ca2 return (atan2(y, x) + r) % PI2 # wrapPI2
def distance(self, p1, p2): '''Return the L{equirectangular_} distance in C{radians squared}. ''' d, _ = unrollPI(p1.b, p2.b, wrap=self._wrap) if self._adjust: d *= _scaler(p1.a, p2.a) return d**2 + (p2.a - p1.a)**2 # like equirectangular_ d2
def distanceTo(self, other, radius=R_M, wrap=False): '''Compute the distance from this to an other point. @arg other: The other point (L{LatLon}). @kwarg radius: Mean earth radius (C{meter}). @kwarg wrap: Wrap and unroll longitudes (C{bool}). @return: Distance between this and the B{C{other}} point (C{meter}, same units as B{C{radius}}). @raise TypeError: The B{C{other}} point is not L{LatLon}. @raise ValueError: Invalid B{C{radius}}. @example: >>> p1 = LatLon(52.205, 0.119) >>> p2 = LatLon(48.857, 2.351); >>> d = p1.distanceTo(p2) # 404300 ''' self.others(other) a1, b1 = self.philam a2, b2 = other.philam db, _ = unrollPI(b1, b2, wrap=wrap) r = vincentys_(a2, a1, db) return r * Radius(radius)
def distance(self, p1, p2): '''Return the L{equirectangular_} distance in C{radians squared}. ''' r, _ = unrollPI(p1.lam, p2.lam, wrap=self._wrap) if self._adjust: r *= _scale_rad(p1.phi, p2.phi) return hypot2(r, p2.phi - p1.phi) # like equirectangular_ d2
def _distanceTo_(self, func_, other, wrap=False): '''(INTERNAL) Helper for (ellipsoidal) methods C{<func>To}. ''' self.others(other) # up=2 r, _ = unrollPI(self.lam, other.lam, wrap=wrap) r = func_(other.phi, self.phi, r, datum=self.datum) return r * self.datum.ellipsoid.a
def _x3d2(start, end, wrap, n, hs): # see <https://www.EdWilliams.org/intersect.htm> (5) ff a1, b1 = start.to2ab() if isscalar(end): # bearing, make a point a2, b2 = _destination2_(a1, b1, PI_4, radians(end)) else: # must be a point _Trll.others(end, name='end' + n) hs.append(end.height) a2, b2 = end.to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) if max(abs(db), abs(a2 - a1)) < EPS: raise ValueError('intersection %s%s null: %r' % ('path', n, (start, end))) # note, in EdWilliams.org/avform.htm W is + and E is - b21, b12 = db * 0.5, -(b1 + b2) * 0.5 sb21, cb21, sb12, cb12, \ sa21, _, sa12, _ = sincos2(b21, b12, a1 - a2, a1 + a2) x = Vector3d(sa21 * sb12 * cb21 - sa12 * cb12 * sb21, sa21 * cb12 * cb21 + sa12 * sb12 * sb21, cos(a1) * cos(a2) * sin(db), ll=start) return x.unit(), (db, (a2 - a1)) # negated d
def _rads(n, points, closed): # angular edge lengths in radians i, m = _imdex2(closed, n) a1, b1 = points[i].to2ab() for i in range(m, n): a2, b2 = points[i].to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) yield haversine_(a2, a1, db) a1, b1 = a2, b2
def distance(self, p1, p2): '''Return the L{cosineForsytheAndoyerLambert_} distance in C{radians}. ''' r, _ = unrollPI(p1.lam, p2.lam, wrap=self._wrap) return cosineForsytheAndoyerLambert_(p2.phi, p1.phi, r, datum=self._datum)
def _rads(n, points, closed): # angular edge lengths in radians i, m = _imdex2(closed, n) a1, b1 = points[i].philam for i in range(m, n): a2, b2 = points[i].philam db, b2 = unrollPI(b1, b2, wrap=wrap) yield vincentys_(a2, a1, db) a1, b1 = a2, b2
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 _exs(n, points): # iterate over spherical edge excess a1, b1 = points[n-1].to2ab() ta1 = tan_2(a1) for i in range(n): a2, b2 = points[i].to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) ta2, tdb = map1(tan_2, a2, db) yield atan2(tdb * (ta1 + ta2), 1 + ta1 * ta2) ta1, b1 = ta2, b2
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 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 distance3To(self, other, radius=R_M, wrap=False): '''Compute the great-circle distance between this and an other geohash using the U{Haversine <https://www.Movable-Type.co.UK/scripts/latlong.html>} formula. @param other: The other geohash (L{Geohash}). @keyword radius: Optional, mean earth radius (C{meter}). @keyword wrap: Wrap and unroll longitudes (C{bool}). @return: Great-circle distance (C{meter}, same units as I{radius}). @raise TypeError: The I{other} is not a L{Geohash}, C{LatLon} or C{str}. ''' other = _2Geohash(other) a1, b1 = self.ab a2, b2 = other.ab db, _ = unrollPI(b1, b2, wrap=wrap) return haversine_(a2, a1, db) * float(radius)
def distance(self, p1, p2): '''Return the L{haversine_} distance in C{radians}. ''' r, _ = unrollPI(p1.lam, p2.lam, wrap=self._wrap) return haversine_(p2.phi, p1.phi, r)
def distance(self, p1, p2): '''Return the L{flatPolar_} distance in C{radians}. ''' r, _ = unrollPI(p1.lam, p2.lam, wrap=self._wrap) return flatPolar_(p2.phi, p1.phi, r)
def distance(self, p1, p2): '''Return the L{flatLocal_}/L{hubeny_} distance in C{radians squared}. ''' d, _ = unrollPI(p1.lam, p2.lam, wrap=self._wrap) return self._hubeny2_(p2.phi, p1.phi, d)
def distance(self, p1, p2): '''Return the L{cosineLaw_} distance in C{radians}. ''' d, _ = unrollPI(p1.lam, p2.lam, wrap=self._wrap) return cosineLaw_(p2.phi, p1.phi, d)
def distance(self, p1, p2): '''Return the L{vincentys_} distance in C{radians}. ''' d, _ = unrollPI(p1.b, p2.b, wrap=self._wrap) return vincentys_(p2.a, p1.a, d)
def _distances(self, x, y): # (x, y) radians**2 for xk, yk in zip(self._xs, self._ys): d, _ = unrollPI(xk, x, wrap=self._wrap) if self._adjust: d *= _scale_rad(yk, y) yield hypot2(d, yk - y) # like equirectangular_ distance2
def _distances_angular_datum_(self, func_, x, y): # return angular distances from func_ for xk, yk in zip(self._xs, self._ys): r, _ = unrollPI(xk, x, wrap=self._wrap) yield func_(yk, y, r, datum=self._datum)
def _distances(self, x, y): # (x, y) radians for xk, yk in zip(self._xs, self._ys): d, _ = unrollPI(xk, x, wrap=self._wrap) yield vincentys_(yk, y, d)
def _distances(self, x, y): # (x, y) radians for xk, yk in zip(self._xs, self._ys): d, _ = unrollPI(xk, x, wrap=self._wrap) if self._adjust: d *= _scaler(yk, y) yield d**2 + (yk - y)**2 # like equirectangular_ distance2
def intersection(start1, end1, start2, end2, height=None, wrap=False, LatLon=LatLon): '''Compute the intersection point of two paths both defined by two points or a start point and bearing from North. @param start1: Start point of the first path (L{LatLon}). @param end1: End point ofthe first path (L{LatLon}) or the initial bearing at the first start point (compass C{degrees360}). @param start2: Start point of the second path (L{LatLon}). @param end2: End point of the second path (L{LatLon}) or the initial bearing at the second start point (compass C{degrees360}). @keyword height: Optional height for the intersection point, overriding the mean height (C{meter}). @keyword wrap: Wrap and unroll longitudes (C{bool}). @keyword LatLon: Optional (sub-)class to return the intersection point (L{LatLon}) or C{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. @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.to2ab() a2, b2 = start2.to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) r12 = haversine_(a2, a1, db) if abs(r12) < EPS: # [nearly] coincident points a, b = map1(degrees, 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) 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.to2ll() # choose intersection similar to sphericalNvector d1 = _xdot(d1, a1, b1, a, b, wrap) d2 = _xdot(d2, a2, b2, a, b, wrap) if (d1 < 0 and d2 > 0) or (d1 > 0 and d2 < 0): a, b = antipode(a, b) h = fmean(hs) if height is None else height r = LatLon3Tuple(a, b, h) if LatLon is None else \ LatLon(a, b, height=h) return _xnamed(r, intersection.__name__)
def distance(self, p1, p2): '''Return the L{thomas_} distance in C{radians}. ''' r, _ = unrollPI(p1.lam, p2.lam, wrap=self._wrap) return thomas_(p2.phi, p1.phi, r, datum=self._datum)
def distance(self, p1, p2): '''Return the L{vincentys_} distance in C{radians}. ''' r, _ = unrollPI(p1.lam, p2.lam, wrap=self._wrap) return vincentys_(p2.phi, p1.phi, r)
def _xdot(d, a1, b1, a, b, wrap): # compute dot product d . (-b + b1, a - a1) db, _ = unrollPI(b1, radians(b), wrap=wrap) return fdot(d, db, radians(a) - a1)