Пример #1
0
    def crossingParallels(self, other, lat, wrap=False):
        '''Return the pair of meridians at which a great circle defined
           by this and an other point crosses the given latitude.

           @param other: The other point defining great circle (L{LatLon}).
           @param lat: Latitude at the crossing (C{degrees}).
           @keyword wrap: Wrap and unroll longitudes (C{bool}).

           @return: 2-Tuple C{(lon1, lon2)}, both in C{degrees180} or
                    C{None} if the great circle doesn't reach B{C{lat}}.
        '''
        self.others(other)

        a1, b1 = self.to2ab()
        a2, b2 = other.to2ab()

        a = radians(lat)
        db, b2 = unrollPI(b1, b2, wrap=wrap)

        sa,  ca,  sa1, ca1, \
        sa2, ca2, sdb, cdb = sincos2(a, a1, a2, db)

        x = sa1 * ca2 * ca * sdb
        y = sa1 * ca2 * ca * cdb - ca1 * sa2 * ca
        z = ca1 * ca2 * sa * sdb

        h = hypot(x, y)
        if h < EPS or abs(z) > h:
            return None  # great circle doesn't reach latitude

        m = atan2(-y, x) + b1  # longitude at max latitude
        d = acos1(z / h)  # delta longitude to intersections
        return degrees180(m - d), degrees180(m + d)
Пример #2
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))
Пример #3
0
    def latlon2(self, datum=None):
        '''Convert this WM coordinate to a lat- and longitude.

           @kwarg datum: Optional, ellipsoidal datum (L{Datum},
                         L{Ellipsoid}, L{Ellipsoid2} or
                         L{a_f2Tuple}) or C{None}.

           @return: A L{LatLon2Tuple}C{(lat, lon)}.

           @raise TypeError: Invalid or non-ellipsoidal B{C{datum}}.

           @see: Method C{toLatLon}.
        '''
        r = self.radius
        x = self._x / r
        y = 2 * atan(exp(self._y / r)) - PI_2
        if datum is not None:
            E = _ellipsoidal_datum(datum, name=self.name).ellipsoid
            if not E.isEllipsoidal:
                raise _IsnotError(_ellipsoidal_, datum=datum)
            # <https://Earth-Info.NGA.mil/GandG/wgs84/web_mercator/
            #        %28U%29%20NGA_SIG_0011_1.0.0_WEBMERC.pdf>
            y = y / r
            if E.e:
                y -= E.e * atanh(E.e * tanh(y))  # == E.es_atanh(tanh(y))
            y *= E.a
            x *= E.a / r

        r = LatLon2Tuple(Lat(degrees90(y)), Lon(degrees180(x)))
        return self._xnamed(r)
Пример #4
0
    def to3lld(self, datum=None):
        '''Convert this L{Lcc} to a geodetic lat- and longitude.

           @keyword datum: Optional datum to use, otherwise use this
                           B{C{Lcc}}'s conic.datum (C{Datum}).

           @return: A L{LatLonDatum3Tuple}C{(lat, lon, datum)}.

           @raise TypeError: If B{C{datum}} is not ellipsoidal.
        '''
        c = self.conic
        if datum:
            c = c.toDatum(datum)

        e =         self.easting  - c._E0
        n = c._r0 - self.northing + c._N0

        r_ = copysign(hypot(e, n), c._n)
        t_ = pow(r_ / c._aF, c._n_)

        x = c._xdef(t_)  # XXX c._lon0
        while True:
            p, x = x, c._xdef(t_ * c._pdef(x))
            if abs(x - p) < 1e-9:  # XXX EPS too small?
                break
        # x, y == lon, lat
        a = degrees90(x)
        b = degrees180((atan(e / n) + c._opt3) * c._n_ + c._lon0)

        return LatLonDatum3Tuple(a, b, c.datum)
Пример #5
0
    def destination(self, distance, bearing, radius=R_M, height=None):
        '''Locate the destination from this point after having
           travelled the given distance on the given initial bearing.

           @arg distance: Distance travelled (C{meter}, same units as
                          B{C{radius}}).
           @arg bearing: Bearing from this point (compass C{degrees360}).
           @kwarg radius: Mean earth radius (C{meter}).
           @kwarg height: Optional height at destination (C{meter}, same
                          units a B{C{radius}}).

           @return: Destination point (L{LatLon}).

           @raise ValueError: Invalid B{C{distance}}, B{C{bearing}},
                              B{C{radius}} or B{C{height}}.

           @example:

           >>> p1 = LatLon(51.4778, -0.0015)
           >>> p2 = p1.destination(7794, 300.7)
           >>> p2.toStr()  # '51.5135°N, 000.0983°W'

           @JSname: I{destinationPoint}.
        '''
        a, b = self.philam

        r, t = _angular(distance, radius), Bearing_(bearing)

        a, b = _destination2(a, b, r, t)
        h = self.height if height is None else Height(height)
        return self.classof(degrees90(a), degrees180(b), height=h)
