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 toLatLon(self, LatLon=None, **LatLon_height_datum_kwds): '''Return the geodetic C{(lat, lon, height[, datum])} coordinates. @kwarg LatLon: Optional class to return C{(lat, lon, height[, datum])} or C{None}. @kwarg LatLon_height_datum_kwds: Optional B{C{LatLon}}, B{C{height}}, B{C{datum}} and other keyword arguments. @return: An instance of C{LatLon}C{(lat, lon, **_height_datum_kwds)} or if B{C{LatLon}} is C{None}, a L{LatLon3Tuple}C{(lat, lon, height)} respectively L{LatLon4Tuple}C{(lat, lon, height, datum)} whether C{datum} is un-/available. @raise TypeError: Invalid B{C{LatLon}} or B{C{LatLon_height_datum_kwds}}. ''' kwds = _xkwds(LatLon_height_datum_kwds, height=self.height, datum=self.datum) # PYCHOK Ecef9Tuple d = kwds['datum'] if LatLon is None: r = LatLon3Tuple(self.lat, self.lon, kwds['height']) # PYCHOK Ecef9Tuple if d: r = r.to4Tuple(d) # checks type(d) else: if d is None: d = kwds.pop['datum'] r = LatLon(self.lat, self.lon, **kwds) # PYCHOK Ecef9Tuple return self._xnamed(r)
def meanOf(points, datum=Datums.WGS84, height=None, LatLon=LatLon, **LatLon_kwds): '''Compute the geographic mean of several points. @arg points: Points to be averaged (L{LatLon}[]). @kwarg datum: Optional datum to use (L{Datum}). @kwarg height: Optional height at mean point, overriding the mean height (C{meter}). @kwarg LatLon: Optional class to return the mean point (L{LatLon}) or C{None}. @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if B{C{LatLon=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._N_vector for p in points) lat, lon, h = m._N_vector.latlonheight if height is not None: h = height if LatLon is None: r = LatLon3Tuple(lat, lon, h) else: kwds = _xkwds(LatLon_kwds, height=h, datum=datum) r = LatLon(lat, lon, **kwds) return _xnamed(r, meanOf.__name__)
def _latlon3(lat, lon, height, func, LatLon, **LatLon_kwds): '''(INTERNAL) Helper for L{intersection} and L{meanof}. ''' if LatLon is None: r = LatLon3Tuple(lat, lon, height) else: kwds = _xkwds(LatLon_kwds, height=height) r = LatLon(lat, lon, **kwds) return _xnamed(r, func.__name__)
def to3llh(self, height=None): '''Convert this n-vector to (geodetic) lat-, longitude in C{degrees} and height. @keyword height: Optional height, overriding this n-vector's height (C{meter}). @return: A L{LatLon3Tuple}C{(lat, lon, height)}. ''' r = self.toLatLon(height=height, LatLon=None) r = LatLon3Tuple(r.lat, r.lon, r.height) return self._xnamed(r)
def to3llh(self, datum=Datums.WGS84): '''Convert this (geocentric) Cartesian (x/y/z) point to (ellipsoidal, geodetic) lat-, longitude and height on the given datum. Uses B. R. Bowring’s formulation for μm precision in concise form: U{'The accuracy of geodetic latitude and height equations' <https://www.ResearchGate.net/publication/ 233668213_The_Accuracy_of_Geodetic_Latitude_and_Height_Equations>}, Survey Review, Vol 28, 218, Oct 1985. See also Ralph M. Toms U{'An Efficient Algorithm for Geocentric to Geodetic Coordinate Conversion'<https://www.OSTI.gov/scitech/biblio/110235>}, Sept 1995 and U{'An Improved Algorithm for Geocentric to Geodetic Coordinate Conversion'<https://www.OSTI.gov/scitech/servlets/purl/231228>}, Apr 1996, from Lawrence Livermore National Laboratory. @keyword datum: Optional datum to use (L{Datum}). @return: A L{LatLon3Tuple}C{(lat, lon, height)}. ''' E = datum.ellipsoid x, y, z = self.to3xyz() p = hypot(x, y) # distance from minor axis r = hypot(p, z) # polar radius if min(p, r) > EPS: # parametric latitude (Bowring eqn 17, replaced) t = (E.b * z) / (E.a * p) * (1 + E.e22 * E.b / r) c = 1 / hypot1(t) s = t * c # geodetic latitude (Bowring eqn 18) a = atan2(z + E.e22 * E.b * s**3, p - E.e2 * E.a * c**3) b = atan2(y, x) # ... and longitude # height above ellipsoid (Bowring eqn 7) sa, ca = sincos2(a) # r = E.a / E.e2s(sa) # length of normal terminated by minor axis # h = p * ca + z * sa - (E.a * E.a / r) h = fsum_(p * ca, z * sa, -E.a * E.e2s(sa)) a, b = degrees90(a), degrees180(b) # see <https://GIS.StackExchange.com/questions/28446> elif p > EPS: # latitude arbitrarily zero a, b, h = 0.0, degrees180(atan2(y, x)), p - E.a else: # polar latitude, longitude arbitrarily zero a, b, h = copysign(90.0, z), 0.0, abs(z) - E.b return self._xnamed(LatLon3Tuple(a, b, h))
def _3llh_(strllh, height, sep): ll = strllh.strip().split(sep) if len(ll) > 2: # XXX interpret height unit h = float(ll.pop(2).strip().rstrip(_LETTERS).rstrip()) else: h = height # None from wgrs.Georef.__new__ if len(ll) != 2: raise ValueError a, b = [_.strip() for _ in ll] # PYCHOK false if a[-1:] in _EW_ or b[-1:] in _NS_: a, b = b, a return LatLon3Tuple(parseDMS(a, suffix=_NS_, clip=clipLat), parseDMS(b, suffix=_EW_, clip=clipLon), h)
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}}. @arg point: The other, reference point (L{LatLon}). @arg points: The polygon points (L{LatLon}[]). @kwarg closed: Optionally, close the polygon (C{bool}). @kwarg radius: Mean earth radius (C{meter}). @kwarg LatLon: Optional class to return the closest point (L{LatLon}) or C{None}. @kwarg options: Optional keyword arguments for function L{equirectangular_}. @return: A L{NearestOn3Tuple}C{(closest, distance, angle)} with the C{closest} point as B{L{LatLon}} or L{LatLon3Tuple}C{(lat, lon, height)} if B{C{LatLon}} is C{None}. The C{distance} is the L{equirectangular_} distance between the C{closest} and the given B{C{point}} in C{meter}, same units as B{C{radius}}. The C{angle} from the given 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 PointsError: Insufficient number of B{C{points}}. @raise TypeError: Some B{C{points}} are not C{LatLon}. @raise ValueError: Invalid B{C{radius}}. @see: Functions L{equirectangular_} and L{nearestOn5}. ''' lat, lon, d, c, h = _nearestOn5(point, points, closed=closed, LatLon=None, **options) r = LatLon3Tuple(lat, lon, h) if LatLon is None else \ LatLon(lat, lon, height=h) r = NearestOn3Tuple(r, degrees2m(d, radius=radius), c) return _xnamed(r, nearestOn3.__name__)
def parse3llh(strll, height=0, sep=',', clipLat=90, clipLon=180): '''Parse a string representing lat-, longitude and height point. The lat- and longitude value must be separated by a separator character. If height is present it must follow, separated by another separator. The lat- and longitude values may be swapped, provided at least one ends with the proper compass point. @param strll: Latitude, longitude[, height] (C{str}, ...). @keyword height: Optional, default height (C{meter}). @keyword sep: Optional separator (C{str}). @keyword clipLat: Keep latitude in B{C{-clipLat..+clipLat}} (C{degrees}). @keyword clipLon: Keep longitude in B{C{-clipLon..+clipLon}} range (C{degrees}). @return: A L{LatLon3Tuple}C{(lat, lon, height)} in C{degrees}, C{degrees} and C{float}. @raise RangeError: Lat- or longitude value of B{C{strll}} outside valid range and L{rangerrors} set to C{True}. @raise ValueError: Invalid B{C{strll}}. @see: Functions L{parseDMS} and L{parseDMS2} for more details on the forms and symbols accepted. @example: >>> parse3llh('000°00′05.31″W, 51° 28′ 40.12″ N') (51.4778°N, 000.0015°W, 0) ''' try: ll = strll.strip().split(sep) if len(ll) > 2: # XXX interpret height unit h = float(ll.pop(2).strip().rstrip(_LETTERS).rstrip()) else: h = height if len(ll) != 2: raise ValueError except (AttributeError, TypeError, ValueError): return ValueError('parsing %r failed' % (strll, )) a, b = [_.strip() for _ in ll] if a[-1:] in 'EW' or b[-1:] in 'NS': a, b = b, a a, b = parseDMS2(a, b, clipLat=clipLat, clipLon=clipLon) # PYCHOK LatLon2Tuple return LatLon3Tuple(a, b, h)
def toLatLon(self, LatLon=None): '''Return the geodetic C{(lat, lon, height[, datum])} coordinates. @keyword LatLon: Optional (sub-)class to return C{(lat, lon, height[, datum])} or C{None}. @return: An instance of C{LatLon}C{(lat, lon, height[, datum])} if B{C{LatLon}} is not C{None} or a L{LatLon3Tuple}C{(lat, lon, height)} or a L{LatLon4Tuple}C{(lat, lon, height, datum)} if C{datum} is unavailable respectively available. ''' lat, lon = self.lat, self.lon # PYCHOK expected h, d = self.height, self.datum # PYCHOK expected if d is None: r = LatLon3Tuple(lat, lon, h) if LatLon is None else \ LatLon(lat, lon, h) elif isinstance(d, Datum): r = LatLon4Tuple(lat, lon, h, d) if LatLon is None else \ LatLon(lat, lon, height=h, datum=d) else: raise AssertionError('%r.%s: %r' % (self, 'datum', d)) return self._xnamed(r)
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 testNvectorBase(self, module, **kwds): try: Nvector = module.Nvector c = Nvector.__name__ except AttributeError: Nvector = module.NvectorBase c = 'Vector4Tuple' self.subtitle(module, Nvector.__name__) v = Nvector(0.500, 0.500, 0.707, **kwds) s = module.sumOf((v, v), h=0, name='sumOf') self.test('sumOf', s.__class__.__name__, c) p = v.toLatLon(LatLon=None) c = v.toCartesian(Cartesian=None) self.test('ecef.x, .y, .z', fstr(p[:3], prec=5), fstr(c[:3], prec=5)) self.test('ecef.lat, .lon', fstr(p[3:5], prec=6), fstr(c[3:5], prec=6)) self.test('ecef.height', fstr(p.height, prec=6), fstr(c.height, prec=6), known=True) if c.M is not None: self.test('ecef.M', fstr(p.M, prec=9), fstr(c.M, prec=9)) if coverage: from pygeodesy.named import LatLon2Tuple, LatLon3Tuple, \ PhiLam2Tuple, PhiLam3Tuple self.test('.isEllipsoidal', v.isEllipsoidal, not v.isSpherical) self.test('.isSpherical', v.isSpherical, not v.isEllipsoidal) self.test('.latlon', v.latlon, LatLon2Tuple(v.lat, v.lon)) self.test('.philam', v.philam, PhiLam2Tuple(v.phi, v.lam)) self.test('.latlonheight', v.latlonheight, LatLon3Tuple(v.lat, v.lon, float(v.h))) self.test('.philamheight', v.philamheight, PhiLam3Tuple(v.phi, v.lam, float(v.h))) t = v.parse('0.5, 0.5, 0.707') self.test('parse', t, v) self.test('cmp', t.cmp(v), 0) self.test('eq', t == v, True) self.test('ge', t >= v, True) self.test('gt', t > v, False) self.test('le', t <= v, True) self.test('lt', t < v, False) self.test('ne', t != v, False) m = t * 2 self.test('*', m, '(1.0, 1.0, 1.414)') self.test('+', t + v, m) self.test('/', m / 2, t) self.test('-', m - t, t) m = v.__matmul__(t) self.test('@', m, '(0.0, 0.0, 0.0)') r = t.__rmatmul__(m) self.test('@', r, m) r = v.rotate(m, 45) self.test('rotate', r, '(0.26268, 0.26268, 0.37143)')