def _latlon3(self, LatLon, datum): '''(INTERNAL) Convert cached LatLon ''' ll = self._latlon if LatLon is None: if datum and datum != ll.datum: raise TypeError('no %s.convertDatum: %r' % (LatLon, ll)) return _xnamed(LatLonDatum3Tuple(ll.lat, ll.lon, ll.datum), ll.name) elif issubclassof(LatLon, _LLEB): ll = _xnamed(LatLon(ll.lat, ll.lon, datum=ll.datum), ll.name) return _ll2datum(ll, datum, 'LatLon') raise TypeError('%s not ellipsoidal: %r' % ('LatLon', LatLon))
def toLcc(latlon, conic=Conics.WRF_Lb, height=None, Lcc=Lcc, name=''): '''Convert an (ellipsoidal) geodetic point to a Lambert location. @param latlon: Ellipsoidal point (C{LatLon}). @keyword conic: Optional Lambert projection to use (L{Conic}). @keyword height: Optional height for the point, overriding the default height (C{meter}). @keyword Lcc: Optional (sub-)class to return the Lambert location (L{Lcc}). @keyword name: Optional B{C{Lcc}} name (C{str}). @return: The Lambert location (L{Lcc}) or an L{EasNor3Tuple}C{(easting, northing, height)} if B{C{Lcc}} is C{None}. @raise TypeError: If B{C{latlon}} is not ellipsoidal. ''' if not isinstance(latlon, _LLEB): raise TypeError('%s not %s: %r' % ('latlon', 'ellipsoidal', latlon)) a, b = latlon.to2ab() c = conic.toDatum(latlon.datum) t = c._n * (b - c._lon0) - c._opt3 st, ct = sincos2(t) r = c._rdef(c._tdef(a)) e = c._E0 + r * st n = c._N0 + c._r0 - r * ct h = latlon.height if height is None else height r = EasNor3Tuple(e, n, h) if Lcc is None else \ Lcc(e, n, h=h, conic=c) return _xnamed(r, name or nameof(latlon))
def meanOf(points, datum=Datums.WGS84, height=None, LatLon=LatLon): '''Compute the geographic mean of several points. @param points: Points to be averaged (L{LatLon}[]). @keyword datum: Optional datum to use (L{Datum}). @keyword height: Optional height at mean point, overriding the mean height (C{meter}). @keyword LatLon: Optional (sub-)class to return the mean point (L{LatLon}) or C{None}. @return: Geographic mean point and mean height (B{C{LatLon}}) or a L{LatLon3Tuple}C{(lat, lon, height)} if B{C{LatLon}} is C{None}. @raise ValueError: Insufficient number of B{C{points}}. ''' _, points = _Nvll.points2(points, closed=False) # geographic mean m = sumOf(p.toNvector() for p in points) a, b, h = m.to3llh() if height is not None: h = height r = LatLon3Tuple(a, b, h) if LatLon is None else \ LatLon(a, b, height=h, datum=datum) return _xnamed(r, meanOf.__name__)
def meanOf(points, height=None, LatLon=LatLon): '''Compute the geographic mean of several points. @param points: Points to be averaged (L{LatLon}[]). @keyword height: Optional height at mean point, overriding the mean height (C{meter}). @keyword LatLon: Optional (sub-)class to return the mean point (L{LatLon}) or C{None}. @return: Point at geographic mean and height (B{C{LatLon}}) or a L{LatLon3Tuple}C{(lat, lon, height)} if B{C{LatLon}} is C{None}. @raise TypeError: Some B{C{points}} are not L{LatLon}. @raise ValueError: No B{C{points}}. ''' # geographic mean n, points = _Trll.points2(points, closed=False) m = sumOf(points[i].toVector3d() for i in range(n)) a, b = m.to2ll() if height is None: h = fmean(points[i].height for i in range(n)) else: h = height r = LatLon3Tuple(a, b, h) if LatLon is None else \ LatLon(a, b, height=h) return _xnamed(r, meanOf.__name__)
def parseETM5(strUTM, datum=Datums.WGS84, Etm=Etm, falsed=True, name=''): '''Parse a string representing a UTM coordinate, consisting of C{"zone[band] hemisphere easting northing"}. @param strUTM: A UTM coordinate (C{str}). @keyword datum: Optional datum to use (L{Datum}). @keyword Etm: Optional (sub-)class to return the UTM coordinate (L{Etm}) or C{None}. @keyword falsed: Both easting and northing are falsed (C{bool}). @keyword name: Optional B{C{Etm}} name (C{str}). @return: The UTM coordinate (B{C{Etm}}) or a L{UtmUps5Tuple}C{(zone, hemipole, easting, northing, band)} if B{C{Etm}} is C{None}. The C{hemipole} is the hemisphere C{'N'|'S'}. @raise ETMError: Invalid B{C{strUTM}}. @example: >>> u = parseETM5('31 N 448251 5411932') >>> u.toStr2() # [Z:31, H:N, E:448251, N:5411932] >>> u = parseETM5('31 N 448251.8 5411932.7') >>> u.toStr() # 31 N 448252 5411933 ''' r = _parseUTM5(strUTM, UTMError) if Etm is not None: z, h, e, n, B = r r = Etm(z, h, e, n, band=B, datum=datum, falsed=falsed) return _xnamed(r, name)
def parseWM(strWM, radius=R_MA, Wm=Wm, name=''): '''Parse a string representing a WM coordinate, consisting of easting, northing and an optional radius. @param strWM: A WM coordinate (C{str}). @keyword radius: Optional earth radius (C{meter}). @keyword Wm: Optional (sub-)class to return the WM coordinate (L{Wm}) or C{None}. @keyword name: Optional name (C{str}). @return: The WM coordinate (B{C{Wm}}) or an L{EasNorRadius3Tuple}C{(easting, northing, radius)} if B{C{Wm}} is C{None}. @raise ValueError: Invalid B{C{strWM}}. @example: >>> u = parseWM('448251 5411932') >>> u.toStr2() # [E:448251, N:5411932] ''' w = strWM.strip().replace(',', ' ').split() try: if len(w) == 2: w += [radius] elif len(w) != 3: raise ValueError # caught below x, y, r = map(float, w) except (TypeError, ValueError): raise ValueError('%s invalid: %r' % ('strWM', strWM)) r = EasNorRadius3Tuple(x, y, r) if Wm is None else \ Wm(x, y, radius=r) return _xnamed(r, name)
def parseMGRS(strMGRS, datum=Datums.WGS84, Mgrs=Mgrs, name=''): '''Parse a string representing a MGRS grid reference, consisting of zoneBand, grid, easting and northing. @param strMGRS: MGRS grid reference (C{str}). @keyword datum: Optional datum to use (L{Datum}). @keyword Mgrs: Optional (sub-)class to return the MGRS grid reference (L{Mgrs}) or C{None}. @keyword name: Optional B{C{Mgrs}} name (C{str}). @return: The MGRS grid reference (B{L{Mgrs}}) or an L{Mgrs4Tuple}C{(zone, digraph, easting, northing)} if B{C{Mgrs}} is C{None}. @raise ValueError: Invalid B{C{strMGRS}}. @example: >>> m = parseMGRS('31U DQ 48251 11932') >>> str(m) # 31U DQ 48251 11932 >>> m = parseMGRS('31UDQ4825111932') >>> repr(m) # [Z:31U, G:DQ, E:48251, N:11932] ''' def _mg(cre, s): # return re.match groups m = cre.match(s) if not m: raise ValueError return m.groups() def _s2m(g): # e or n string to meter f = float(g) if f > 0: x = int(log10(f)) if 0 <= x < 4: # at least 5 digits f *= (10000, 1000, 100, 10)[x] return f m = tuple(strMGRS.strip().replace(',', ' ').split()) try: if len(m) == 1: # 01ABC1234512345' m = _mg(_MGRSre, m[0]) m = m[:2] + halfs2(m[2]) elif len(m) == 2: # 01ABC 1234512345' m = _mg(_GZDre, m[0]) + halfs2(m[1]) elif len(m) == 3: # 01ABC 12345 12345' m = _mg(_GZDre, m[0]) + m[1:] if len(m) != 4: # 01A BC 1234 12345 raise ValueError e, n = map(_s2m, m[2:]) except ValueError: raise ValueError('%s invalid: %r' % ('strMGRS', strMGRS)) z, EN = m[0], m[1].upper() r = Mgrs4Tuple(z, EN, e, n) if Mgrs is None else \ Mgrs(z, EN, e, n, datum=datum) return _xnamed(r, name)
def decode3(garef, center=True): '''Decode a C{garef} to lat-, longitude and precision. @param garef: To be decoded (L{Garef} or C{str}). @keyword center: If C{True} the center, otherwise the south-west, lower-left corner (C{bool}). @return: A L{LatLonPrec3Tuple}C{(lat, lon, precision)}. @raise ValueError: Invalid B{C{garef}}, INValid, non-alphanumeric or bad length B{C{garef}}. ''' def _Error(i): return ValueError('%s invalid: %r[%s]' % ('garef', garef, i)) def _ll(chars, g, i, j, lo, hi): ll, b = 0, len(chars) for i in range(i, j): d = chars.find(g[i]) if d < 0: raise _Error(i) ll = ll * b + d if ll < lo or ll > hi: raise _Error(j) return ll def _ll2(lon, lat, g, i, m): d = _Digits.find(g[i]) if d < 1 or d > m * m: raise _Error(i) d, r = divmod(d - 1, m) lon = lon * m + r lat = lat * m + (m - 1 - d) return lon, lat g, precision = _2garstr2(garef) lon = _ll(_Digits, g, 0, _LonLen, 1, 720) + _LonOrig_M1_1 lat = _ll(_Letters, g, _LonLen, _MinLen, 0, 359) + _LatOrig_M1 if precision > 0: lon, lat = _ll2(lon, lat, g, _MinLen, _M2) if precision > 1: lon, lat = _ll2(lon, lat, g, _MinLen + 1, _M3) r = _Resolutions[precision] # == 1.0 / unit if center: lon = lon * 2 + 1 lat = lat * 2 + 1 r *= 0.5 lon *= r lat *= r return _xnamed(LatLonPrec3Tuple(lat, lon, precision), nameof(garef))
def _latlon5(self, LatLon): '''(INTERNAL) Convert cached LatLon ''' ll = self._latlon if LatLon is None: r = LatLonDatum5Tuple(ll.lat, ll.lon, ll.datum, ll.convergence, ll.scale) elif issubclassof(LatLon, _LLEB): r = _xattrs(LatLon(ll.lat, ll.lon, datum=ll.datum), ll, '_convergence', '_scale') else: raise TypeError('%s not ellipsoidal: %r' % ('LatLon', LatLon)) return _xnamed(r, ll.name)
def toWm(latlon, lon=None, radius=R_MA, Wm=Wm, name=''): '''Convert a lat-/longitude point to a WM coordinate. @param latlon: Latitude (C{degrees}) or an (ellipsoidal or spherical) geodetic C{LatLon} point. @keyword lon: Optional longitude (C{degrees} or C{None}). @keyword radius: Optional earth radius (C{meter}). @keyword Wm: Optional (sub-)class to return the WM coordinate (L{Wm}) or C{None}. @keyword name: Optional name (C{str}). @return: The WM coordinate (B{C{Wm}}) or an L{EasNorRadius3Tuple}C{(easting, northing, radius)} if B{C{Wm}} is C{None}. @raise ValueError: If B{C{lon}} value is missing, if B{C{latlon}} is not scalar, if B{C{latlon}} is beyond the valid WM range and L{rangerrors} is set to C{True} or if B{C{radius}} is invalid. @example: >>> p = LatLon(48.8582, 2.2945) # 448251.8 5411932.7 >>> w = toWm(p) # 448252 5411933 >>> p = LatLon(13.4125, 103.8667) # 377302.4 1483034.8 >>> w = toWm(p) # 377302 1483035 ''' r, e = radius, None try: lat, lon = latlon.lat, latlon.lon if isinstance(latlon, _LLEB): r = latlon.datum.ellipsoid.a e = latlon.datum.ellipsoid.e if not name: # use latlon.name name = nameof(latlon) lat = clipDMS(lat, _LatLimit) except AttributeError: lat, lon = parseDMS2(latlon, lon, clipLat=_LatLimit) s = sin(radians(lat)) y = atanh(s) # == log(tan((90 + lat) / 2)) == log(tanPI_2_2(radians(lat))) if e: y -= e * atanh(e * s) e, n = r * radians(lon), r * y r = EasNorRadius3Tuple(e, n, r) if Wm is None else \ Wm(e, n, radius=r) return _xnamed(r, name)
def _toXtm8(Xtm, zlxyBdckf, name, latlon, eps): '''(INTERNAL) Helper for L{toEtm8} and L{toUtm8}. ''' z, lat, x, y, B, d, c, k, f = zlxyBdckf h = _hemi(lat) if f: x, y = _false2(x, y, h) if Xtm is None: # DEPRECATED r = UtmUps8Tuple(z, h, x, y, B, d, c, k) else: r = Xtm(z, h, x, y, band=B, datum=d, falsed=f, convergence=c, scale=k) if isinstance(latlon, _LLEB) and d is latlon.datum: r._latlon_to(latlon, eps, f) # XXX weakref(latlon)? latlon._convergence = c latlon._scale = k return _xnamed(r, name)
def nearestOn3(point, points, closed=False, radius=R_M, LatLon=LatLon, **options): '''Locate the point on a polygon closest to an other, reference point. Distances are approximated by function L{equirectangular_}, subject to the supplied B{C{options}}. @param point: The other, reference point (L{LatLon}). @param points: The polygon points (L{LatLon}[]). @keyword closed: Optionally, close the polygon (C{bool}). @keyword radius: Optional, mean earth radius (C{meter}). @keyword LatLon: Optional (sub-)class to return the closest point (L{LatLon}) or C{None}. @keyword options: Optional keyword arguments for function L{equirectangular_}. @return: A L{NearestOn3Tuple}C{(closest, distance, angle)}. The C{distance} is the L{equirectangular_} distance between the C{closest} and reference B{C{point}} in C{meter}, same units as B{C{radius}}. The C{angle} from the reference B{C{point}} to the C{closest} is in compass C{degrees360}, like function L{compassAngle}. The C{height} is the (interpolated) height at the C{closest} point. @raise LimitError: Lat- and/or longitudinal delta exceeds the B{C{limit}}, see function L{equirectangular_}. @raise TypeError: Some I{points} are not C{LatLon}. @raise ValueError: Insufficient number of B{C{points}}. @see: Functions L{equirectangular_} and L{nearestOn5}. ''' a, b, d, c, h = _nearestOn5(point, points, closed=closed, LatLon=None, **options) r = LatLon3Tuple(a, b, h) if LatLon is None else \ LatLon(a, b, height=h) r = NearestOn3Tuple(r, degrees2m(d, radius=radius), c) return _xnamed(r, nearestOn3.__name__)
def toMgrs(utm, Mgrs=Mgrs, name=''): '''Convert a UTM coordinate to an MGRS grid reference. @param utm: A UTM coordinate (L{Utm} or L{Etm}). @keyword Mgrs: Optional (sub-)class to return the MGRS grid reference (L{Mgrs}) or C{None}. @keyword name: Optional B{C{Mgrs}} name (C{str}). @return: The MGRS grid reference (B{L{Mgrs}}) or an L{Mgrs6Tuple}C{(zone, digraph, easting, northing, band, datum)} if B{L{Mgrs}} is C{None}. @raise TypeError: If B{C{utm}} is not L{Utm} not L{Etm}. @raise ValueError: Invalid B{C{utm}}. @example: >>> u = Utm(31, 'N', 448251, 5411932) >>> m = u.toMgrs() # 31U DQ 48251 11932 ''' if not isinstance(utm, Utm): raise TypeError('%s not Utm: %s' % ('utm', type(utm))) e, n = utm.to2en(falsed=True) # truncate east-/northing to within 100 km grid square # XXX add rounding to nm precision? E, e = divmod(e, _100km) N, n = divmod(n, _100km) # columns in zone 1 are A-H, zone 2 J-R, zone 3 S-Z, then # repeating every 3rd zone (note -1 because eastings start # at 166e3 due to 500km false origin) z = utm.zone - 1 en = ( _Le100k[z % 3][int(E) - 1] + # rows in even zones are A-V, in odd zones are F-E _Ln100k[z % 2][int(N) % len(_Ln100k[0])]) if Mgrs is None: r = Mgrs6Tuple(utm.zone, en, e, n, utm.band, utm.datum) else: r = Mgrs(utm.zone, en, e, n, band=utm.band, datum=utm.datum) return _xnamed(r, name or utm.name)
def toCss(latlon, cs0=_CassiniSoldner0, height=None, Css=Css, name=''): '''Convert an (ellipsoidal) geodetic point to a Cassini-Soldner location. @param latlon: Ellipsoidal point (C{LatLon}). @keyword cs0: Optional, the Cassini-Soldner projection to use (L{CassiniSoldner}). @keyword height: Optional height for the point, overriding the default height (C{meter}). @keyword Css: Optional (sub-)class to return the location (L{Css}) or C{None}. @keyword name: Optional B{C{Css}} name (C{str}). @return: The Cassini-Soldner location (B{C{Css}}) or an L{EasNor3Tuple}C{(easting, northing, height)} if B{C{Css}} is C{None}. @raise ImportError: Package U{GeographicLib<https://PyPI.org/ project/geographiclib>} missing. @raise TypeError: If B{C{latlon}} is not ellipsoidal. ''' if not isinstance(latlon, _LLEB): raise TypeError('%s not %s: %r' % ('latlon', 'ellipsoidal', latlon)) cs = _CassiniSoldner(cs0) C, E = cs.datum.ellipsoid, latlon.datum.ellipsoid if C != E: raise ValueError('%s mistmatch: %r vs %r' % ('ellipsoidal', C, E)) c = cs.forward4(latlon.lat, latlon.lon) h = latlon.height if height is None else height if Css is None: r = EasNor3Tuple(c.easting, c.northing, h) else: r = Css(c.easting, c.northing, h=h, cs0=cs) r._latlon = LatLon2Tuple(latlon.lat, latlon.lon) r._azi, r._rk = c.azimuth, c.reciprocal return _xnamed(r, name or nameof(latlon))
def parseUPS5(strUPS, datum=Datums.WGS84, Ups=Ups, falsed=True, name=''): '''Parse a string representing a UPS coordinate, consisting of C{"[zone][band] pole easting northing"} where B{C{zone}} is pseudo zone C{"00"|"0"|""} and C{band} is C{'A'|'B'|'Y'|'Z'|''}. @param strUPS: A UPS coordinate (C{str}). @keyword datum: Optional datum to use (L{Datum}). @keyword Ups: Optional (sub-)class to return the UPS coordinate (L{Ups}) or C{None}. @keyword falsed: Both B{C{easting}} and B{C{northing}} are falsed (C{bool}). @keyword name: Optional B{C{Ups}} name (C{str}). @return: The UPS coordinate (B{C{Ups}}) or a L{UtmUps5Tuple}C{(zone, hemipole, easting, northing, band)} if B{C{Ups}} is C{None}. The C{hemipole} is the C{'N'|'S'} pole, the UPS projection top/center. @raise UPSError: Invalid B{C{strUPS}}. ''' try: u = strUPS.lstrip() if not u.startswith(_UPS_ZONE_STR): raise ValueError z, p, e, n, B = _parseUTMUPS(u) if z != _UPS_ZONE or (B and B not in _Bands): raise ValueError except (AttributeError, TypeError, ValueError): raise UPSError('%s invalid: %r' % ('strUPS', strUPS)) if Ups is None: r = UtmUps5Tuple(z, p, e, n, B) else: r = Ups(z, p, e, n, band=B, falsed=falsed, datum=datum) return _xnamed(r, name)
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 ValueError: 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 ValueError('%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 parseOSGR(strOSGR, Osgr=Osgr, name=''): '''Parse an OSGR coordinate string to an Osgr instance. Accepts standard OS Grid References like 'SU 387 148', with or without whitespace separators, from 2- up to 10-digit references (1 m × 1 m square), or fully numeric, comma-separated references in metres, for example '438700,114800'. @param strOSGR: An OSGR coordinate (C{str}). @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 ValueError: Invalid B{C{strOSGR}}. @example: >>> g = parseOSGR('TG 51409 13177') >>> str(g) # TG 51409 13177 >>> g = parseOSGR('TG5140913177') >>> str(g) # TG 51409 13177 >>> g = parseOSGR('TG51409 13177') >>> str(g) # TG 51409 13177 >>> g = parseOSGR('651409,313177') >>> str(g) # TG 51409 13177 >>> g.toStr(prec=0) # 651409,313177 ''' def _c2i(G): g = ord(G.upper()) - ord('A') if g > 7: g -= 1 return g def _s2f(g): return float(g.strip()) def _s2i(G, g): g += '00000' # std to meter return int(str(G) + g[:5]) s = strOSGR.strip() try: g = s.split(',') if len(g) == 2: # "easting,northing" if len(s) < 13: raise ValueError # caught below e, n = map(_s2f, g) else: # "GR easting northing" g, s = s[:2], s[2:].strip() e, n = map(_c2i, g) n, m = divmod(n, 5) E = ((e - 2) % 5) * 5 + m N = 19 - (e // 5) * 5 - n if 0 > E or E > 6 or \ 0 > N or N > 12: raise ValueError # caught below g = s.split() if len(g) == 1: # no whitespace e, n = halfs2(s) elif len(g) == 2: e, n = g else: raise ValueError # caught below e = _s2i(E, e) n = _s2i(N, n) except ValueError: raise ValueError('%s invalid: %r' % ('strOSGR', strOSGR)) r = EasNor2Tuple(e, n) if Osgr is None else Osgr(e, n) return _xnamed(r, name)
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 toUps8(latlon, lon=None, datum=None, Ups=Ups, pole='', falsed=True, strict=True, name=''): '''Convert a lat-/longitude point to a UPS coordinate. @param latlon: Latitude (C{degrees}) or an (ellipsoidal) geodetic C{LatLon} point. @keyword lon: Optional longitude (C{degrees}) or C{None} if B{C{latlon}} is a C{LatLon}. @keyword datum: Optional datum for this UPS coordinate, overriding B{C{latlon}}'s datum (C{Datum}). @keyword Ups: Optional (sub-)class to return the UPS coordinate (L{Ups}) or C{None}. @keyword pole: Optional top/center of (stereographic) projection (C{str}, C{'N[orth]'} or C{'S[outh]'}). @keyword falsed: False both easting and northing (C{bool}). @keyword strict: Restrict B{C{lat}} to UPS ranges (C{bool}). @keyword name: Optional B{C{Ups}} name (C{str}). @return: The UPS coordinate (B{C{Ups}}) or a L{UtmUps8Tuple}C{(zone, hemipole, easting, northing, band, datum, convergence, scale)} if B{C{Ups}} is C{None}. The C{hemipole} is the C{'N'|'S'} pole, the UPS projection top/center. @raise RangeError: If B{C{strict}} and B{C{lat}} outside the valid UPS bands or if B{C{lat}} or B{C{lon}} outside the valid range and L{rangerrors} set to C{True}. @raise TypeError: If B{C{latlon}} is not ellipsoidal. @raise ValueError: If B{C{lon}} value is missing or if B{C{latlon}} is invalid. @see: Karney's C++ class U{UPS <https://GeographicLib.SourceForge.io/html/classGeographicLib_1_1UPS.html>}. ''' lat, lon, d, name = _to4lldn(latlon, lon, datum, name) z, B, p, lat, lon = upsZoneBand5( lat, lon, strict=strict) # PYCHOK UtmUpsLatLon5Tuple E = d.ellipsoid p = str(pole or p)[:1].upper() N = p == 'N' # is north a = lat if N else -lat A = abs(a - 90) < _TOL # at pole t = tan(radians(a)) T = E.es_taupf(t) r = hypot1(T) + abs(T) if T >= 0: r = 0 if A else 1 / r k0 = getattr(Ups, '_scale0', _K0) # Ups is class or None r *= 2 * k0 * E.a / E.es_c k = k0 if A else _scale(E, r, t) c = lon # [-180, 180) from .upsZoneBand5 x, y = sincos2d(c) x *= r y *= r if N: y = -y else: c = -c if falsed: x += _Falsing y += _Falsing if Ups is None: r = UtmUps8Tuple(z, p, x, y, B, d, c, k) else: if z != _UPS_ZONE and not strict: z = _UPS_ZONE # ignore UTM zone r = Ups(z, p, x, y, band=B, datum=d, falsed=falsed, convergence=c, scale=k) r._hemisphere = _hemi(lat) if isinstance(latlon, _LLEB) and d is latlon.datum: r._latlon_to(latlon, falsed) # XXX weakref(latlon)? return _xnamed(r, name)