Пример #6
0
    def to2ll(self, datum=None):
        '''Convert this WM coordinate to a geodetic lat- and longitude.

           @keyword datum: Optional datum (C{Datum}).

           @return: A L{LatLon2Tuple}C{(lat, lon)}.

           @raise TypeError: Non-ellipsoidal B{C{datum}}.
        '''
        r = self.radius
        x = self._x / r
        y = 2 * atan(exp(self._y / r)) - PI_2
        if datum:
            E = datum.ellipsoid
            if not E.isEllipsoidal:
                raise TypeError('%s not %s: %r' %
                                ('datum', 'ellipsoidal', datum))
            # <https://Earth-Info.NGA.mil/GandG/wgs84/web_mercator/
            #       %28U%29%20NGA_SIG_0011_1.0.0_WEBMERC.pdf>
            y = y / r
            if E.e:
                y -= E.e * atanh(E.e * tanh(y))
            y *= E.a
            x *= E.a / r
        return self._xnamed(LatLon2Tuple(degrees90(y), degrees180(x)))
Пример #7
0
    def latlon2(self, datum=None):
        '''Convert this WM coordinate to a lat- and longitude.

           @kwarg datum: Optional ellipsoidal datum (C{Datum}).

           @return: A L{LatLon2Tuple}C{(lat, lon)}.

           @raise TypeError: Non-ellipsoidal B{C{datum}}.

           @see: Method C{toLatLon}.
        '''
        r = self.radius
        x = self._x / r
        y = 2 * atan(exp(self._y / r)) - PI_2
        if datum:
            _xinstanceof(Datum, datum=datum)
            E = datum.ellipsoid
            if not E.isEllipsoidal:
                raise IsnotError('ellipsoidal', datum=datum)
            # <https://Earth-Info.NGA.mil/GandG/wgs84/web_mercator/
            #        %28U%29%20NGA_SIG_0011_1.0.0_WEBMERC.pdf>
            y = y / r
            if E.e:
                y -= E.e * atanh(E.e * tanh(y))  # == E.es_atanh(tanh(y))
            y *= E.a
            x *= E.a / r

        r = LatLon2Tuple(degrees90(y), degrees180(x))
        return self._xnamed(r)
Пример #8
0
    def intermediateTo(self, other, fraction, height=None, wrap=False):
        '''Locate the point at given fraction between this and an
           other point.

           @arg other: The other point (L{LatLon}).
           @arg fraction: Fraction between both points (float, between
                          0.0 for this and 1.0 for the other point).
           @kwarg height: Optional height, overriding the fractional
                          height (C{meter}).
           @kwarg wrap: Wrap and unroll longitudes (C{bool}).

           @return: Intermediate point (L{LatLon}).

           @raise TypeError: The B{C{other}} point is not L{LatLon}.

           @raise ValueError: Invalid B{C{fraction}} or B{C{height}}.

           @example:

           >>> p1 = LatLon(52.205, 0.119)
           >>> p2 = LatLon(48.857, 2.351)
           >>> p = p1.intermediateTo(p2, 0.25)  # 51.3721°N, 000.7073°E

           @JSname: I{intermediatePointTo}.
        '''
        self.others(other)

        f = Scalar(fraction, name='fraction')

        a1, b1 = self.philam
        a2, b2 = other.philam

        db, b2 = unrollPI(b1, b2, wrap=wrap)
        r = haversine_(a2, a1, db)
        sr = sin(r)
        if abs(sr) > EPS:
            sa1, ca1, sa2, ca2, \
            sb1, cb1, sb2, cb2 = sincos2(a1, a2, b1, b2)

            A = sin((1 - f) * r) / sr
            B = sin(f * r) / sr

            x = A * ca1 * cb1 + B * ca2 * cb2
            y = A * ca1 * sb1 + B * ca2 * sb2
            z = A * sa1 + B * sa2

            a = atan2(z, hypot(x, y))
            b = atan2(y, x)

        else:  # points too close
            a = favg(a1, a2, f=f)
            b = favg(b1, b2, f=f)

        if height is None:
            h = self._havg(other, f=f)
        else:
            h = Height(height)
        return self.classof(degrees90(a), degrees180(b), height=h)
