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__)
Esempio n. 2
0
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__)
Esempio n. 3
0
    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)
Esempio n. 4
0
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__)
Esempio n. 5
0
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__)
Esempio n. 6
0
    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)
Esempio n. 7
0
    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))
Esempio n. 8
0
    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)
Esempio n. 9
0
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__)
Esempio n. 10
0
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)
Esempio n. 11
0
    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)
Esempio n. 12
0
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__)
Esempio n. 13
0
    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)')