def _to3zBll(lat, lon, cmoff=True): '''(INTERNAL) Return zone, Band and lat- and (central) longitude in degrees. @arg lat: Latitude (C{degrees}). @arg lon: Longitude (C{degrees}). @kwarg cmoff: Offset B{C{lon}} from zone's central meridian. @return: 4-Tuple (zone, Band, lat, lon). ''' z, lat, lon = _to3zll(lat, lon) # in .utmupsBase if _UTM_LAT_MIN > lat or lat >= _UTM_LAT_MAX: # [-80, 84) like Veness r = _range_(_UTM_LAT_MIN, _UTM_LAT_MAX, ropen=True) t = _SPACE_(_outside_, _UTM_, _range_, r) raise RangeError(lat=degDMS(lat), txt=t) B = _Bands[int(lat + 80) >> 3] x = lon - _cmlon(z) # z before Norway/Svaldbard if abs(x) > _UTM_ZONE_OFF_MAX: t = _SPACE_(_outside_, _UTM_, _zone_, str(z), _by_, degDMS(x, prec=6)) raise RangeError(lon=degDMS(lon), txt=t) if B == _X_: # and 0 <= int(lon) < 42: z = int(lon + 183) // 6 + 1 x = {32: 9, 34: 21, 36: 33}.get(z, None) if x: # Svalbard z += 1 if lon >= x else -1 elif B == _V_ and z == 31 and lon >= 3: z += 1 # SouthWestern Norway if cmoff: # lon off central meridian lon -= _cmlon(z) # z after Norway/Svaldbard return Zone(z), Band(B), Lat(lat), Lon(lon)
def __imul__(self, other): '''Multiply this instance by a scalar or an other instance. @arg other: L{Fsum} instance or C{scalar}. @return: This instance, updated (L{Fsum}). @raise TypeError: Invalid B{C{other}} type. @see: Method L{Fsum.fmul}. ''' if isscalar(other): self.fmul(other) elif isinstance(other, Fsum): ps = list(other._ps) # copy if ps: s = self.fcopy() self.fmul(ps.pop()) while ps: # self += s * ps.pop() p = s.fcopy() p.fmul(ps.pop()) self.fadd(p._ps) else: self._ps = [] # zero self._fsum2_ = None else: raise _TypeError(_SPACE_(self, '*=', repr(other))) return self
def _reframeTransforms(rf2, rf, epoch): '''(INTERNAL) Get 0, 1 or 2 Helmert C{Transforms} to convert reference frame C{rf} observed at C{epoch} into C{rf2}. ''' n2 = rf2.name # .upper() n1 = rf.name # .upper() if n1 == n2 or (n1.startswith(_S.ITRF) and n2.startswith(_S.WGS84)) \ or (n2.startswith(_S.ITRF) and n1.startswith(_S.WGS84)): return () # PYCHOK returns if (n1, n2) in _trfXs: return (_2Transform((n1, n2), epoch, _Forward), ) # PYCHOK returns if (n2, n1) in _trfXs: return (_2Transform((n2, n1), epoch, _Reverse), ) # PYCHOK returns n = _intermediate(n1, n2) if n: return ( _2Transform((n1, n), epoch, _Forward), # PYCHOK returns _2Transform((n, n2), epoch, _Forward)) n = _intermediate(n2, n1) if n: return ( _2Transform((n, n1), epoch, _Reverse), # PYCHOK returns _2Transform((n2, n), epoch, _Reverse)) t = _SPACE_(RefFrame.__name__, repr(n1), _to_, repr(n2)) raise TRFError(_no_(_conversion_), txt=t)
def upsZoneBand5(lat, lon, strict=True): '''Return the UTM/UPS zone number, (polar) Band letter, pole and clipped lat- and longitude for a given location. @arg lat: Latitude in degrees (C{scalar} or C{str}). @arg lon: Longitude in degrees (C{scalar} or C{str}). @kwarg strict: Restrict B{C{lat}} to UPS ranges (C{bool}). @return: A L{UtmUpsLatLon5Tuple}C{(zone, band, hemipole, lat, lon)} where C{hemipole} is the C{'N'|'S'} pole, the UPS projection top/center. @raise RangeError: If B{C{strict}} and B{C{lat}} in the UTM and not the UPS range or if B{C{lat}} or B{C{lon}} outside the valid range and L{rangerrors} set to C{True}. @raise ValueError: Invalid B{C{lat}} or B{C{lon}}. ''' z, lat, lon = _to3zll(*parseDMS2(lat, lon)) if lat < _UPS_LAT_MIN: # includes 30' overlap z, B, p = _UPS_ZONE, _Band(lat, lon), _S_ elif lat > _UPS_LAT_MAX: # includes 30' overlap z, B, p = _UPS_ZONE, _Band(lat, lon), _N_ elif strict: r = _range_(_UPS_LAT_MIN, _UPS_LAT_MAX) t = _SPACE_(_inside_, _UTM_, _range_, r) raise RangeError(lat=degDMS(lat), txt=t) else: B, p = NN, _hemi(lat) return UtmUpsLatLon5Tuple(z, B, p, lat, lon, Error=UPSError)
def _xkwds_Error(_xkwds_func, kwds, name_txt, txt='default'): # Helper for _xkwds_get and _xkwds_pop below from pygeodesy.streprs import Fmt, pairs f = _COMMASPACE_.join(pairs(kwds) + pairs(name_txt)) f = Fmt.PAREN(_xkwds_func.__name__, f) t = _multiple_ if name_txt else _no_ t = _SPACE_(t, _EQUAL_(_name_, txt), 'kwargs') return _AssertionError(f, txt=t)
def _en(en, lo, hi, ename): # U, Error try: if lo <= float(en) <= hi: return except (TypeError, ValueError): pass t = _SPACE_(_outside_, U, _range_, _range_(lo, hi)) raise Error(ename, en, txt=t)
def _immutable(inst, value): '''Throws an C{AttributeError}, always. ''' from pygeodesy.named import classname s = _DOT_(repr(inst), self.name) s = _EQUALSPACED_(s, repr(value)) t = _SPACE_(_immutable_, classname(self)) raise _AttributeError(s, txt=t)
def _callname(name, class_name, self_name, up=1): # imported by .points '''(INTERNAL) Assemble the name for an invokation. ''' n, c = class_name, callername(up=up + 1) if c: n = _DOT_(n, Fmt.PAREN(c, name)) if self_name: n = _SPACE_(n, repr(self_name)) return n
def _xtend(self, xTuple, *items): '''(INTERNAL) Extend this C{Named-Tuple} with C{items} to an other B{C{xTuple}}. ''' if not (issubclassof(xTuple, _NamedTuple) and (len(self._Names_) + len(items)) == len(xTuple._Names_) and self._Names_ == xTuple._Names_[:len(self)]): c = NN(self.classname, repr(self._Names_)) x = NN(xTuple.__name__, repr(xTuple._Names_)) raise TypeError(_SPACE_(c, _vs_, x)) return self._xnamed(xTuple(*(self + items)))
def _datum_datum(datum1, datum2, Error=None): '''(INTERNAL) Check for datum or ellipsoid match. ''' if Error: E1, E2 = datum1.ellipsoid, datum2.ellipsoid if E1 != E2: raise Error(E2.named2, txt=_incompatible(E1.named2)) elif datum1 != datum2: t = _SPACE_(_datum_, repr(datum1.name), _not_, repr(datum2.name)) raise _AssertionError(t)
def _txt(c1, r1, c2, r2): # check for concentric or too distant spheres d = c1.minus(c2).length if d < abs(r1 - r2): t = _near_concentric_ elif d > (r1 + r2): t = _too_(Fmt.distant(d)) else: return NN return _SPACE_(c1.name, 'and', c2.name, t)
def _or(*words): '''(INTERNAL) Join C{words} with C{", "} and C{" or "}. ''' t, w = NN, list(words) if w: t = w.pop() if w: w = _COMMASPACE_.join(w) t = _SPACE_(w, _or_, t) return t
def _boolkwds(inst, **name_value_pairs): # in .frechet, .hausdorff, .heights '''(INTERNAL) Set applicable C{bool} properties/attributes. ''' for n, v in name_value_pairs.items(): b = getattr(inst, n, None) if b is None: # invalid bool attr t = _SPACE_(_EQUAL_(n, repr(v)), 'for', inst.__class__.__name__) # XXX .classname raise _AttributeError(t, txt=_not_('applicable')) if v in (False, True) and v != b: setattr(inst, NN(_UNDER_, n), v)
def _xversion(package, where, *required): # in .karney '''(INTERNAL) Check the C{package} version vs B{C{required}}. ''' t = map2(int, package.__version__.split(_DOT_)[:2]) if t < required: from pygeodesy.named import modulename as mn t = _SPACE_(package.__name__, _version_, _DOT_.join(map2(str, t)), 'below', _DOT_.join(map2(str, required)), 'required', _by_, mn(where, True)) raise ImportError(t) return package
def trilaterate5( self, distance1, point2, distance2, point3, distance3, # PYCHOK signature area=False, eps=EPS1, radius=R_M, wrap=False): '''B{Not implemented} for C{B{area}=True} or C{B{wrap}=True} and falls back to method C{trilaterate} otherwise. @return: A L{Trilaterate5Tuple}C{(min, minPoint, max, maxPoint, n)} with a single trilaterated intersection C{minPoint I{is} maxPoint}, C{min I{is} max} the nearest intersection margin and count C{n = 1}. @raise IntersectionError: No intersection, trilateration failed. @raise NotImplementedError: Keyword argument C{B{area}=True} or B{C{wrap}=True} not (yet) supported. @raise TypeError: Invalid B{C{point2}} or B{C{point3}}. @raise ValueError: Some B{C{points}} coincide or invalid B{C{distance1}}, B{C{distance2}}, B{C{distance3}} or B{C{radius}}. ''' if area or wrap: from pygeodesy.named import notImplemented notImplemented(self, self.trilaterate5, area=area, wrap=wrap) t = _trilaterate(self, distance1, self.others(point2=point2), distance2, self.others(point3=point3), distance3, radius=radius, height=None, useZ=True, LatLon=self.classof) # ... and handle B{C{eps}} and C{IntersectionError} as # method C{.latlonBase.LatLonBase.trilaterate2} d = self.distanceTo(t, radius=radius, wrap=wrap) # PYCHOK distanceTo d = abs(distance1 - d), abs(distance2 - d), abs(distance3 - d) d = float(min(d)) if d < eps: # min is max, minPoint is maxPoint return Trilaterate5Tuple(d, t, d, t, 1) # n = 1 t = _SPACE_(_no_(_intersection_), Fmt.PAREN(min.__name__, Fmt.f(d, prec=3))) raise IntersectionError(area=area, eps=eps, wrap=wrap, txt=t)
def _error_init(Error, inst, name_value, fmt_name_value='%s (%r)', txt=_invalid_, **name_values): # by .lazily '''(INTERNAL) Format an error text and initialize an C{Error} instance. @arg Error: The error super-class (C{Exception}). @arg inst: Sub-class instance to be initialized (C{_Exception}). @arg name_value: Either just a value or several name, value, ... positional arguments (C{str}, any C{type}), in particular for name conflicts with keyword arguments of C{error_init} or which can't be used as C{name=value} keyword arguments. @kwarg name_value_fmt: Format for (name, value) (C{str}). @kwarg txt: Optional explanation of the error (C{str}). @kwarg name_values: One or more B{C{name=value}} pairs overriding any B{C{name_value}} positional arguments. ''' if name_values: t = _or(*sorted(fmt_name_value % t for t in name_values.items())) elif len(name_value) > 1: t = _or(*sorted(fmt_name_value % t for t in zip(name_value[0::2], name_value[1::2]))) elif name_value: t = str(name_value[0]) else: t = _SPACE_(_name_value_, str(MISSING)) if txt is None: x = NN else: x = str(txt) or _invalid_ c = _COMMA_ if _COLON_ in t else _COLON_ t = _SPACE_(t + c, x) Error.__init__(inst, t) # inst.__x_txt__ = x # hold explanation _error_chain(inst) # no Python 3+ exception chaining _error_under(inst)
def __init__(self, *name_n_corners, **txt): '''New L{ClipError}. @arg name_n_corners: Either just a name (C{str}) or name, number, corners (C{str}, C{int}, C{tuple}). @kwarg txt: Optional explanation of the error (C{str}). ''' if len(name_n_corners) == 3: t, n, v = name_n_corners n = _SPACE_(t, 'clip', 'box' if n == 2 else 'region') name_n_corners = n, v _ValueError.__init__(self, *name_n_corners, **txt)
def unregister(self): '''Remove this instance from its C{_NamedEnum} registry. @raise AssertionError: Mismatch of this and registered item. @raise NameError: This item is unregistered. ''' enum = self._enum if enum and self.name and self.name in enum: item = enum.unregister(self.name) if item is not self: t = _SPACE_(repr(item), _vs_, repr(self)) raise _AssertionError(t)
def __init__(self, where, **lens_txt): # txt=None '''New L{LenError}. @arg where: Object with C{.__name__} attribute (C{class}, C{method}, or C{function}). @kwarg lens_txt: Two or more C{name=len(name)} pairs (C{keyword arguments}). ''' from pygeodesy.streprs import Fmt as _Fmt x = _xkwds_pop(lens_txt, txt=_invalid_) ns, vs = zip(*sorted(lens_txt.items())) ns = _COMMASPACE_.join(ns) vs = ' vs '.join(map(str, vs)) t = _SPACE_(_Fmt.PAREN(where.__name__, ns), _len_, vs) _ValueError.__init__(self, t, txt=x)
def toUps(self, pole=NN, **unused): '''Duplicate this UPS coordinate. @kwarg pole: Optional top/center of the UPS projection, (C{str}, 'N[orth]'|'S[outh]'). @return: A copt of this UPS coordinate (L{Ups}). @raise UPSError: Invalid B{C{pole}} or attempt to transfer the projection top/center. ''' if self.pole == pole or not pole: return self.copy() t = _SPACE_(_pole_, repr(self.pole), _to_, repr(pole)) raise UPSError('no transfer', txt=t)
def __iadd__(self, other): '''Add a scalar or an other instance to this instance. @arg other: L{Fsum} instance or C{scalar}. @return: This instance, updated (L{Fsum}). @raise TypeError: Invalid B{C{other}} type. @see: Method L{Fsum.fadd}. ''' if isscalar(other): self.fadd_(other) elif other is self: self.fmul(2) elif isinstance(other, Fsum): self.fadd(other._ps) else: raise _TypeError(_SPACE_(self, '+=', repr(other))) return self
def __isub__(self, other): '''Subtract a scalar or an other instance from this instance. @arg other: L{Fsum} instance or C{scalar}. @return: This instance, updated (L{Fsum}). @raise TypeError: Invalid B{C{other}} type. @see: Method L{Fsum.fadd}. ''' if isscalar(other): self.fadd_(-other) elif other is self: self._ps = [] # zero self._fsum2_ = None elif isinstance(other, Fsum): self.fadd(-p for p in other._ps) else: raise _TypeError(_SPACE_(self, '-=', repr(other))) return self
def convertRefFrame(self, reframe2): '''Convert this point to an other reference frame. @arg reframe2: Reference frame to convert I{to} (L{RefFrame}). @return: The converted point (ellipsoidal C{LatLon}) or this point if conversion is C{nil}. @raise TRFError: No B{C{.reframe}} or no conversion available from B{C{.reframe}} to B{C{reframe2}}. @raise TypeError: The B{C{reframe2}} is not a L{RefFrame}. @example: >>> p = LatLon(51.4778, -0.0016, reframe=RefFrames.ETRF2000) # default Datums.WGS84 >>> p.convertRefFrame(RefFrames.ITRF2014) # 51.477803°N, 000.001597°W, +0.01m ''' from pygeodesy.trf import RefFrame, _reframeTransforms _xinstanceof(RefFrame, reframe2=reframe2) if not self.reframe: t = _SPACE_(_DOT_(repr(self), 'reframe'), MISSING) raise TRFError(_no_(_conversion_), txt=t) ts = _reframeTransforms(reframe2, self.reframe, self.epoch) if ts: c = self.toCartesian() for t in ts: c = c._applyHelmert(t, False) ll = c.toLatLon(datum=self.datum, LatLon=self.classof, epoch=self.epoch, reframe=reframe2) # ll.reframe, ll.epoch = reframe2, self.epoch else: ll = self return ll
def _IsnotError(*nouns, **name_value_Error): # name=value [, Error=TypeeError] '''Create a C{TypeError} for an invalid C{name=value} type. @arg nouns: One or more expected class or type names, usually nouns (C{str}). @kwarg name_value_Error: One B{C{name=value}} pair and optionally, an B{C{Error=...}} keyword argument to override the default B{C{Error=TypeError}}. @return: A C{TypeError} or an B{C{Error}} instance. ''' from pygeodesy.streprs import Fmt as _Fmt Error = _xkwds_pop(name_value_Error, Error=TypeError) n, v = _xkwds_popitem(name_value_Error) if name_value_Error else ( _name_value_, MISSING) # XXX else tuple(...) t = _or(*nouns) or _specified_ if len(nouns) > 1: t = _an(t) e = Error(_SPACE_(n, _Fmt.PAREN(repr(v)), _not_, t)) _error_chain(e) _error_under(e) return e
def _error(arg): n = _DOT_(Fstr.__name__, self.name or self) return _SPACE_(n, _PERCENT_, repr(arg))
def _an(noun): '''(INTERNAL) Prepend an article to a noun based on the pronounciation of the first letter. ''' return _SPACE_((_an_ if noun[:1].lower() in 'aeinoux' else _a_), noun)
def _split2(g, name, _2m): i = max(g.find(name[0]), g.rfind(name[0])) if i > _BaseLen: return g[:i], _2m(int(g[i + 1:]), _SPACE_(georef, name)) else: return g, None
def _invokationError(name, *args): # PYCHOK no cover '''(INTERNAL) Return an L{EllipticError}. ''' return EllipticError(_SPACE_('invokation', NN(name, repr(args)))) # unstr
def _convergenceError(where, *args): # PYCHOK no cover '''(INTERNAL) Return an L{EllipticError}. ''' t = _SPACE_(where.__name__, repr(args)) return EllipticError(_no_(_convergence_), txt=t) # unstr
def _inverse(self, other, azis, wrap): '''(INTERNAL) Inverse 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 and/or if this and the B{C{other}} point are coincident or near-antipodal. ''' 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 self._iteration in range(1, self._iterations + 1): ll_ = ll sll, cll = sincos2(ll) ss = hypot(c2 * sll, c1s2 - s1c2 * cll) if ss < EPS: # coincident or antipodal, ... if self.isantipodeTo(other, eps=self._epsilon): t = '%r %sto %r' % (self, _antipodal_, other) raise VincentyError(_ambiguous_, txt=t) # return zeros like Karney, but unlike Veness return Distance3Tuple(0.0, 0, 0) 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 # # omitted and applied only after failure to converge below, see footnote # # under Inverse at <https://WikiPedia.org/wiki/Vincenty's_formulae> # # <https://GitHub.com/ChrisVeness/geodesy/blob/master/latlon-vincenty.js> # elif abs(ll) > PI and self.isantipodeTo(other, eps=self._epsilon): # raise VincentyError('%s, %r %sto %r' % ('ambiguous', self, # _antipodal_, other)) else: t = _antipodal_ if self.isantipodeTo(other, eps=self._epsilon) else NN t = _SPACE_(repr(self), NN(t, _to_), repr(other)) raise VincentyError(_no_(_convergence_), txt=t) 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 sll, cll = sincos2(ll) f = atan2b(c2 * sll, c1s2 - s1c2 * cll) r = atan2b(c1 * sll, -s1c2 + c1s2 * cll) else: f = r = 0 return Distance3Tuple(d, f, r)