Пример #9
0
    def _direct(self, distance, bearing, llr, height=None):
        '''(INTERNAL) Direct 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.
        '''
        E = self.ellipsoid()

        c1, s1, t1 = _r3(self.lat, E.f)

        i = radians(bearing)  # initial bearing (forward azimuth)
        si, ci = sincos2(i)
        s12 = atan2(t1, ci) * 2

        sa = c1 * si
        c2a = 1 - sa**2
        if c2a < EPS:
            c2a = 0
            A, B = 1, 0
        else:  # e22 == (a / b)**2 - 1
            A, B = _p2(c2a * E.e22)

        s = d = distance / (E.b * A)
        for self._iteration in range(1, self._iterations + 1):
            ss, cs = sincos2(s)
            c2sm = cos(s12 + s)
            s_, s = s, d + _ds(B, cs, ss, c2sm)
            if abs(s - s_) < self._epsilon:
                break
        else:
            raise VincentyError(_no_(_convergence_),
                                txt=repr(self))  # self.toRepr()

        t = s1 * ss - c1 * cs * ci
        # final bearing (reverse azimuth +/- 180)
        r = atan2b(sa, -t)

        if llr:
            # destination latitude in [-90, 90)
            a = degrees90(
                atan2(s1 * cs + c1 * ss * ci, (1 - E.f) * hypot(sa, t)))
            # destination longitude in [-180, 180)
            b = degrees180(
                atan2(ss * si, c1 * cs - s1 * ss * ci) -
                _dl(E.f, c2a, sa, s, cs, ss, c2sm) + radians(self.lon))
            h = self.height if height is None else height
            d = self.classof(a, b, height=h, datum=self.datum)
        else:
            d = None
        return Destination2Tuple(d, r)
Пример #10
0
def _destination2(a, b, r, t):
    '''(INTERNAL) Computes destination lat- and longitude.

       @param a: Latitude (C{radians}).
       @param b: Longitude (C{radians}).
       @param r: Angular distance (C{radians}).
       @param t: Bearing (compass C{radians}).

       @return: 2-Tuple (lat, lon) of (C{degrees90}, C{degrees180}).
    '''
    a, b = _destination2_(a, b, r, t)
    return degrees90(a), degrees180(b)
Пример #11
0
    def to2ll(self):
        '''Convert this vector to (geodetic) lat- and longitude in C{degrees}.

           @return: A L{LatLon2Tuple}C{(lat, lon)}.

           @example:

           >>> v = Vector3d(0.500, 0.500, 0.707)
           >>> a, b = v.to2ll()  # 44.99567, 45.0
        '''
        a, b = self.to2ab()
        r = LatLon2Tuple(degrees90(a), degrees180(b))
        return self._xnamed(r)
Пример #12
0
def n_xyz2latlon(x, y, z):
    '''Convert C{n-vector} components to lat- and longitude in C{degrees}.

       @arg x: X component (C{scalar}).
       @arg y: Y component (C{scalar}).
       @arg z: Z component (C{scalar}).

       @return: A L{LatLon2Tuple}C{(lat, lon)}.

       @see: Function L{n_xyz2philam}.
    '''
    a, b = n_xyz2philam(x, y, z)  # PYCHOK PhiLam2Tuple
    return LatLon2Tuple(degrees90(a), degrees180(b))
Пример #13
0
    def toLatLon(self, LatLon=None, unfalse=True, **LatLon_kwds):
        '''Convert this UPS coordinate to an (ellipsoidal) geodetic point.

           @kwarg LatLon: Optional, ellipsoidal class to return the
                          geodetic point (C{LatLon}) or C{None}.
           @kwarg unfalse: Unfalse B{C{easting}} and B{C{northing}}
                           if falsed (C{bool}).
           @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
                               arguments, ignored if B{C{LatLon=None}}.

           @return: This UPS coordinate (B{C{LatLon}}) or if B{C{LatLon}}
                    is C{None}, a L{LatLonDatum5Tuple}C{(lat, lon, datum,
                    convergence, scale)}.

           @raise TypeError: If B{C{LatLon}} is not ellipsoidal.

           @raise UPSError: Invalid meridional radius or H-value.
        '''
        if self._latlon and self._latlon_args == unfalse:
            return self._latlon5(LatLon)

        E = self.datum.ellipsoid  # XXX vs LatLon.datum.ellipsoid

        x, y = self.to2en(falsed=not unfalse)

        r = hypot(x, y)
        t = (r / (2 * self.scale0 * E.a / E.es_c)) if r > 0 else EPS**2
        t = E.es_tauf((1 / t - t) * 0.5)
        if self._pole == _N_:
            a, b, c = atan(t), atan2(x, -y), 1
        else:
            a, b, c = -atan(t), atan2(x, y), -1

        a, b = degrees90(a), degrees180(b)
        if not self._band:
            self._band = _Band(a, b)
        if not self._hemisphere:
            self._hemisphere = _hemi(a)

        ll = _LLEB(a, b, datum=self._datum, name=self.name)
        ll._convergence = b * c  # gamma
        ll._scale = _scale(E, r, t) if r > 0 else self.scale0

        self._latlon_to(ll, unfalse)
        return self._latlon5(LatLon, **LatLon_kwds)
