def bearing_(a1, b1, a2, b2, final=False, wrap=False): '''Compute the initial or final bearing (forward or reverse azimuth) between a (spherical) start and end point. @param a1: Start latitude (C{radians}). @param b1: Start longitude (C{radians}). @param a2: End latitude (C{radians}). @param b2: End longitude (C{radians}). @keyword final: Return final bearing if C{True}, initial otherwise (C{bool}). @keyword 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: a1, b1, a2, b2 = a2, b2, a1, b1 r = PI + PI2 else: r = PI2 db, _ = unrollPI(b1, b2, wrap=wrap) ca1, ca2, cdb = map1(cos, a1, a2, db) sa1, sa2, sdb = map1(sin, a1, a2, db) # see <http://MathForum.org/library/drmath/view/55417.html> x = ca1 * sa2 - sa1 * ca2 * cdb y = sdb * ca2 return (atan2(y, x) + r) % PI2 # wrapPI2
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 (lon1, lon2) in (C{degrees180}) or C{None} if the great circle doesn't reach the given I{lat}. ''' self.others(other) a1, b1 = self.to2ab() a2, b2 = other.to2ab() a = radians(lat) db, b2 = unrollPI(b1, b2, wrap=wrap) ca, ca1, ca2, cdb = map1(cos, a, a1, a2, db) sa, sa1, sa2, sdb = map1(sin, 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 _x3d2(start, end, wrap, n): # see <http://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) 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 cb21, cb12 = map1(cos, b21, b12) sb21, sb12 = map1(sin, b21, b12) sa21, sa12 = map1(sin, 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 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: Optional, mean earth radius (C{meter}). @keyword wrap: Wrap and unroll longitudes (C{bool}). @return: Distance between this and the I{other} point (C{meter}, same units as I{radius}). @raise TypeError: The I{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 _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 _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 intermediateTo(self, other, fraction, height=None, wrap=False): '''Locate the point at given fraction between this and an other point. @param other: The other point (L{LatLon}). @param fraction: Fraction between both points (float, 0.0 = this point, 1.0 = the other point). @keyword height: Optional height, overriding the fractional height (C{meter}). @keyword wrap: Wrap and unroll longitudes (C{bool}). @return: Intermediate point (L{LatLon}). @raise TypeError: The I{other} point is not L{LatLon}. @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) a1, b1 = self.to2ab() a2, b2 = other.to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) r = haversine_(a2, a1, db) sr = sin(r) if abs(sr) > EPS: cb1, cb2, ca1, ca2 = map1(cos, b1, b2, a1, a2) sb1, sb2, sa1, sa2 = map1(sin, b1, b2, a1, a2) A = sin((1 - fraction) * r) / sr B = sin(fraction * 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=fraction) b = favg(b1, b2, f=fraction) if height is None: h = self._havg(other, f=fraction) else: h = height return self.classof(degrees90(a), degrees180(b), height=h)
def _rads2(n, pts): # trapezoidal areas in rads**2 x1, y1, _ = pts[n - 1] for i in range(n): x2, y2, _ = pts[i] w, x2 = unrollPI(x1, x2, wrap=wrap if i < (n - 1) else False) # approximate trapezoid by a rectangle, adjusting # the top width by the cosine of the latitudinal # average and bottom width by some fudge factor h = (y2 + y1) * 0.5 if adjust: w *= (cos(h) + 1.2876) * 0.5 yield h * w # signed trapezoidal area x1, y1 = x2, y2
def centroidOf(points, wrap=True, LatLon=None): '''Determine the centroid of a polygon. @param points: The polygon points (C{LatLon}[]). @keyword wrap: Wrap lat-, wrap and unroll longitudes (C{bool}). @keyword LatLon: Optional (sub-)class to return the centroid (L{LatLon}) or C{None}. @return: Centroid location (I{LatLon}) or as 2-tuple (C{lat, lon}) in C{degrees} if I{LatLon} is C{None}. @raise TypeError: Some I{points} are not C{LatLon}. @raise ValueError: Insufficient number of I{points} or I{points} enclose a pole or zero area. @see: U{Centroid<http://WikiPedia.org/wiki/Centroid#Of_a_polygon>} and U{Calculating The Area And Centroid Of A Polygon <http://www.Seas.UPenn.edu/~sys502/extra_materials/ Polygon%20Area%20and%20Centroid.pdf>}. ''' # setting radius=1 converts degrees to radians pts = LatLon2psxy(points, closed=True, radius=1, wrap=wrap) n = len(pts) A, X, Y = Fsum(), Fsum(), Fsum() x1, y1, _ = pts[n-1] for i in range(n): x2, y2, _ = pts[i] if wrap and i < (n - 1): _, x2 = unrollPI(x1, x2, wrap=True) t = x1 * y2 - x2 * y1 A += t X += t * (x1 + x2) Y += t * (y1 + y2) # XXX more elaborately: # t1, t2 = x1 * y2, -(x2 * y1) # A.fadd_(t1, t2) # X.fadd_(t1 * x1, t1 * x2, t2 * x1, t2 * x2) # Y.fadd_(t1 * y1, t1 * y2, t2 * y1, t2 * y2) x1, y1 = x2, y2 A = A.fsum() * 3.0 # 6.0 / 2.0 if abs(A) < EPS: raise ValueError('polar or zero area: %r' % (pts,)) Y, X = degrees90(Y.fsum() / A), degrees180(X.fsum() / A) return (Y, X) if LatLon is None else LatLon(Y, X)
def distance3To(self, other, radius=R_M, wrap=False): '''Compute the great-circle distance between this and an other geohash using the U{Haversine <http://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, b2 = unrollPI(b1, b2, wrap=wrap) return haversine_(a2, a1, db) * float(radius)
def midpointTo(self, other, height=None, wrap=False): '''Find the midpoint between this and an other point. @param other: The other point (L{LatLon}). @keyword height: Optional height for midpoint, overriding the mean height (C{meter}). @keyword wrap: Wrap and unroll longitudes (C{bool}). @return: Midpoint (L{LatLon}). @raise TypeError: The I{other} point is not L{LatLon}. @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 <http://MathForum.org/library/drmath/view/51822.html> a1, b1 = self.to2ab() a2, b2 = other.to2ab() db, b2 = unrollPI(b1, b2, wrap=wrap) ca1, ca2, cdb = map1(cos, a1, a2, db) sa1, sa2, sdb = map1(sin, a1, a2, db) x = ca2 * cdb + ca1 y = ca2 * sdb a = atan2(sa1 + sa2, hypot(x, y)) b = atan2(y, x) + b1 h = self._havg(other) if height is None else height return self.classof(degrees90(a), degrees180(b), height=h)
def intersection(start1, bearing1, start2, bearing2, height=None, wrap=False, LatLon=LatLon): '''Compute the intersection point of two paths each defined by a start point and an initial bearing. @param start1: Start point of first path (L{LatLon}). @param bearing1: Initial bearing from start1 (compass C{degrees360}). @param start2: Start point of second path (L{LatLon}). @param bearing2: Initial bearing from start2 (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 for the intersection point (L{LatLon}) or C{None}. @return: Intersection point (L{LatLon}) or 3-tuple (C{degrees90}, C{degrees180}, height) if C{LatLon} is C{None}. @raise TypeError: Point I{start1} or I{start2} is not L{LatLon}. @raise ValueError: Intersection is ambiguous or infinite or the paths are parallel or coincident. @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') # see <http://www.EdWilliams.org/avform.htm#Intersection> 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)) else: ca1, ca2, cr12 = map1(cos, a1, a2, r12) sa1, sa2, sr12 = map1(sin, 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, start2)) # handle domain error for equivalent longitudes, # see also functions asin_safe and acos_safe at # <http://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, bearing1, bearing2) x1, x2 = map1(wrapPI, t13 - t12, # angle 2-1-3 t21 - t23) # angle 1-2-3 sx1, sx2 = map1(sin, x1, x2) if sx1 == 0 and sx2 == 0: raise ValueError('intersection %s: %r vs %r' % ('infinite', start1, start2)) sx3 = sx1 * sx2 if sx3 < 0: raise ValueError('intersection %s: %r vs %r' % ('ambiguous', start1, start2)) cx1, cx2 = map1(cos, x1, x2) x3 = acos1(cr12 * sx3 - cx2 * cx1) r13 = atan2(sr12 * sx3, cx2 + cx1 * cos(x3)) a, b = _destination2(a1, b1, r13, t13) h = start1._havg(start2) if height is None else height return (a, b, h) if LatLon is None else LatLon(a, b, height=h)
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 for the intersection point (L{LatLon}) or C{None}. @return: The intersection point (I{LatLon}) or 3-tuple (C{degrees90}, C{degrees180}, height) if I{LatLon} is C{None}. An alternate intersection point might be the L{antipode} to the returned result. @raise TypeError: Start or end point(s) 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') 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 <http://www.EdWilliams.org/avform.htm#Intersection> elif isscalar(end1) and isscalar(end2): # both bearings ca1, ca2, cr12 = map1(cos, a1, a2, r12) sa1, sa2, sr12 = map1(sin, 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 # <http://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, sx2 = map1(sin, 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))) cx1, cx2 = map1(cos, x1, x2) 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') x2, d2 = _x3d2(start2, end2, wrap, '2') 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 = start1._havg(start2) if height is None else height return (a, b, h) if LatLon is None else LatLon(a, b, height=h)
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)
def _distances(self, x, y): # (x, y) radians for xk, yk in zip(self._xs, self._ys): dx, _ = unrollPI(xk, x, wrap=self._wrap) if self._adjust: dx *= _scaler(yk, y) yield dx**2 + (y - yk)**2 # like equirectangular_ distance2
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 haversine_(y, yk, d)
def areaOf(points, radius=R_M, wrap=True): '''Calculate the area of a (spherical) polygon (with great circle arcs joining the points). @param points: The polygon points (L{LatLon}[]). @keyword radius: Optional, mean earth radius (C{meter}). @keyword wrap: Wrap and unroll longitudes (C{bool}). @return: Polygon area (C{meter}, same units as I{radius}, squared). @raise TypeError: Some I{points} are not L{LatLon}. @raise ValueError: Insufficient number of I{points}. @note: The area is based on Karney's U{'Area of a spherical polygon' <http://osgeo-org.1560.x6.nabble.com/Area-of-a-spherical-polygon-td3841625.html>}. @see: L{pygeodesy.areaOf}, L{sphericalNvector.areaOf} and L{ellipsoidalKarney.areaOf}. @example: >>> b = LatLon(45, 1), LatLon(45, 2), LatLon(46, 2), LatLon(46, 1) >>> areaOf(b) # 8666058750.718977 >>> c = LatLon(0, 0), LatLon(1, 0), LatLon(0, 1) >>> areaOf(c) # 6.18e9 ''' n, points = _Trll.points2(points, closed=True) # Area method due to Karney: for each edge of the polygon, # # tan(Δλ/2) · (tan(φ1/2) + tan(φ2/2)) # tan(E/2) = ------------------------------------ # 1 + tan(φ1/2) · tan(φ2/2) # # where E is the spherical excess of the trapezium obtained by # extending the edge to the equator-circle vector for each edge if iterNumpy2(points): 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 s = fsum(_exs(n, points)) * 2 else: a1, b1 = points[n - 1].to2ab() s, 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) s.append(atan2(tdb * (ta1 + ta2), 1 + ta1 * ta2)) ta1, b1 = ta2, b2 s = fsum(s) * 2 if isPoleEnclosedBy(points): s = abs(s) - PI2 return abs(s * radius**2)