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 _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, name=_knots_)) # PYCHOK unzip n = len(hs) if n < m: raise _insufficientError(m, knots=n) return map1(atype, xs, ys, hs)
def cross(self, other, raiser=None): # raiser=NN '''Compute the cross product of this and an other vector. @arg other: The other vector (L{Vector3d}). @kwarg 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(raiser, r, txt=t) return self.classof(x, y, z)
def isequalTo(self, other, eps=None): '''Compare this point with an other point, I{ignoring} height. @arg other: The other point (C{LatLon}). @kwarg eps: Tolerance for equality (C{degrees}). @return: C{True} if both points are identical, I{ignoring} height, C{False} otherwise. @raise TypeError: The B{C{other}} point is not C{LatLon} or mismatch of the B{C{other}} and this C{class} or C{type}. @raise ValueError: Invalid B{C{eps}}. @see: Method L{isequalTo3}. @example: >>> p = LatLon(52.205, 0.119) >>> q = LatLon(52.205, 0.119) >>> e = p.isequalTo(q) # True ''' self.others(other) e = _0_0 if eps in (None, 0, _0_0) else Scalar_(eps, name='eps') if e > 0: return max(map1(abs, self.lat - other.lat, self.lon - other.lon)) < e else: return self.lat == other.lat and \ self.lon == other.lon
def _exs(n, points): # iterate over spherical edge excess a1, b1 = points[n - 1].philam ta1 = tan_2(a1) for i in range(n): a2, b2 = points[i].philam 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 _to4lldn(latlon, lon, datum, name): '''(INTERNAL) Return 4-tuple (C{lat, lon, datum, name}). ''' try: # if lon is not None: # raise AttributeError lat, lon = map1(float, latlon.lat, latlon.lon) _xinstanceof(_LLEB, LatLonDatum5Tuple, latlon=latlon) d = datum or latlon.datum except AttributeError: lat, lon = parseDMS2(latlon, lon) d = datum or Datums.WGS84 return lat, lon, d, (name or nameof(latlon))
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 _xyzn4(xyz, y, z, Error=_TypeError): # imported by .ecef '''(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: d = dict(zip(('xyz', _y_, _z_), t)) raise Error(txt=str(x), **d) return x, y, z, getattr(xyz, _name_, NN)
def _llhn4(latlonh, lon, height, suffix): '''(INTERNAL) Get C{lat, lon, h, name} as C{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. @arg year: Year of the date (C{scalar}). @arg month: Month in the B{C{year}} (C{scalar}, 1..12). @arg 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 date2epoch(year, month, day): '''Return the reference frame C{epoch} for a calendar day. @arg year: Year of the date (C{scalar}). @arg month: Month in the B{C{year}} (C{scalar}, 1..12). @arg 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 Epoch(y + float(sum(_mDays[:m]) + d) / _366_0, low=0) t = NN # _invalid_ except (TRFError, TypeError, ValueError) as x: t = str(x) raise TRFError(year=year, month=month, day=day, txt=t)
def toOsgr(latlon, lon=None, datum=Datums.WGS84, Osgr=Osgr, name=NN, **Osgr_kwds): '''Convert a lat-/longitude point to an OSGR coordinate. @arg latlon: Latitude (C{degrees}) or an (ellipsoidal) geodetic C{LatLon} point. @kwarg lon: Optional longitude in degrees (scalar or C{None}). @kwarg datum: Optional datum to convert B{C{lat, lon}} from (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}). @kwarg Osgr: Optional class to return the OSGR coordinate (L{Osgr}) or C{None}. @kwarg name: Optional B{C{Osgr}} name (C{str}). @kwarg Osgr_kwds: Optional, additional B{C{Osgr}} keyword arguments, ignored if B{C{Osgr=None}}. @return: The OSGR coordinate (B{C{Osgr}}) or an L{EasNor2Tuple}C{(easting, northing)} if B{C{Osgr}} is C{None}. @raise OSGRError: Invalid B{C{latlon}} or B{C{lon}}. @raise TypeError: Non-ellipsoidal B{C{latlon}} or invalid B{C{datum}} or conversion failed. @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(lon=lon, txt='not %s' % (None, )) elif not name: # use latlon.name name = nameof(latlon) # if necessary, convert to OSGB36 first ll = _ll2datum(latlon, _Datums_OSGB36, _latlon_) try: a, b = ll.philam except AttributeError: a, b = map1(radians, ll.lat, ll.lon) sa, ca = sincos2(a) E = _Datums_OSGB36.ellipsoid s = E.e2s2(sa) # r, v = 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) == s / E.e12 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, datum=_Datums_OSGB36, **Osgr_kwds) if lon is None and isinstance(latlon, _LLEB): r._latlon = latlon # XXX weakref(latlon)? return _xnamed(r, name)
def intersection(start1, end1, start2, end2, height=None, wrap=False, LatLon=LatLon, **LatLon_kwds): '''Compute the intersection point of two paths both defined by two points or a start point and bearing from North. @arg start1: Start point of the first path (L{LatLon}). @arg end1: End point ofthe first path (L{LatLon}) or the initial bearing at the first start point (compass C{degrees360}). @arg start2: Start point of the second path (L{LatLon}). @arg end2: End point of the second path (L{LatLon}) or the initial bearing at the second start point (compass C{degrees360}). @kwarg height: Optional height for the intersection point, overriding the mean height (C{meter}). @kwarg wrap: Wrap and unroll longitudes (C{bool}). @kwarg LatLon: Optional class to return the intersection point (L{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if B{C{LatLon=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 or invalid B{C{height}}. @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.philam a2, b2 = start2.philam db, b2 = unrollPI(b1, b2, wrap=wrap) r12 = haversine_(a2, a1, db) if abs(r12) < EPS: # [nearly] coincident points a, b = 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) # PYCHOK PhiLam2Tuple 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.philam # choose intersection similar to sphericalNvector d1 = _xdot(d1, a1, b1, a, b, wrap) if d1: d2 = _xdot(d2, a2, b2, a, b, wrap) if (d2 < 0 and d1 > 0) or (d2 > 0 and d1 < 0): a, b = antipode_(a, b) # PYCHOK PhiLam2Tuple h = fmean(hs) if height is None else Height(height) return _latlon3(degrees90(a), degrees180(b), h, intersection, LatLon, **LatLon_kwds)
def _trilaterate(point1, distance1, point2, distance2, point3, distance3, radius=R_M, height=None, useZ=False, **LatLon_LatLon_kwds): # (INTERNAL) Locate a point at given distances from # three other points, see LatLon.triangulate above def _nd2(p, d, r, _i_, *qs): # .toNvector and angular distance squared for q in qs: if p.isequalTo(q, EPS): raise _ValueError(points=p, txt=_coincident_) return p.toNvector(), (Scalar(d, name=_distance_ + _i_) / r)**2 r = Radius_(radius) n1, r12 = _nd2(point1, distance1, r, _1_) n2, r22 = _nd2(point2, distance2, r, _2_, point1) n3, r32 = _nd2(point3, distance3, r, _3_, 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) z = None 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_(r12, -r22, d**2) / (2 * d) # n1->intersection x- and ... y = fsum_(r12, -r32, i**2, j**2, -2 * x * i) / ( 2 * j) # ... y-component # courtesy AleixDev <https://GitHub.com/mrJean1/PyGeodesy/issues/43> z = fsum_(max(r12, r22, r32), -(x**2), -(y**2)) # XXX not just r12! if z > EPS: n = n1.plus(X.times(x)).plus(Y.times(y)) if useZ: # include Z component 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(height) kwds = _xkwds(LatLon_LatLon_kwds, height=h) return n.toLatLon(** kwds) # Nvector(n.x, n.y, n.z).toLatLon(...) # no intersection, d < EPS_2 or abs(j) < EPS_2 or z < EPS t = NN(_no_, _intersection_, _SPACE_) raise IntersectionError(point1=point1, distance1=distance1, point2=point2, distance2=distance2, point3=point3, distance3=distance3, txt=unstr(t, z=z, useZ=useZ))
def _T(*fs): '''(INTERNAL) Cache a tuple of single C{float}s. ''' return map1(_F, *fs)
def utmupsValidate(coord, falsed=False, MGRS=False, Error=UTMUPSError): '''Check a UTM or UPS coordinate. @arg coord: The UTM or UPS coordinate (L{Utm}, L{Ups} or C{5+Tuple}). @kwarg falsed: C{5+Tuple} easting and northing are falsed (C{bool}). @kwarg MGRS: Increase easting and northing ranges (C{bool}). @kwarg Error: Optional error to raise, overriding the default (L{UTMUPSError}). @return: C{None} if validation passed. @raise Error: Validation failed. @see: Function L{utmupsValidateOK}. ''' def _en(en, lo, hi, ename): # U, Error try: if lo <= float(en) <= hi: return except (TypeError, ValueError): pass t = _SPACE_.join( (_outside_, U, _range_, '[%.0F' % (lo, ), '%.0F]' % (hi, ))) raise Error(ename, en, txt=t) if isinstance(coord, (Ups, Utm)): hemi = coord.hemisphere enMM = coord.falsed elif isinstance(coord, (UtmUps5Tuple, UtmUps8Tuple)): hemi = coord.hemipole enMM = falsed else: raise _IsnotError(Error=Error, coord=coord, *map1(modulename, Utm, Ups, UtmUps5Tuple, UtmUps8Tuple)) band = coord.band zone = coord.zone z, B, h = _to3zBhp(zone, band, hemipole=hemi) if z == _UPS_ZONE: # UPS import pygeodesy.ups as u # PYCHOK expected U, M = _UPS_, _UpsMinMax else: # UTM import pygeodesy.utm as u # PYCHOK expected U, M = _UTM_, _UtmMinMax if MGRS: U, s = _MGRS_, _MGRS_TILE else: s = 0 i = _NS_.find(h) if i < 0 or z < _UTMUPS_ZONE_MIN \ or z > _UTMUPS_ZONE_MAX \ or B not in u._Bands: t = '%s(%s%s %s)' % (U, z, B, h) raise Error(coord=t, zone=zone, band=band, hemisphere=hemi) if enMM: _en(coord.easting, M.eMin[i] - s, M.eMax[i] + s, _easting_) # PYCHOK .eMax .eMin _en(coord.northing, M.nMin[i] - s, M.nMax[i] + s, _northing_) # PYCHOK .nMax .nMin