Пример #14
0
    def toLatLon(self, LatLon=None, datum=None, height=None):
        '''Convert this L{Lcc} to an (ellipsoidal) geodetic point.

           @kwarg LatLon: Optional, ellipsoidal class to return the
                          geodetic point (C{LatLon}) or C{None}.
           @kwarg datum: Optional datum to use, otherwise use this
                         B{C{Lcc}}'s conic.datum (L{Datum}, L{Ellipsoid},
                         L{Ellipsoid2} or L{a_f2Tuple}).
           @kwarg height: Optional height for the point, overriding
                          the default height (C{meter}).

           @return: The point (B{C{LatLon}}) or a
                    L{LatLon4Tuple}C{(lat, lon, height, datum)}
                    if B{C{LatLon}} is C{None}.

           @raise TypeError: If B{C{LatLon}} or B{C{datum}} is
                             not ellipsoidal or not valid.
        '''
        if LatLon:
            _xsubclassof(_LLEB, LatLon=LatLon)

        c = self.conic
        if datum not in (None, c.datum):
            c = c.toDatum(datum)

        e = self.easting - c._E0
        n = c._r0 - self.northing + c._N0

        r_ = copysign(hypot(e, n), c._n)
        t_ = pow(r_ / c._aF, c._n_)

        x = c._xdef(t_)  # XXX c._lam0
        while True:
            p, x = x, c._xdef(t_ * c._pdef(x))
            if abs(x - p) < 1e-9:  # XXX EPS too small?
                break
        lat = degrees90(x)
        lon = degrees180((atan(e / n) + c._opt3) * c._n_ + c._lam0)

        h = self.height if height is None else height
        d = c.datum

        r = LatLon4Tuple(lat, lon, h, d) if LatLon is None else \
                  LatLon(lat, lon, height=h, datum=d)
        return self._xnamed(r)
Пример #15
0
    def rhumbMidpointTo(self, other, height=None):
        '''Return the (loxodromic) midpoint between this and
           an other point.

           @arg other: The other point (spherical LatLon).
           @kwarg height: Optional height, overriding the mean height
                          (C{meter}).

           @return: The midpoint (spherical C{LatLon}).

           @raise TypeError: The I{other} point is not spherical.

           @raise ValueError: Invalid B{C{height}}.

           @example:

           >>> p = LatLon(51.127, 1.338)
           >>> q = LatLon(50.964, 1.853)
           >>> m = p.rhumb_midpointTo(q)
           >>> m.toStr()  # '51.0455°N, 001.5957°E'
        '''
        self.others(other)

        # see <https://MathForum.org/library/drmath/view/51822.html>
        a1, b1 = self.philam
        a2, b2 = other.philam
        if abs(b2 - b1) > PI:
            b1 += PI2  # crossing anti-meridian

        a3 = favg(a1, a2)
        b3 = favg(b1, b2)

        f1 = tanPI_2_2(a1)
        if abs(f1) > EPS:
            f2 = tanPI_2_2(a2)
            f = f2 / f1
            if abs(f) > EPS:
                f = log(f)
                if abs(f) > EPS:
                    f3 = tanPI_2_2(a3)
                    b3 = fsum_(b1 * log(f2), -b2 * log(f1),
                               (b2 - b1) * log(f3)) / f

        h = self._havg(other) if height is None else Height(height)
        return self.classof(degrees90(a3), degrees180(b3), height=h)
