def cross(self, other, raiser=None): '''Compute the cross product of this and an other vector. @param other: The other vector (L{Vector3d}). @keyword raiser: Optional, L{CrossError} label if raised (C{str}). @return: Cross product (L{Vector3d}). @raise CrossError: Zero or near-zero cross product and both B{C{raiser}} and L{crosserrors} set. @raise TypeError: Incompatible B{C{other}} C{type}. ''' self.others(other) x = self.y * other.z - self.z * other.y y = self.z * other.x - self.x * other.z z = self.x * other.y - self.y * other.x if raiser and self.crosserrors and max(map1(abs, x, y, z)) < EPS: t = 'coincident' if self.isequalTo(other) else 'colinear' r = getattr(other, '_fromll', None) or other raise CrossError('%s %s: %r' % (t, raiser, r)) return self.classof(x, y, z)
def _xyhs3(atype, m, knots, off=True): # convert knot C{LatLon}s to tuples or C{NumPy} arrays and C{SciPy} sphericals xs, ys, hs = zip(*_xyhs(knots, off=off)) # PYCHOK unzip n = len(hs) if n < m: raise HeightError('insufficient %s: %s, need %s' % ('knots', n, m)) return map1(atype, xs, ys, hs)
def __init__(self, x, y, radius=R_MA, name=''): '''New L{Wm} Web Mercator (WM) coordinate. @param x: Easting from central meridian (C{meter}). @param y: Northing from equator (C{meter}). @keyword radius: Optional earth radius (C{meter}). @keyword name: Optional name (C{str}). @raise WebMercatorError: Invalid B{C{x}}, B{C{y}} or B{C{radius}}. @example: >>> import pygeodesy >>> w = pygeodesy.Wm(448251, 5411932) ''' if name: self.name = name try: self._x, self._y, r = map1(float, x, y, radius) except (TypeError, ValueError): raise WebMercatorError('%s invalid: %r' % (Wm.__name__, (x, y, radius))) if r < EPS: # check radius t = '%s.%s' % (self.classname, 'radius') raise WebMercatorError('%s invalid: %r' % (t, r)) self._radius = r
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 to2ab(self): '''Return this point's lat- and longitude in C{radians}. @return: A L{PhiLam2Tuple}C{(phi, lambda)}. ''' if not self._ab: a, b = map1(radians, self.lat, self.lon) self._ab = self._xnamed(PhiLam2Tuple(a, b)) return self._ab
def _xyzn4(xyz, y, z, Error=TypeError): # imported by .ecef.py '''(INTERNAL) Get an C{(x, y, z, name)} 4-tuple. ''' try: t = xyz.x, xyz.y, xyz.z except AttributeError: t = xyz, y, z try: x, y, z = map1(float, *t) except (TypeError, ValueError) as x: raise Error('%s invalid: %r, %s' % ('xyz, y or z', t, x)) return x, y, z, getattr(xyz, 'name', '')
def bearing(lat1, lon1, lat2, lon2, **options): '''Compute the initial or final bearing (forward or reverse azimuth) between a (spherical) start and end point. @param lat1: Start latitude (C{degrees}). @param lon1: Start longitude (C{degrees}). @param lat2: End latitude (C{degrees}). @param lon2: End longitude (C{degrees}). @keyword options: Optional keyword arguments for function L{bearing_}. @return: Initial or final bearing (compass C{degrees360}) or zero if start and end point coincide. ''' ab4 = map1(radians, lat1, lon1, lat2, lon2) return degrees(bearing_(*ab4, **options))
def _llhn4(latlonh, lon, height, suffix): '''(INTERNAL) Get an C{(lat, lon, h, name)} 4-tuple. ''' try: llh = latlonh.lat, latlonh.lon, getattr(latlonh, 'height', getattr(latlonh, 'h', height)) except AttributeError: llh = latlonh, lon, height try: lat, lon, h = map1(float, *llh) except (TypeError, ValueError) as x: t = 'lat_, lon_ or height_'.replace('_', suffix) raise EcefError('%s invalid: %r, %s' % (t, llh, x)) if abs(lat) > 90: raise EcefError('%s%s out of range: %.6g' % ('lat', suffix, lat)) return lat, lon, h, getattr(latlonh, 'name', '')
def date2epoch(year, month, day): '''Return the reference frame C{epoch} for a calendar day. @param year: Year of the date (C{scalar}). @param month: Month in the B{C{year}} (C{scalar}, 1..12). @param day: Day in the B{C{month}} (C{scalar}, 1..31). @return: Epoch, the fractional year (C{float}). @raise TRFError: Invalid B{C{year}}, B{C{month}} or B{C{day}}. @note: Any B{C{year}} is considered a leap year, i.e. having 29 days in February. ''' try: y, m, d = map1(int, year, month, day) if y > 0 and 1 <= m <= 12 and 1 <= d <= _mDays[m]: return y + float(sum(_mDays[:m]) + d) / 366.0 except (ValueError, TypeError): pass raise TRFError('%s invalid: %s-%s-%s' % ('date', year, month, day))
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 trilaterate(point1, distance1, point2, distance2, point3, distance3, radius=R_M, height=None, LatLon=LatLon, useZ=False): '''Locate a point at given distances from three other points. See also U{Trilateration<https://WikiPedia.org/wiki/Trilateration>}. @param point1: First point (L{LatLon}). @param distance1: Distance to the first point (C{meter}, same units as B{C{radius}}). @param point2: Second point (L{LatLon}). @param distance2: Distance to the second point (C{meter}, same units as B{C{radius}}). @param point3: Third point (L{LatLon}). @param distance3: Distance to the third point (C{meter}, same units as B{C{radius}}). @keyword radius: Optional, mean earth radius (C{meter}). @keyword height: Optional height at the trilaterated point, overriding the IDW height (C{meter}, same units as B{C{radius}}). @keyword LatLon: Optional (sub-)class to return the trilaterated point (L{LatLon}). @keyword useZ: Include Z component iff non-NaN, non-zero (C{bool}). @return: Trilaterated point (B{C{LatLon}}). @raise TypeError: If B{C{point1}}, B{C{point2}} or B{C{point3}} is not L{LatLon}. @raise ValueError: Invalid B{C{radius}}, some B{C{distances}} exceed trilateration or some B{C{points}} coincide. ''' def _nd2(p, d, name, *qs): # return Nvector and radial distance squared _Nvll.others(p, name=name) for q in qs: if p.isequalTo(q, EPS): raise ValueError('%s %s: %r' % ('coincident', 'points', p)) return p.toNvector(), (float(d) / radius)**2 if float(radius or 0) < EPS: raise ValueError('%s %s: %r' % ('radius', 'invalid', radius)) n1, d12 = _nd2(point1, distance1, 'point1') n2, d22 = _nd2(point2, distance2, 'point2', point1) n3, d32 = _nd2(point3, distance3, 'point3', 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) 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_(d12, -d22, d**2) / (2 * d) # n1->intersection x- and ... y = fsum_(d12, -d32, i**2, j**2) / (2 * j) - (x * i / j ) # ... y-component n = n1.plus(X.times(x)).plus(Y.times(y)) # .plus(Z.times(z)) if useZ: # include non-NaN, non-zero Z component z = fsum_(d12, -(x**2), -(y**2)) if z > EPS: 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 return n.toLatLon( height=h, LatLon=LatLon) # Nvector(n.x, n.y, n.z).toLatLon(...) # no intersection, d < EPS_2 or j < EPS_2 raise ValueError('no %s for %r, %r, %r at %r, %r, %r' % ('trilaterate', point1, point2, point3, distance1, distance2, distance3))
def toOsgr(latlon, lon=None, datum=Datums.WGS84, Osgr=Osgr, name=''): '''Convert a lat-/longitude point to an OSGR coordinate. @param latlon: Latitude (C{degrees}) or an (ellipsoidal) geodetic C{LatLon} point. @keyword lon: Optional longitude in degrees (scalar or C{None}). @keyword datum: Optional datum to convert (C{Datum}). @keyword Osgr: Optional (sub-)class to return the OSGR coordinate (L{Osgr}) or C{None}. @keyword name: Optional B{C{Osgr}} name (C{str}). @return: The OSGR coordinate (B{C{Osgr}}) or an L{EasNor2Tuple}C{(easting, northing)} if B{C{Osgr}} is C{None}. @raise TypeError: Non-ellipsoidal B{C{latlon}} or B{C{datum}} conversion failed. @raise OSGRError: Invalid B{C{latlon}} or B{C{lon}}. @example: >>> p = LatLon(52.65798, 1.71605) >>> r = toOsgr(p) # TG 51409 13177 >>> # for conversion of (historical) OSGB36 lat-/longitude: >>> r = toOsgr(52.65757, 1.71791, datum=Datums.OSGB36) ''' if not isinstance(latlon, _LLEB): # XXX fix failing _LLEB.convertDatum() latlon = _LLEB(*parseDMS2(latlon, lon), datum=datum) elif lon is not None: raise OSGRError('%s not %s: %r' % ('lon', None, lon)) elif not name: # use latlon.name name = nameof(latlon) E = _OSGB36.ellipsoid ll = _ll2datum(latlon, _OSGB36, 'latlon') a, b = map1(radians, ll.lat, ll.lon) sa, ca = sincos2(a) s = E.e2s2(sa) # v, r = E.roc2_(sa, _F0); r = v / r v = E.a * _F0 / sqrt(s) # nu r = s / E.e12 # nu / rho == v / (v * E.e12 / s) x2 = r - 1 # η2 ta = tan(a) ca3, ca5 = fpowers(ca, 5, 3) # PYCHOK false! ta2, ta4 = fpowers(ta, 4, 2) # PYCHOK false! vsa = v * sa I4 = (E.b * _F0 * _M(E.Mabcd, a) + _N0, (vsa / 2) * ca, (vsa / 24) * ca3 * fsum_(5, -ta2, 9 * x2), (vsa / 720) * ca5 * fsum_(61, ta4, -58 * ta2)) V4 = (_E0, (v * ca), (v / 6) * ca3 * (r - ta2), (v / 120) * ca5 * fdot((-18, 1, 14, -58), ta2, 5 + ta4, x2, ta2 * x2)) d, d2, d3, d4, d5, d6 = fpowers(b - _B0, 6) # PYCHOK false! n = fdot(I4, 1, d2, d4, d6) e = fdot(V4, 1, d, d3, d5) if Osgr is None: r = EasNor2Tuple(e, n) else: r = Osgr(e, n) if lon is None and isinstance(latlon, _LLEB): r._latlon = latlon # XXX weakref(latlon)? return _xnamed(r, name)
def _T(*fs): '''(INTERNAL) Cache a tuple of single C{float}s. ''' return map1(_F, *fs)