Пример #16
0
    def rhumbDestination(self, distance, bearing, radius=R_M, height=None):
        '''Return the destination point having travelled along a rhumb
           (loxodrome) line from this point the given distance on the
           given bearing.

           @arg distance: Distance travelled (C{meter}, same units as
                          I{radius}).
           @arg bearing: Bearing from this point (compass C{degrees360}).
           @kwarg radius: Mean earth radius (C{meter}).
           @kwarg height: Optional height, overriding the default height
                          (C{meter}, same unit as I{radius}).

           @return: The destination point (spherical C{LatLon}).

           @raise ValueError: Invalid B{C{distance}}, B{C{bearing}},
                              B{C{radius}} or B{C{height}}.

           @example:

           >>> p = LatLon(51.127, 1.338)
           >>> q = p.rhumbDestination(40300, 116.7)  # 50.9642°N, 001.8530°E

           @JSname: I{rhumbDestinationPoint}
        '''
        r = _angular(distance, radius)

        a1, b1 = self.philam
        sb, cb = sincos2(Bearing_(bearing))

        da = r * cb
        a2 = a1 + da
        # normalize latitude if past pole
        if a2 > PI_2:
            a2 = PI - a2
        elif a2 < -PI_2:
            a2 = -PI - a2

        dp = log(tanPI_2_2(a2) / tanPI_2_2(a1))
        # E-W course becomes ill-conditioned with 0/0
        q = (da / dp) if abs(dp) > EPS else cos(a1)
        b2 = (b1 + r * sb / q) if abs(q) > EPS else b1

        h = self.height if height is None else Height(height)
        return self.classof(degrees90(a2), degrees180(b2), height=h)
Пример #17
0
    def rhumbDestination(self, distance, bearing, radius=R_M, height=None):
        '''Return the destination point having travelled along a rhumb
           (loxodrome) line from this point the given distance on the
           given bearing.

           @param distance: Distance travelled (C{meter}, same units as
                            I{radius}).
           @param bearing: Bearing from this point (compass C{degrees360}).
           @keyword radius: Optional, mean earth radius (C{meter}).
           @keyword height: Optional height, overriding the default
                            height (C{meter}, same unit as I{radius}).

           @return: The destination point (spherical C{LatLon}).

           @example:

           >>> p = LatLon(51.127, 1.338)
           >>> q = p.rhumbDestination(40300, 116.7)  # 50.9642°N, 001.8530°E

           @JSname: I{rhumbDestinationPoint}
        '''
        a1, b1 = self.to2ab()

        r = float(distance) / float(radius)  # angular distance in radians

        sb, cb = sincos2d(bearing)

        da = r * cb
        a2 = a1 + da
        # normalize latitude if past pole
        if a2 > PI_2:
            a2 = PI - a2
        elif a2 < -PI_2:
            a2 = -PI - a2

        dp = log(tanPI_2_2(a2) / tanPI_2_2(a1))
        # E-W course becomes ill-conditioned with 0/0
        q = (da / dp) if abs(dp) > EPS else cos(a1)
        b2 = (b1 + r * sb / q) if abs(q) > EPS else b1

        h = self.height if height is None else height
        return self.classof(degrees90(a2), degrees180(b2), height=h)
Пример #18
0
    def midpointTo(self, other, height=None, wrap=False):
        '''Find the midpoint between this and an other point.

           @arg other: The other point (L{LatLon}).
           @kwarg height: Optional height for midpoint, overriding
                          the mean height (C{meter}).
           @kwarg wrap: Wrap and unroll longitudes (C{bool}).

           @return: Midpoint (L{LatLon}).

           @raise TypeError: The B{C{other}} point is not L{LatLon}.

           @raise ValueError: Invalid B{C{height}}.

           @example:

           >>> p1 = LatLon(52.205, 0.119)
           >>> p2 = LatLon(48.857, 2.351)
           >>> m = p1.midpointTo(p2)  # '50.5363°N, 001.2746°E'
        '''
        self.others(other)

        # see <https://MathForum.org/library/drmath/view/51822.html>
        a1, b1 = self.philam
        a2, b2 = other.philam

        db, b2 = unrollPI(b1, b2, wrap=wrap)

        sa1, ca1, sa2, ca2, sdb, cdb = sincos2(a1, a2, db)

        x = ca2 * cdb + ca1
        y = ca2 * sdb

        a = atan2(sa1 + sa2, hypot(x, y))
        b = atan2(y, x) + b1

        if height is None:
            h = self._havg(other)
        else:
            h = Height(height)
        return self.classof(degrees90(a), degrees180(b), height=h)
Пример #19
0
    def toLatLon(self, LatLon=None, datum=Datums.WGS84):
        '''Convert this OSGR coordinate to an (ellipsoidal) geodetic
           point.

           While OS grid references are based on the OSGB36 datum, the
           I{Ordnance Survey} have deprecated the use of OSGB36 for
           lat-/longitude coordinates (in favour of WGS84). Hence, this
           method returns WGS84 by default with OSGB36 as an option,
           U{see<https://www.OrdnanceSurvey.co.UK/blog/2014/12/2>}.

           I{Note formulation implemented here due to Thomas, Redfearn,
           etc. is as published by OS, but is inferior to Krüger as
           used by e.g. Karney 2011.}

           @kwarg LatLon: Optional ellipsoidal class to return the
                          geodetic point (C{LatLon}) or C{None}.
           @kwarg datum: Optional datum to convert to (L{Datum},
                         L{Ellipsoid}, L{Ellipsoid2}, L{Ellipsoid2}
                         or L{a_f2Tuple}).

           @return: The geodetic point (B{C{LatLon}}) or a
                    L{LatLonDatum3Tuple}C{(lat, lon, datum)}
                    if B{C{LatLon}} is C{None}.

           @raise OSGRError: No convergence.

           @raise TypeError: If B{C{LatLon}} is not ellipsoidal or
                             B{C{datum}} is invalid or conversion failed.

           @example:

           >>> from pygeodesy import ellipsoidalVincenty as eV
           >>> g = Osgr(651409.903, 313177.270)
           >>> p = g.toLatLon(eV.LatLon)  # 52°39′28.723″N, 001°42′57.787″E
           >>> # to obtain (historical) OSGB36 lat-/longitude point
           >>> p = g.toLatLon(eV.LatLon, datum=Datums.OSGB36)  # 52°39′27.253″N, 001°43′04.518″E
        '''
        if self._latlon:
            return self._latlon3(LatLon, datum)

        E = self.datum.ellipsoid  # _Datums_OSGB36.ellipsoid, Airy130
        a_F0 = E.a * _F0
        b_F0 = E.b * _F0

        e, n = self.easting, self.northing
        n_N0 = n - _N0

        a, m = _A0, n_N0
        sa = Fsum(a)
        for self._iteration in range(1, _TRIPS):
            a = sa.fsum_(m / a_F0)
            m = n_N0 - b_F0 * _M(E.Mabcd, a)  # meridional arc
            if abs(m) < _10um:
                break
        else:
            t = _dot_(_item_ps(self.classname, self.toStr(prec=-3)),
                      self.toLatLon.__name__)
            raise OSGRError(_no_convergence_, txt=t)
        sa, ca = sincos2(a)

        s = E.e2s2(sa)  # r, v = E.roc2_(sa, _F0)
        v = a_F0 / sqrt(s)  # nu
        r = v * E.e12 / s  # rho = a_F0 * E.e12 / pow(s, 1.5) == a_F0 * E.e12 / (s * sqrt(s))

        vr = v / r  # == s / E.e12
        x2 = vr - 1  # η2
        ta = tan(a)

        v3, v5, v7 = fpowers(v, 7, 3)  # PYCHOK false!
        ta2, ta4, ta6 = fpowers(ta**2, 3)  # PYCHOK false!

        tar = ta / r
        V4 = (a, tar / (2 * v), tar / (24 * v3) * fdot(
            (1, 3, -9), 5 + x2, ta2, ta2 * x2), tar / (720 * v5) * fdot(
                (61, 90, 45), 1, ta2, ta4))

        csa = 1.0 / ca
        X5 = (_B0, csa / v, csa / (6 * v3) * fsum_(vr, ta2, ta2),
              csa / (120 * v5) * fdot(
                  (5, 28, 24), 1, ta2, ta4), csa / (5040 * v7) * fdot(
                      (61, 662, 1320, 720), 1, ta2, ta4, ta6))

        d, d2, d3, d4, d5, d6, d7 = fpowers(e - _E0, 7)  # PYCHOK false!
        a = fdot(V4, 1, -d2, d4, -d6)
        b = fdot(X5, 1, d, -d3, d5, -d7)

        r = _LLEB(degrees90(a),
                  degrees180(b),
                  datum=self.datum,
                  name=self.name)
        r._iteration = self._iteration  # only ellipsoidal LatLon
        self._latlon = r
        return self._latlon3(LatLon, datum)
Пример #20
0
    def reverse(self, xyz, y=None, z=None, **no_M):  # PYCHOK unused M
        '''Convert from geocentric C{(x, y, z)} to geodetic C{(lat, lon, height)}
           transcribed from I{Chris Veness}' U{JavaScript
           <https://www.Movable-Type.co.UK/scripts/geodesy/docs/latlon-ellipsoidal.js.html>}.

           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.

           @arg xyz: Either an L{Ecef9Tuple}, an C{(x, y, z)} 3-tuple or C{scalar}
                     ECEF C{x} coordinate in C{meter}.
           @kwarg y: ECEF C{y} coordinate in C{meter} for C{scalar} B{C{xyz}} and B{C{z}}.
           @kwarg z: ECEF C{z} coordinate in C{meter} for C{scalar} B{C{xyz}} and B{C{y}}.
           @kwarg no_M: Rotation matrix C{M} not available.

           @return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)}
                    with geodetic coordinates C{(lat, lon, height)} for the given
                    geocentric ones C{(x, y, z)}, case C{C}, L{EcefMatrix} C{M}
                    always C{None} and C{datum} if available.

           @raise EcefError: If B{C{xyz}} not L{Ecef9Tuple} or C{scalar} C{x}
                             or B{C{y}} and/or B{C{z}} not C{scalar} for C{scalar}
                             B{C{xyz}}.

           @see: 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, both from Lawrence Livermore National Laboratory (LLNL).
        '''
        x, y, z, name = _xyzn4(xyz, y, z, Error=EcefError)

        E = self.ellipsoid

        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))

            C, lat, lon = 1, degrees90(a), degrees180(b)

        # see <https://GIS.StackExchange.com/questions/28446>
        elif p > EPS:  # lat arbitrarily zero
            C, lat, lon, h = 2, 0.0, degrees180(atan2(y, x)), p - E.a

        else:  # polar lat, lon arbitrarily zero
            C, lat, lon, h = 3, copysign(90.0, z), 0.0, abs(z) - E.b

        r = Ecef9Tuple(x, y, z, lat, lon, h, C, None, self.datum)
        return self._xnamed(r, name)
Пример #21
0
    def toLatLon(self, LatLon=None, eps=EPS, unfalse=True, **LatLon_kwds):
        '''Convert this UTM coordinate to an (ellipsoidal) geodetic point.

           @kwarg LatLon: Optional, ellipsoidal class to return the
                          geodetic point (C{LatLon}) or C{None}.
           @kwarg eps: Optional convergence limit, L{EPS} or above
                       (C{float}).
           @kwarg unfalse: Unfalse B{C{easting}} and B{C{northing}}
                           if falsed (C{bool}).
           @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
                               arguments, ignored if B{C{LatLon=None}}.

           @return: This UTM coordinate (B{C{LatLon}}) or if B{C{LatLon}}
                    is C{None}, a L{LatLonDatum5Tuple}C{(lat, lon, datum,
                    convergence, scale)}.

           @raise TypeError: If B{C{LatLon}} is not ellipsoidal.

           @raise UTMError: Invalid meridional radius or H-value.

           @example:

           >>> u = Utm(31, 'N', 448251.795, 5411932.678)
           >>> from pygeodesy import ellipsoidalVincenty as eV
           >>> ll = u.toLatLon(eV.LatLon)  # 48°51′29.52″N, 002°17′40.20″E
        '''
        if eps < EPS:
            eps = EPS  # less doesn't converge

        if self._latlon and self._latlon_args == (eps, unfalse):
            return self._latlon5(LatLon)

        E = self.datum.ellipsoid  # XXX vs LatLon.datum.ellipsoid

        x, y = self.to2en(falsed=not unfalse)

        # from Karney 2011 Eq 15-22, 36
        A0 = self.scale0 * E.A
        if A0 < EPS:
            raise self._Error(meridional=A0)
        x /= A0  # η eta
        y /= A0  # ξ ksi

        K = _Kseries(E.BetaKs, x, y)  # Krüger series
        y = -K.ys(-y)  # ξ'
        x = -K.xs(-x)  # η'

        shx = sinh(x)
        sy, cy = sincos2(y)

        H = hypot(shx, cy)
        if H < EPS:
            raise self._Error(H=H)

        T = t0 = sy / H  # τʹ
        S = Fsum(T)
        q = 1.0 / E.e12
        P = 7  # -/+ toggle trips
        d = 1.0 + eps
        while abs(d) > eps and P > 0:
            p = -d  # previous d, toggled
            h = hypot1(T)
            s = sinh(E.e * atanh(E.e * T / h))
            t = T * hypot1(s) - s * h
            d = (t0 - t) / hypot1(t) * ((q + T**2) / h)
            T, d = S.fsum2_(d)  # τi, (τi - τi-1)
            if d == p:  # catch -/+ toggling of d
                P -= 1
            # else:
            #   P = 0

        a = atan(T)  # lat
        b = atan2(shx, cy)
        if unfalse and self.falsed:
            b += radians(_cmlon(self.zone))
        ll = _LLEB(degrees90(a), degrees180(b), datum=self.datum, name=self.name)

        # convergence: Karney 2011 Eq 26, 27
        p = -K.ps(-1)
        q =  K.qs(0)
        ll._convergence = degrees(atan(tan(y) * tanh(x)) + atan2(q, p))

        # scale: Karney 2011 Eq 28
        ll._scale = E.e2s(sin(a)) * hypot1(T) * H * (A0 / E.a / hypot(p, q))

        self._latlon_to(ll, eps, unfalse)
        return self._latlon5(LatLon, **LatLon_kwds)
Пример #22
0
 def opt3(self):
     '''Get the optional meridian (C{degrees180}).
     '''
     return degrees180(self._opt3)
Пример #23
0
 def lon0(self):
     '''Get the central meridian (C{degrees180}).
     '''
     return degrees180(self._lam0)
Пример #24
0
 def _destination1(bearing):
     a, b = _destination2(a1, b1, r1, bearing)
     return _latlon3(degrees90(a), degrees180(b), h, intersections2, LatLon,
                     **LatLon_kwds)
Пример #25
0
    def toLatLon(self, LatLon=None, datum=Datums.WGS84):
        '''Convert this OSGR coordinate to an (ellipsoidal) geodetic
           point.

           I{Note formulation implemented here due to Thomas, Redfearn,
           etc. is as published by OS, but is inferior to Krüger as
           used by e.g. Karney 2011.}

           @keyword LatLon: Optional ellipsoidal (sub-)class to return
                            the point (C{LatLon}) or C{None}.
           @keyword datum: Optional datum to use (C{Datum}).

           @return: The geodetic point (B{C{LatLon}}) or a
                    L{LatLonDatum3Tuple}C{(lat, lon, datum)}
                    if B{C{LatLon}} is C{None}.

           @raise TypeError: If B{C{LatLon}} is not ellipsoidal or if
                             B{C{datum}} conversion failed.

           @example:

           >>> from pygeodesy import ellipsoidalVincenty as eV
           >>> g = Osgr(651409.903, 313177.270)
           >>> p = g.toLatLon(eV.LatLon)  # 52°39′28.723″N, 001°42′57.787″E
           >>> # to obtain (historical) OSGB36 lat-/longitude point
           >>> p = g.toLatLon(eV.LatLon, datum=Datums.OSGB36)  # 52°39′27.253″N, 001°43′04.518″E
        '''
        if self._latlon:
            return self._latlon3(LatLon, datum)

        E = _OSGB36.ellipsoid  # Airy130
        a_F0 = E.a * _F0
        b_F0 = E.b * _F0

        e, n = self._easting, self._northing
        n_N0 = n - _N0

        a, M = _A0, 0
        sa = Fsum(a)
        while True:
            t = n_N0 - M
            if t < _10um:
                break
            a = sa.fsum_(t / a_F0)
            M = b_F0 * _M(E.Mabcd, a)

        sa, ca = sincos2(a)

        s = E.e2s2(sa)  # v, r = E.roc2_(sa, _F0)
        v = a_F0 / sqrt(s)  # nu
        r = v * E.e12 / s  # rho

        vr = v / r  # == s / E.e12
        x2 = vr - 1  # η2
        ta = tan(a)

        v3, v5, v7 = fpowers(v, 7, 3)  # PYCHOK false!
        ta2, ta4, ta6 = fpowers(ta**2, 3)  # PYCHOK false!

        tar = ta / r
        V4 = (a,
              tar / (  2 * v),
              tar / ( 24 * v3) * fdot((1, 3, -9), 5 + x2, ta2, ta2 * x2),
              tar / (720 * v5) * fdot((61, 90, 45), 1, ta2, ta4))

        csa = 1.0 / ca
        X5 = (_B0,
              csa / v,
              csa / (   6 * v3) * fsum_(vr, ta, ta),
              csa / ( 120 * v5) * fdot((5, 28, 24), 1, ta2, ta4),
              csa / (5040 * v7) * fdot((61, 662, 1320, 720), ta, ta2, ta4, ta6))

        d, d2, d3, d4, d5, d6, d7 = fpowers(e - _E0, 7)  # PYCHOK false!
        a = fdot(V4, 1,    -d2, d4, -d6)
        b = fdot(X5, 1, d, -d3, d5, -d7)

        self._latlon = _LLEB(degrees90(a), degrees180(b), datum=_OSGB36, name=self.name)
        return self._latlon3(LatLon, datum)
Пример #26
0
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)