Beispiel #1
0
 def _transit(lon1, lon2):
     """Count crossings of prime meridian for AddPoint."""
     # Return 1 or -1 if crossing prime meridian in east or west direction.
     # Otherwise return zero.
     # Compute lon12 the same way as Geodesic::Inverse.
     lon1 = Math.AngNormalize(lon1)
     lon2 = Math.AngNormalize(lon2)
     lon12, _ = Math.AngDiff(lon1, lon2)
     cross = (1 if lon1 <= 0 and lon2 > 0 and lon12 > 0 else
              (-1 if lon2 <= 0 and lon1 > 0 and lon12 < 0 else 0))
     return cross
Beispiel #2
0
 def transit(lon1, lon2):
   # Return 1 or -1 if crossing prime meridian in east or west direction.
   # Otherwise return zero.
   from geographiclib.geodesic import Geodesic
   # Compute lon12 the same way as Geodesic::Inverse.
   lon1 = Math.AngNormalize(lon1);
   lon2 = Math.AngNormalize(lon2);
   lon12 = Math.AngDiff(lon1, lon2);
   cross = (1 if lon1 < 0 and lon2 >= 0 and lon12 > 0
            else (-1 if lon2 < 0 and lon1 >= 0 and lon12 < 0 else 0))
   return cross
Beispiel #3
0
  def ArcPosition(self, a12, outmask = GeodesicCapability.STANDARD):
    """Find the position on the line given *a12*

    :param a12: spherical arc length from the first point to the second
      in degrees
    :param outmask: the :ref:`output mask <outmask>`
    :return: a :ref:`dict`

    The default value of *outmask* is STANDARD, i.e., the *lat1*,
    *lon1*, *azi1*, *lat2*, *lon2*, *azi2*, *s12*, *a12* entries are
    returned.

    """

    from geographiclib.geodesic import Geodesic
    result = {'lat1': self.lat1,
              'lon1': self.lon1 if outmask & Geodesic.LONG_UNROLL else
              Math.AngNormalize(self.lon1),
              'azi1': self.azi1, 'a12': a12}
    a12, lat2, lon2, azi2, s12, m12, M12, M21, S12 = self._GenPosition(
      True, a12, outmask)
    outmask &= Geodesic.OUT_MASK
    if outmask & Geodesic.DISTANCE: result['s12'] = s12
    if outmask & Geodesic.LATITUDE: result['lat2'] = lat2
    if outmask & Geodesic.LONGITUDE: result['lon2'] = lon2
    if outmask & Geodesic.AZIMUTH: result['azi2'] = azi2
    if outmask & Geodesic.REDUCEDLENGTH: result['m12'] = m12
    if outmask & Geodesic.GEODESICSCALE:
      result['M12'] = M12; result['M21'] = M21
    if outmask & Geodesic.AREA: result['S12'] = S12
    return result
Beispiel #4
0
 def CheckPosition(lat, lon):
     """Check that lat and lon are legal and return normalized lon"""
     if (abs(lat) > 90):
         raise ValueError("latitude " + str(lat) + " not in [-90, 90]")
     if (lon < -540 or lon >= 540):
         raise ValueError("longitude " + str(lon) + " not in [-540, 540)")
     return Math.AngNormalize(lon)
Beispiel #5
0
 def CheckPosition(lat, lon):
   """Check that lat and lon are legal and return normalized lon"""
   if abs(lat) > 90:
     raise ValueError("latitude " + str(lat) + " not in [-90, 90]")
   # if not Math.isfinite(lon):
   #   raise ValueError("longitude " + str(lon) + " not a finite number")
   return Math.AngNormalize(lon)
Beispiel #6
0
  def Position(self, s12, outmask = GeodesicCapability.STANDARD):
    """Find the position on the line given *s12*

    :param s12: the distance from the first point to the second in
      meters
    :param outmask: the :ref:`output mask <outmask>`
    :return: a :ref:`dict`

    The default value of *outmask* is STANDARD, i.e., the *lat1*,
    *lon1*, *azi1*, *lat2*, *lon2*, *azi2*, *s12*, *a12* entries are
    returned.  The :class:`~geographiclib.geodesicline.GeodesicLine`
    object must have been constructed with the DISTANCE_IN capability.

    """

    from geographiclib.geodesic import Geodesic
    result = {'lat1': self.lat1,
              'lon1': self.lon1 if outmask & Geodesic.LONG_UNROLL else
              Math.AngNormalize(self.lon1),
              'azi1': self.azi1, 's12': s12}
    a12, lat2, lon2, azi2, s12, m12, M12, M21, S12 = self._GenPosition(
      False, s12, outmask)
    outmask &= Geodesic.OUT_MASK
    result['a12'] = a12
    if outmask & Geodesic.LATITUDE: result['lat2'] = lat2
    if outmask & Geodesic.LONGITUDE: result['lon2'] = lon2
    if outmask & Geodesic.AZIMUTH: result['azi2'] = azi2
    if outmask & Geodesic.REDUCEDLENGTH: result['m12'] = m12
    if outmask & Geodesic.GEODESICSCALE:
      result['M12'] = M12; result['M21'] = M21
    if outmask & Geodesic.AREA: result['S12'] = S12
    return result
Beispiel #7
0
  def ArcPosition(self, a12,
                  outmask = GeodesicCapability.LATITUDE |
                  GeodesicCapability.LONGITUDE | GeodesicCapability.AZIMUTH |
                  GeodesicCapability.DISTANCE):
    """Return the point a spherical arc length a12 along the geodesic line.
    Return a dictionary with (some) of the following entries:

      lat1 latitude of point 1
      lon1 longitude of point 1
      azi1 azimuth of line at point 1
      lat2 latitude of point 2
      lon2 longitude of point 2
      azi2 azimuth of line at point 2
      s12 distance from 1 to 2
      a12 arc length on auxiliary sphere from 1 to 2
      m12 reduced length of geodesic
      M12 geodesic scale 2 relative to 1
      M21 geodesic scale 1 relative to 2
      S12 area between geodesic and equator

    outmask determines which fields get included and if outmask is
    omitted, then only the basic geodesic fields are computed.  The
    LONG_UNROLL bit unrolls the longitudes (instead of reducing them to
    the range [-180,180)).  The mask is an or'ed combination of the
    following values

      Geodesic.LATITUDE
      Geodesic.LONGITUDE
      Geodesic.AZIMUTH
      Geodesic.DISTANCE
      Geodesic.REDUCEDLENGTH
      Geodesic.GEODESICSCALE
      Geodesic.AREA
      Geodesic.ALL (all of the above)
      Geodesic.LONG_UNROLL

    The default value of outmask is LATITUDE | LONGITUDE | AZIMUTH |
    DISTANCE.

    """

    from geographiclib.geodesic import Geodesic
    Geodesic.CheckDistance(a12)
    result = {'lat1': self._lat1,
              'lon1': self._lon1 if outmask & Geodesic.LONG_UNROLL else
              Math.AngNormalize(self._lon1),
              'azi1': self._azi1, 'a12': a12}
    a12, lat2, lon2, azi2, s12, m12, M12, M21, S12 = self.GenPosition(
      True, a12, outmask)
    outmask &= Geodesic.OUT_MASK
    if outmask & Geodesic.DISTANCE: result['s12'] = s12
    if outmask & Geodesic.LATITUDE: result['lat2'] = lat2
    if outmask & Geodesic.LONGITUDE: result['lon2'] = lon2
    if outmask & Geodesic.AZIMUTH: result['azi2'] = azi2
    if outmask & Geodesic.REDUCEDLENGTH: result['m12'] = m12
    if outmask & Geodesic.GEODESICSCALE:
      result['M12'] = M12; result['M21'] = M21
    if outmask & Geodesic.AREA: result['S12'] = S12
    return result
  def Position(self, s12,
               outmask = GeodesicCapability.LATITUDE |
               GeodesicCapability.LONGITUDE | GeodesicCapability.AZIMUTH):
    """Return the point a distance s12 along the geodesic line.  Return a
    dictionary with (some) of the following entries:

      lat1 latitude of point 1
      lon1 longitude of point 1
      azi1 azimuth of line at point 1
      lat2 latitude of point 2
      lon2 longitude of point 2
      azi2 azimuth of line at point 2
      s12 distance from 1 to 2
      a12 arc length on auxiliary sphere from 1 to 2
      m12 reduced length of geodesic
      M12 geodesic scale 2 relative to 1
      M21 geodesic scale 1 relative to 2
      S12 area between geodesic and equator

    outmask determines which fields get included and if outmask is
    omitted, then only the basic geodesic fields are computed.  The
    LONG_NOWRAP bit prevents the longitudes being reduced to the range
    [-180,180).  The mask is an or'ed combination of the following
    values

      Geodesic.LATITUDE
      Geodesic.LONGITUDE
      Geodesic.AZIMUTH
      Geodesic.REDUCEDLENGTH
      Geodesic.GEODESICSCALE
      Geodesic.AREA
      Geodesic.ALL
      Geodesic.LONG_NOWRAP

    """

    from geographiclib.geodesic import Geodesic
    Geodesic.CheckDistance(s12)
    result = {'lat1': self._lat1,
              'lon1': self._lon1 if outmask & Geodesic.LONG_NOWRAP else
              Math.AngNormalize(self._lon1),
              'azi1': self._azi1, 's12': s12}
    a12, lat2, lon2, azi2, s12, m12, M12, M21, S12 = self.GenPosition(
      False, s12, outmask)
    outmask &= Geodesic.OUT_MASK
    result['a12'] = a12
    if outmask & Geodesic.LATITUDE: result['lat2'] = lat2
    if outmask & Geodesic.LONGITUDE: result['lon2'] = lon2
    if outmask & Geodesic.AZIMUTH: result['azi2'] = azi2
    if outmask & Geodesic.REDUCEDLENGTH: result['m12'] = m12
    if outmask & Geodesic.GEODESICSCALE:
      result['M12'] = M12; result['M21'] = M21
    if outmask & Geodesic.AREA: result['S12'] = S12
    return result
Beispiel #9
0
  def Inverse(self, lat1, lon1, lat2, lon2,
              outmask = GeodesicCapability.STANDARD):
    """Solve the inverse geodesic problem

    :param lat1: latitude of the first point in degrees
    :param lon1: longitude of the first point in degrees
    :param lat2: latitude of the second point in degrees
    :param lon2: longitude of the second point in degrees
    :param outmask: the :ref:`output mask <outmask>`
    :return: a :ref:`dict`

    Compute geodesic between (*lat1*, *lon1*) and (*lat2*, *lon2*).
    The default value of *outmask* is STANDARD, i.e., the *lat1*,
    *lon1*, *azi1*, *lat2*, *lon2*, *azi2*, *s12*, *a12* entries are
    returned.

    """

    a12, s12, salp1,calp1, salp2,calp2, m12, M12, M21, S12 = self._GenInverse(
      lat1, lon1, lat2, lon2, outmask)
    outmask &= Geodesic.OUT_MASK
    if outmask & Geodesic.LONG_UNROLL:
      lon12, e = Math.AngDiff(lon1, lon2)
      lon2 = (lon1 + lon12) + e
    else:
      lon2 = Math.AngNormalize(lon2)
    result = {'lat1': Math.LatFix(lat1),
              'lon1': lon1 if outmask & Geodesic.LONG_UNROLL else
              Math.AngNormalize(lon1),
              'lat2': Math.LatFix(lat2),
              'lon2': lon2}
    result['a12'] = a12
    if outmask & Geodesic.DISTANCE: result['s12'] = s12
    if outmask & Geodesic.AZIMUTH:
      result['azi1'] = Math.atan2d(salp1, calp1)
      result['azi2'] = Math.atan2d(salp2, calp2)
    if outmask & Geodesic.REDUCEDLENGTH: result['m12'] = m12
    if outmask & Geodesic.GEODESICSCALE:
      result['M12'] = M12; result['M21'] = M21
    if outmask & Geodesic.AREA: result['S12'] = S12
    return result
Beispiel #10
0
 def __init__(self, lat0, lon0, h0=0):
     self.lat0 = lat0
     self.lon0 = geomath.AngNormalize(lon0)
     self.h0 = h0
     self.origin = (earth_forward(self.lat0, self.lon0, self.h0))
     phi = math.radians(lat0)
     lam = math.radians(lon0)
     sphi = math.sin(phi)
     cphi = 0 if abs(self.lat0) == 90 else math.cos(phi)
     slam = 0 if self.lon0 == -180 else math.sin(lam)
     clam = 0 if abs(self.lon0) == 90 else math.cos(lam)
     self.rot = geocentric_rotation(sphi, cphi, slam, clam)
Beispiel #11
0
  def ArcDirect(self, lat1, lon1, azi1, a12,
                outmask = GeodesicCapability.STANDARD):
    """Solve the direct geodesic problem in terms of spherical arc length

    :param lat1: latitude of the first point in degrees
    :param lon1: longitude of the first point in degrees
    :param azi1: azimuth at the first point in degrees
    :param a12: spherical arc length from the first point to the second
      in degrees
    :param outmask: the :ref:`output mask <outmask>`
    :return: a :ref:`dict`

    Compute geodesic starting at (*lat1*, *lon1*) with azimuth *azi1*
    and arc length *a12*.  The default value of *outmask* is STANDARD,
    i.e., the *lat1*, *lon1*, *azi1*, *lat2*, *lon2*, *azi2*, *s12*,
    *a12* entries are returned.

    """

    a12, lat2, lon2, azi2, s12, m12, M12, M21, S12 = self._GenDirect(
      lat1, lon1, azi1, True, a12, outmask)
    outmask &= Geodesic.OUT_MASK
    result = {'lat1': Math.LatFix(lat1),
              'lon1': lon1 if outmask & Geodesic.LONG_UNROLL else
              Math.AngNormalize(lon1),
              'azi1': Math.AngNormalize(azi1),
              'a12': a12}
    if outmask & Geodesic.DISTANCE: result['s12'] = s12
    if outmask & Geodesic.LATITUDE: result['lat2'] = lat2
    if outmask & Geodesic.LONGITUDE: result['lon2'] = lon2
    if outmask & Geodesic.AZIMUTH: result['azi2'] = azi2
    if outmask & Geodesic.REDUCEDLENGTH: result['m12'] = m12
    if outmask & Geodesic.GEODESICSCALE:
      result['M12'] = M12; result['M21'] = M21
    if outmask & Geodesic.AREA: result['S12'] = S12
    return result
Beispiel #12
0
def earth_forward(lat, lon, h):
    """Geocentric::IntForward"""
    lon = geomath.AngNormalize(lon)
    phi = math.radians(lat)
    lam = math.radians(lon)
    sphi = math.sin(phi)
    cphi = 0 if abs(lat) == 90 else math.cos(phi)
    _a = geoconst.WGS84_a
    _f = geoconst.WGS84_f
    _e2 = _f * (2 - _f)
    _e2m = 1 - _e2
    n = _a/math.sqrt(1 - _e2 * sphi * sphi)
    slam = 0 if lon == -180 else math.sin(lam)
    clam = 0 if abs(lon) == 90 else math.cos(lam)
    Z = (_e2m * n + h) * sphi
    X = (n + h) * cphi
    Y = X * slam
    X *= clam
    return numpy.array([X, Y, Z])
Beispiel #13
0
    def GenPosition(self, arcmode, s12_a12, outmask):

        from geographiclib.geodesic import Geodesic
        a12 = lat2 = lon2 = azi2 = s12 = m12 = M12 = M21 = S12 = Math.nan
        outmask &= self._caps & Geodesic.OUT_ALL
        if not (arcmode or
                (self._caps & Geodesic.DISTANCE_IN & Geodesic.OUT_ALL)):
            # Uninitialized or impossible distance calculation requested
            return a12, lat2, lon2, azi2, s12, m12, M12, M21, S12

        # Avoid warning about uninitialized B12.
        B12 = 0
        AB1 = 0
        if arcmode:
            # Interpret s12_a12 as spherical arc length
            sig12 = s12_a12 * Math.degree
            s12a = abs(s12_a12)
            s12a -= 180 * math.floor(s12a / 180)
            ssig12 = 0 if s12a == 0 else math.sin(sig12)
            csig12 = 0 if s12a == 90 else math.cos(sig12)
        else:
            # Interpret s12_a12 as distance
            tau12 = s12_a12 / (self._b * (1 + self._A1m1))
            s = math.sin(tau12)
            c = math.cos(tau12)
            # tau2 = tau1 + tau12
            B12 = -Geodesic.SinCosSeries(
                True, self._stau1 * c + self._ctau1 * s,
                self._ctau1 * c - self._stau1 * s, self._C1pa, Geodesic.nC1p_)
            sig12 = tau12 - (B12 - self._B11)
            ssig12 = math.sin(sig12)
            csig12 = math.cos(sig12)
            if abs(self._f) > 0.01:
                # Reverted distance series is inaccurate for |f| > 1/100, so correct
                # sig12 with 1 Newton iteration.  The following table shows the
                # approximate maximum error for a = WGS_a() and various f relative to
                # GeodesicExact.
                #     erri = the error in the inverse solution (nm)
                #     errd = the error in the direct solution (series only) (nm)
                #     errda = the error in the direct solution (series + 1 Newton) (nm)
                #
                #       f     erri  errd errda
                #     -1/5    12e6 1.2e9  69e6
                #     -1/10  123e3  12e6 765e3
                #     -1/20   1110 108e3  7155
                #     -1/50  18.63 200.9 27.12
                #     -1/100 18.63 23.78 23.37
                #     -1/150 18.63 21.05 20.26
                #      1/150 22.35 24.73 25.83
                #      1/100 22.35 25.03 25.31
                #      1/50  29.80 231.9 30.44
                #      1/20   5376 146e3  10e3
                #      1/10  829e3  22e6 1.5e6
                #      1/5   157e6 3.8e9 280e6
                ssig2 = self._ssig1 * csig12 + self._csig1 * ssig12
                csig2 = self._csig1 * csig12 - self._ssig1 * ssig12
                B12 = Geodesic.SinCosSeries(True, ssig2, csig2, self._C1a,
                                            Geodesic.nC1_)
                serr = ((1 + self._A1m1) * (sig12 + (B12 - self._B11)) -
                        s12_a12 / self._b)
                sig12 = sig12 - serr / math.sqrt(1 + self._k2 * Math.sq(ssig2))
                ssig12 = math.sin(sig12)
                csig12 = math.cos(sig12)
                # Update B12 below

        # real omg12, lam12, lon12
        # real ssig2, csig2, sbet2, cbet2, somg2, comg2, salp2, calp2
        # sig2 = sig1 + sig12
        ssig2 = self._ssig1 * csig12 + self._csig1 * ssig12
        csig2 = self._csig1 * csig12 - self._ssig1 * ssig12
        dn2 = math.sqrt(1 + self._k2 * Math.sq(ssig2))
        if outmask & (Geodesic.DISTANCE | Geodesic.REDUCEDLENGTH
                      | Geodesic.GEODESICSCALE):
            if arcmode or abs(self._f) > 0.01:
                B12 = Geodesic.SinCosSeries(True, ssig2, csig2, self._C1a,
                                            Geodesic.nC1_)
            AB1 = (1 + self._A1m1) * (B12 - self._B11)
        # sin(bet2) = cos(alp0) * sin(sig2)
        sbet2 = self._calp0 * ssig2
        # Alt: cbet2 = hypot(csig2, salp0 * ssig2)
        cbet2 = math.hypot(self._salp0, self._calp0 * csig2)
        if cbet2 == 0:
            # I.e., salp0 = 0, csig2 = 0.  Break the degeneracy in this case
            cbet2 = csig2 = Geodesic.tiny_
        # tan(omg2) = sin(alp0) * tan(sig2)
        somg2 = self._salp0 * ssig2
        comg2 = csig2  # No need to normalize
        # tan(alp0) = cos(sig2)*tan(alp2)
        salp2 = self._salp0
        calp2 = self._calp0 * csig2  # No need to normalize
        # omg12 = omg2 - omg1
        omg12 = math.atan2(somg2 * self._comg1 - comg2 * self._somg1,
                           comg2 * self._comg1 + somg2 * self._somg1)

        if outmask & Geodesic.DISTANCE:
            s12 = self._b * (
                (1 + self._A1m1) * sig12 + AB1) if arcmode else s12_a12

        if outmask & Geodesic.LONGITUDE:
            lam12 = omg12 + self._A3c * (sig12 + (Geodesic.SinCosSeries(
                True, ssig2, csig2, self._C3a, Geodesic.nC3_ - 1) - self._B31))
            lon12 = lam12 / Math.degree
            # Use Math.AngNormalize2 because longitude might have wrapped
            # multiple times.
            lon12 = Math.AngNormalize2(lon12)
            lon2 = Math.AngNormalize(self._lon1 + lon12)

        if outmask & Geodesic.LATITUDE:
            lat2 = math.atan2(sbet2, self._f1 * cbet2) / Math.degree

        if outmask & Geodesic.AZIMUTH:
            # minus signs give range [-180, 180). 0- converts -0 to +0.
            azi2 = 0 - math.atan2(-salp2, calp2) / Math.degree

        if outmask & (Geodesic.REDUCEDLENGTH | Geodesic.GEODESICSCALE):
            B22 = Geodesic.SinCosSeries(True, ssig2, csig2, self._C2a,
                                        Geodesic.nC2_)
            AB2 = (1 + self._A2m1) * (B22 - self._B21)
            J12 = (self._A1m1 - self._A2m1) * sig12 + (AB1 - AB2)
            if outmask & Geodesic.REDUCEDLENGTH:
                # Add parens around (_csig1 * ssig2) and (_ssig1 * csig2) to ensure
                # accurate cancellation in the case of coincident points.
                m12 = self._b * (
                    (dn2 * (self._csig1 * ssig2) - self._dn1 *
                     (self._ssig1 * csig2)) - self._csig1 * csig2 * J12)
            if outmask & Geodesic.GEODESICSCALE:
                t = (self._k2 * (ssig2 - self._ssig1) * (ssig2 + self._ssig1) /
                     (self._dn1 + dn2))
                M12 = csig12 + (t * ssig2 -
                                csig2 * J12) * self._ssig1 / self._dn1
                M21 = csig12 - (t * self._ssig1 -
                                self._csig1 * J12) * ssig2 / dn2

        if outmask & Geodesic.AREA:
            B42 = Geodesic.SinCosSeries(False, ssig2, csig2, self._C4a,
                                        Geodesic.nC4_)
            # real salp12, calp12
            if self._calp0 == 0 or self._salp0 == 0:
                # alp12 = alp2 - alp1, used in atan2 so no need to normalized
                salp12 = salp2 * self._calp1 - calp2 * self._salp1
                calp12 = calp2 * self._calp1 + salp2 * self._salp1
                # The right thing appears to happen if alp1 = +/-180 and alp2 = 0, viz
                # salp12 = -0 and alp12 = -180.  However this depends on the sign being
                # attached to 0 correctly.  The following ensures the correct behavior.
                if salp12 == 0 and calp12 < 0:
                    salp12 = Geodesic.tiny_ * self._calp1
                    calp12 = -1
            else:
                # tan(alp) = tan(alp0) * sec(sig)
                # tan(alp2-alp1) = (tan(alp2) -tan(alp1)) / (tan(alp2)*tan(alp1)+1)
                # = calp0 * salp0 * (csig1-csig2) / (salp0^2 + calp0^2 * csig1*csig2)
                # If csig12 > 0, write
                #   csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1)
                # else
                #   csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1
                # No need to normalize
                salp12 = self._calp0 * self._salp0 * (
                    self._csig1 * (1 - csig12) +
                    ssig12 * self._ssig1 if csig12 <= 0 else ssig12 *
                    (self._csig1 * ssig12 / (1 + csig12) + self._ssig1))
                calp12 = (Math.sq(self._salp0) +
                          Math.sq(self._calp0) * self._csig1 * csig2)
            S12 = self._c2 * math.atan2(salp12,
                                        calp12) + self._A4 * (B42 - self._B41)

        a12 = s12_a12 if arcmode else sig12 / Math.degree
        return a12, lat2, lon2, azi2, s12, m12, M12, M21, S12
    def _GenPosition(self, arcmode, s12_a12, outmask):
        """Private: General solution of position along geodesic"""
        from geographiclib.geodesic import Geodesic
        a12 = lat2 = lon2 = azi2 = s12 = m12 = M12 = M21 = S12 = Math.nan
        outmask &= self.caps & Geodesic.OUT_MASK
        if not (arcmode or (self.caps &
                            (Geodesic.OUT_MASK & Geodesic.DISTANCE_IN))):
            # Uninitialized or impossible distance calculation requested
            return a12, lat2, lon2, azi2, s12, m12, M12, M21, S12

        # Avoid warning about uninitialized B12.
        B12 = 0.0
        AB1 = 0.0
        if arcmode:
            # Interpret s12_a12 as spherical arc length
            sig12 = math.radians(s12_a12)
            ssig12, csig12 = Math.sincosd(s12_a12)
        else:
            # Interpret s12_a12 as distance
            tau12 = s12_a12 / (self._b * (1 + self._A1m1))
            s = math.sin(tau12)
            c = math.cos(tau12)
            # tau2 = tau1 + tau12
            B12 = -Geodesic._SinCosSeries(
                True, self._stau1 * c + self._ctau1 * s,
                self._ctau1 * c - self._stau1 * s, self._C1pa)
            sig12 = tau12 - (B12 - self._B11)
            ssig12 = math.sin(sig12)
            csig12 = math.cos(sig12)
            if abs(self.f) > 0.01:
                # Reverted distance series is inaccurate for |f| > 1/100, so correct
                # sig12 with 1 Newton iteration.  The following table shows the
                # approximate maximum error for a = WGS_a() and various f relative to
                # GeodesicExact.
                #     erri = the error in the inverse solution (nm)
                #     errd = the error in the direct solution (series only) (nm)
                #     errda = the error in the direct solution (series + 1 Newton) (nm)
                #
                #       f     erri  errd errda
                #     -1/5    12e6 1.2e9  69e6
                #     -1/10  123e3  12e6 765e3
                #     -1/20   1110 108e3  7155
                #     -1/50  18.63 200.9 27.12
                #     -1/100 18.63 23.78 23.37
                #     -1/150 18.63 21.05 20.26
                #      1/150 22.35 24.73 25.83
                #      1/100 22.35 25.03 25.31
                #      1/50  29.80 231.9 30.44
                #      1/20   5376 146e3  10e3
                #      1/10  829e3  22e6 1.5e6
                #      1/5   157e6 3.8e9 280e6
                ssig2 = self._ssig1 * csig12 + self._csig1 * ssig12
                csig2 = self._csig1 * csig12 - self._ssig1 * ssig12
                B12 = Geodesic._SinCosSeries(True, ssig2, csig2, self._C1a)
                serr = ((1 + self._A1m1) * (sig12 + (B12 - self._B11)) -
                        s12_a12 / self._b)
                sig12 = sig12 - serr / math.sqrt(1 + self._k2 * Math.sq(ssig2))
                ssig12 = math.sin(sig12)
                csig12 = math.cos(sig12)
                # Update B12 below

        # real omg12, lam12, lon12
        # real ssig2, csig2, sbet2, cbet2, somg2, comg2, salp2, calp2
        # sig2 = sig1 + sig12
        ssig2 = self._ssig1 * csig12 + self._csig1 * ssig12
        csig2 = self._csig1 * csig12 - self._ssig1 * ssig12
        dn2 = math.sqrt(1 + self._k2 * Math.sq(ssig2))
        if outmask & (Geodesic.DISTANCE | Geodesic.REDUCEDLENGTH
                      | Geodesic.GEODESICSCALE):
            if arcmode or abs(self.f) > 0.01:
                B12 = Geodesic._SinCosSeries(True, ssig2, csig2, self._C1a)
            AB1 = (1 + self._A1m1) * (B12 - self._B11)
        # sin(bet2) = cos(alp0) * sin(sig2)
        sbet2 = self._calp0 * ssig2
        # Alt: cbet2 = hypot(csig2, salp0 * ssig2)
        cbet2 = math.hypot(self._salp0, self._calp0 * csig2)
        if cbet2 == 0:
            # I.e., salp0 = 0, csig2 = 0.  Break the degeneracy in this case
            cbet2 = csig2 = Geodesic.tiny_
        # tan(alp0) = cos(sig2)*tan(alp2)
        salp2 = self._salp0
        calp2 = self._calp0 * csig2  # No need to normalize

        if outmask & Geodesic.DISTANCE:
            s12 = self._b * (
                (1 + self._A1m1) * sig12 + AB1) if arcmode else s12_a12

        if outmask & Geodesic.LONGITUDE:
            # tan(omg2) = sin(alp0) * tan(sig2)
            somg2 = self._salp0 * ssig2
            comg2 = csig2  # No need to normalize
            E = Math.copysign(1, self._salp0)  # East or west going?
            # omg12 = omg2 - omg1
            omg12 = (E *
                     (sig12 - (math.atan2(ssig2, csig2) -
                               math.atan2(self._ssig1, self._csig1)) +
                      (math.atan2(E * somg2, comg2) -
                       math.atan2(E * self._somg1, self._comg1))) if outmask
                     & Geodesic.LONG_UNROLL else math.atan2(
                         somg2 * self._comg1 -
                         comg2 * self._somg1, comg2 * self._comg1 +
                         somg2 * self._somg1))
            lam12 = omg12 + self._A3c * (sig12 + (Geodesic._SinCosSeries(
                True, ssig2, csig2, self._C3a) - self._B31))
            lon12 = math.degrees(lam12)
            lon2 = (self.lon1 + lon12 if outmask
                    & Geodesic.LONG_UNROLL else Math.AngNormalize(
                        Math.AngNormalize(self.lon1) +
                        Math.AngNormalize(lon12)))

        if outmask & Geodesic.LATITUDE:
            lat2 = Math.atan2d(sbet2, self._f1 * cbet2)

        if outmask & Geodesic.AZIMUTH:
            azi2 = Math.atan2d(salp2, calp2)

        if outmask & (Geodesic.REDUCEDLENGTH | Geodesic.GEODESICSCALE):
            B22 = Geodesic._SinCosSeries(True, ssig2, csig2, self._C2a)
            AB2 = (1 + self._A2m1) * (B22 - self._B21)
            J12 = (self._A1m1 - self._A2m1) * sig12 + (AB1 - AB2)
            if outmask & Geodesic.REDUCEDLENGTH:
                # Add parens around (_csig1 * ssig2) and (_ssig1 * csig2) to ensure
                # accurate cancellation in the case of coincident points.
                m12 = self._b * (
                    (dn2 * (self._csig1 * ssig2) - self._dn1 *
                     (self._ssig1 * csig2)) - self._csig1 * csig2 * J12)
            if outmask & Geodesic.GEODESICSCALE:
                t = (self._k2 * (ssig2 - self._ssig1) * (ssig2 + self._ssig1) /
                     (self._dn1 + dn2))
                M12 = csig12 + (t * ssig2 -
                                csig2 * J12) * self._ssig1 / self._dn1
                M21 = csig12 - (t * self._ssig1 -
                                self._csig1 * J12) * ssig2 / dn2

        if outmask & Geodesic.AREA:
            B42 = Geodesic._SinCosSeries(False, ssig2, csig2, self._C4a)
            # real salp12, calp12
            if self._calp0 == 0 or self._salp0 == 0:
                # alp12 = alp2 - alp1, used in atan2 so no need to normalize
                salp12 = salp2 * self.calp1 - calp2 * self.salp1
                calp12 = calp2 * self.calp1 + salp2 * self.salp1
            else:
                # tan(alp) = tan(alp0) * sec(sig)
                # tan(alp2-alp1) = (tan(alp2) -tan(alp1)) / (tan(alp2)*tan(alp1)+1)
                # = calp0 * salp0 * (csig1-csig2) / (salp0^2 + calp0^2 * csig1*csig2)
                # If csig12 > 0, write
                #   csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1)
                # else
                #   csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1
                # No need to normalize
                salp12 = self._calp0 * self._salp0 * (
                    self._csig1 * (1 - csig12) +
                    ssig12 * self._ssig1 if csig12 <= 0 else ssig12 *
                    (self._csig1 * ssig12 / (1 + csig12) + self._ssig1))
                calp12 = (Math.sq(self._salp0) +
                          Math.sq(self._calp0) * self._csig1 * csig2)
            S12 = (self._c2 * math.atan2(salp12, calp12) + self._A4 *
                   (B42 - self._B41))

        a12 = s12_a12 if arcmode else math.degrees(sig12)
        return a12, lat2, lon2, azi2, s12, m12, M12, M21, S12
Beispiel #15
0
 def CheckAzimuth(azi):
   """Check that azi is legal and return normalized value"""
   # if not Math.isfinite(azi):
   #   raise ValueError("azimuth " + str(azi) + " not a finite number")
   return Math.AngNormalize(azi)
Beispiel #16
0
  def __init__(self, geod, lat1, lon1, azi1, caps = GeodesicCapability.ALL):
    """Construct a GeodesicLine object describing a geodesic line
    starting at (lat1, lon1) with azimuth azi1.  geod is a Geodesic
    object (which embodies the ellipsoid parameters).  caps is caps is
    an or'ed combination of bit the following values indicating the
    capabilities of the returned object

      Geodesic.LATITUDE
      Geodesic.LONGITUDE
      Geodesic.AZIMUTH
      Geodesic.DISTANCE
      Geodesic.REDUCEDLENGTH
      Geodesic.GEODESICSCALE
      Geodesic.AREA
      Geodesic.DISTANCE_IN
      Geodesic.ALL (all of the above)

    The default value of caps is ALL.

    """

    from geographiclib.geodesic import Geodesic
    self._a = geod._a
    self._f = geod._f
    self._b = geod._b
    self._c2 = geod._c2
    self._f1 = geod._f1
    self._caps = (caps | Geodesic.LATITUDE | Geodesic.AZIMUTH |
                  Geodesic.LONG_UNROLL)

    # Guard against underflow in salp0
    self._lat1 = Math.LatFix(lat1)
    self._lon1 = lon1
    self._azi1 = Math.AngNormalize(azi1)
    self._salp1, self._calp1 = Math.sincosd(Math.AngRound(azi1))

    # real cbet1, sbet1
    sbet1, cbet1 = Math.sincosd(Math.AngRound(lat1)); sbet1 *= self._f1
    # Ensure cbet1 = +epsilon at poles
    sbet1, cbet1 = Math.norm(sbet1, cbet1); cbet1 = max(Geodesic.tiny_, cbet1)
    self._dn1 = math.sqrt(1 + geod._ep2 * Math.sq(sbet1))

    # Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0),
    self._salp0 = self._salp1 * cbet1 # alp0 in [0, pi/2 - |bet1|]
    # Alt: calp0 = hypot(sbet1, calp1 * cbet1).  The following
    # is slightly better (consider the case salp1 = 0).
    self._calp0 = math.hypot(self._calp1, self._salp1 * sbet1)
    # Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1).
    # sig = 0 is nearest northward crossing of equator.
    # With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line).
    # With bet1 =  pi/2, alp1 = -pi, sig1 =  pi/2
    # With bet1 = -pi/2, alp1 =  0 , sig1 = -pi/2
    # Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1).
    # With alp0 in (0, pi/2], quadrants for sig and omg coincide.
    # No atan2(0,0) ambiguity at poles since cbet1 = +epsilon.
    # With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi.
    self._ssig1 = sbet1; self._somg1 = self._salp0 * sbet1
    self._csig1 = self._comg1 = (cbet1 * self._calp1
                                 if sbet1 != 0 or self._calp1 != 0 else 1)
    # sig1 in (-pi, pi]
    self._ssig1, self._csig1 = Math.norm(self._ssig1, self._csig1)
    # No need to normalize
    # self._somg1, self._comg1 = Math.norm(self._somg1, self._comg1)

    self._k2 = Math.sq(self._calp0) * geod._ep2
    eps = self._k2 / (2 * (1 + math.sqrt(1 + self._k2)) + self._k2)

    if self._caps & Geodesic.CAP_C1:
      self._A1m1 = Geodesic.A1m1f(eps)
      self._C1a = list(range(Geodesic.nC1_ + 1))
      Geodesic.C1f(eps, self._C1a)
      self._B11 = Geodesic.SinCosSeries(
        True, self._ssig1, self._csig1, self._C1a)
      s = math.sin(self._B11); c = math.cos(self._B11)
      # tau1 = sig1 + B11
      self._stau1 = self._ssig1 * c + self._csig1 * s
      self._ctau1 = self._csig1 * c - self._ssig1 * s
      # Not necessary because C1pa reverts C1a
      #    _B11 = -SinCosSeries(true, _stau1, _ctau1, _C1pa)

    if self._caps & Geodesic.CAP_C1p:
      self._C1pa = list(range(Geodesic.nC1p_ + 1))
      Geodesic.C1pf(eps, self._C1pa)

    if self._caps & Geodesic.CAP_C2:
      self._A2m1 = Geodesic.A2m1f(eps)
      self._C2a = list(range(Geodesic.nC2_ + 1))
      Geodesic.C2f(eps, self._C2a)
      self._B21 = Geodesic.SinCosSeries(
        True, self._ssig1, self._csig1, self._C2a)

    if self._caps & Geodesic.CAP_C3:
      self._C3a = list(range(Geodesic.nC3_))
      geod.C3f(eps, self._C3a)
      self._A3c = -self._f * self._salp0 * geod.A3f(eps)
      self._B31 = Geodesic.SinCosSeries(
        True, self._ssig1, self._csig1, self._C3a)

    if self._caps & Geodesic.CAP_C4:
      self._C4a = list(range(Geodesic.nC4_))
      geod.C4f(eps, self._C4a)
      # Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0)
      self._A4 = Math.sq(self._a) * self._calp0 * self._salp0 * geod._e2
      self._B41 = Geodesic.SinCosSeries(
        False, self._ssig1, self._csig1, self._C4a)
Beispiel #17
0
    def __init__(self, geod, lat1, lon1, azi1, caps=GeodesicCapability.ALL):
        from geographiclib.geodesic import Geodesic
        self._a = geod._a
        self._f = geod._f
        self._b = geod._b
        self._c2 = geod._c2
        self._f1 = geod._f1
        self._caps = caps | Geodesic.LATITUDE | Geodesic.AZIMUTH

        # Guard against underflow in salp0
        azi1 = Geodesic.AngRound(Math.AngNormalize(azi1))
        lon1 = Math.AngNormalize(lon1)
        self._lat1 = lat1
        self._lon1 = lon1
        self._azi1 = azi1
        # alp1 is in [0, pi]
        alp1 = azi1 * Math.degree
        # Enforce sin(pi) == 0 and cos(pi/2) == 0.  Better to face the ensuing
        # problems directly than to skirt them.
        self._salp1 = 0 if azi1 == -180 else math.sin(alp1)
        self._calp1 = 0 if abs(azi1) == 90 else math.cos(alp1)
        # real cbet1, sbet1, phi
        phi = lat1 * Math.degree
        # Ensure cbet1 = +epsilon at poles
        sbet1 = self._f1 * math.sin(phi)
        cbet1 = Geodesic.tiny_ if abs(lat1) == 90 else math.cos(phi)
        sbet1, cbet1 = Geodesic.SinCosNorm(sbet1, cbet1)
        self._dn1 = math.sqrt(1 + geod._ep2 * Math.sq(sbet1))

        # Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0),
        self._salp0 = self._salp1 * cbet1  # alp0 in [0, pi/2 - |bet1|]
        # Alt: calp0 = hypot(sbet1, calp1 * cbet1).  The following
        # is slightly better (consider the case salp1 = 0).
        self._calp0 = math.hypot(self._calp1, self._salp1 * sbet1)
        # Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1).
        # sig = 0 is nearest northward crossing of equator.
        # With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line).
        # With bet1 =  pi/2, alp1 = -pi, sig1 =  pi/2
        # With bet1 = -pi/2, alp1 =  0 , sig1 = -pi/2
        # Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1).
        # With alp0 in (0, pi/2], quadrants for sig and omg coincide.
        # No atan2(0,0) ambiguity at poles since cbet1 = +epsilon.
        # With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi.
        self._ssig1 = sbet1
        self._somg1 = self._salp0 * sbet1
        self._csig1 = self._comg1 = (cbet1 * self._calp1
                                     if sbet1 != 0 or self._calp1 != 0 else 1)
        # sig1 in (-pi, pi]
        self._ssig1, self._csig1 = Geodesic.SinCosNorm(self._ssig1,
                                                       self._csig1)
        # No need to normalize
        # self._somg1, self._comg1 = Geodesic.SinCosNorm(self._somg1, self._comg1)

        self._k2 = Math.sq(self._calp0) * geod._ep2
        eps = self._k2 / (2 * (1 + math.sqrt(1 + self._k2)) + self._k2)

        if self._caps & Geodesic.CAP_C1:
            self._A1m1 = Geodesic.A1m1f(eps)
            self._C1a = range(Geodesic.nC1_ + 1)
            Geodesic.C1f(eps, self._C1a)
            self._B11 = Geodesic.SinCosSeries(True, self._ssig1, self._csig1,
                                              self._C1a, Geodesic.nC1_)
            s = math.sin(self._B11)
            c = math.cos(self._B11)
            # tau1 = sig1 + B11
            self._stau1 = self._ssig1 * c + self._csig1 * s
            self._ctau1 = self._csig1 * c - self._ssig1 * s
            # Not necessary because C1pa reverts C1a
            #    _B11 = -SinCosSeries(true, _stau1, _ctau1, _C1pa, nC1p_)

        if self._caps & Geodesic.CAP_C1p:
            self._C1pa = range(Geodesic.nC1p_ + 1)
            Geodesic.C1pf(eps, self._C1pa)

        if self._caps & Geodesic.CAP_C2:
            self._A2m1 = Geodesic.A2m1f(eps)
            self._C2a = range(Geodesic.nC2_ + 1)
            Geodesic.C2f(eps, self._C2a)
            self._B21 = Geodesic.SinCosSeries(True, self._ssig1, self._csig1,
                                              self._C2a, Geodesic.nC2_)

        if self._caps & Geodesic.CAP_C3:
            self._C3a = range(Geodesic.nC3_)
            geod.C3f(eps, self._C3a)
            self._A3c = -self._f * self._salp0 * geod.A3f(eps)
            self._B31 = Geodesic.SinCosSeries(True, self._ssig1, self._csig1,
                                              self._C3a, Geodesic.nC3_ - 1)

        if self._caps & Geodesic.CAP_C4:
            self._C4a = range(Geodesic.nC4_)
            geod.C4f(eps, self._C4a)
            # Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0)
            self._A4 = Math.sq(self._a) * self._calp0 * self._salp0 * geod._e2
            self._B41 = Geodesic.SinCosSeries(False, self._ssig1, self._csig1,
                                              self._C4a, Geodesic.nC4_)
Beispiel #18
0
 def CheckPosition(lat, lon):
     if (abs(lat) > 90):
         raise ValueError("latitude " + str(lat) + " not in [-90, 90]")
     if (lon < -540 or lon >= 540):
         raise ValueError("longitude " + str(lon) + " not in [-540, 540)")
     return Math.AngNormalize(lon)
Beispiel #19
0
    def __init__(self,
                 geod,
                 lat1,
                 lon1,
                 azi1,
                 caps=GeodesicCapability.STANDARD
                 | GeodesicCapability.DISTANCE_IN,
                 salp1=Math.nan,
                 calp1=Math.nan):
        """Construct a GeodesicLine object

    :param geod: a :class:`~geographiclib.geodesic.Geodesic` object
    :param lat1: latitude of the first point in degrees
    :param lon1: longitude of the first point in degrees
    :param azi1: azimuth at the first point in degrees
    :param caps: the :ref:`capabilities <outmask>`

    This creates an object allowing points along a geodesic starting at
    (*lat1*, *lon1*), with azimuth *azi1* to be found.  The default
    value of *caps* is STANDARD | DISTANCE_IN.  The optional parameters
    *salp1* and *calp1* should not be supplied; they are part of the
    private interface.

    """

        from geographiclib.geodesic import Geodesic
        self.a = geod.a
        """The equatorial radius in meters (readonly)"""
        self.f = geod.f
        """The flattening (readonly)"""
        self._b = geod._b
        self._c2 = geod._c2
        self._f1 = geod._f1
        self.caps = (caps | Geodesic.LATITUDE | Geodesic.AZIMUTH
                     | Geodesic.LONG_UNROLL)
        """the capabilities (readonly)"""

        # Guard against underflow in salp0
        self.lat1 = Math.LatFix(lat1)
        """the latitude of the first point in degrees (readonly)"""
        self.lon1 = lon1
        """the longitude of the first point in degrees (readonly)"""
        if Math.isnan(salp1) or Math.isnan(calp1):
            self.azi1 = Math.AngNormalize(azi1)
            self.salp1, self.calp1 = Math.sincosd(Math.AngRound(azi1))
        else:
            self.azi1 = azi1
            """the azimuth at the first point in degrees (readonly)"""
            self.salp1 = salp1
            """the sine of the azimuth at the first point (readonly)"""
            self.calp1 = calp1
            """the cosine of the azimuth at the first point (readonly)"""

        # real cbet1, sbet1
        sbet1, cbet1 = Math.sincosd(Math.AngRound(lat1))
        sbet1 *= self._f1
        # Ensure cbet1 = +epsilon at poles
        sbet1, cbet1 = Math.norm(sbet1, cbet1)
        cbet1 = max(Geodesic.tiny_, cbet1)
        self._dn1 = math.sqrt(1 + geod._ep2 * Math.sq(sbet1))

        # Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0),
        self._salp0 = self.salp1 * cbet1  # alp0 in [0, pi/2 - |bet1|]
        # Alt: calp0 = hypot(sbet1, calp1 * cbet1).  The following
        # is slightly better (consider the case salp1 = 0).
        self._calp0 = math.hypot(self.calp1, self.salp1 * sbet1)
        # Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1).
        # sig = 0 is nearest northward crossing of equator.
        # With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line).
        # With bet1 =  pi/2, alp1 = -pi, sig1 =  pi/2
        # With bet1 = -pi/2, alp1 =  0 , sig1 = -pi/2
        # Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1).
        # With alp0 in (0, pi/2], quadrants for sig and omg coincide.
        # No atan2(0,0) ambiguity at poles since cbet1 = +epsilon.
        # With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi.
        self._ssig1 = sbet1
        self._somg1 = self._salp0 * sbet1
        self._csig1 = self._comg1 = (cbet1 * self.calp1
                                     if sbet1 != 0 or self.calp1 != 0 else 1)
        # sig1 in (-pi, pi]
        self._ssig1, self._csig1 = Math.norm(self._ssig1, self._csig1)
        # No need to normalize
        # self._somg1, self._comg1 = Math.norm(self._somg1, self._comg1)

        self._k2 = Math.sq(self._calp0) * geod._ep2
        eps = self._k2 / (2 * (1 + math.sqrt(1 + self._k2)) + self._k2)

        if self.caps & Geodesic.CAP_C1:
            self._A1m1 = Geodesic._A1m1f(eps)
            self._C1a = list(range(Geodesic.nC1_ + 1))
            Geodesic._C1f(eps, self._C1a)
            self._B11 = Geodesic._SinCosSeries(True, self._ssig1, self._csig1,
                                               self._C1a)
            s = math.sin(self._B11)
            c = math.cos(self._B11)
            # tau1 = sig1 + B11
            self._stau1 = self._ssig1 * c + self._csig1 * s
            self._ctau1 = self._csig1 * c - self._ssig1 * s
            # Not necessary because C1pa reverts C1a
            #    _B11 = -_SinCosSeries(true, _stau1, _ctau1, _C1pa)

        if self.caps & Geodesic.CAP_C1p:
            self._C1pa = list(range(Geodesic.nC1p_ + 1))
            Geodesic._C1pf(eps, self._C1pa)

        if self.caps & Geodesic.CAP_C2:
            self._A2m1 = Geodesic._A2m1f(eps)
            self._C2a = list(range(Geodesic.nC2_ + 1))
            Geodesic._C2f(eps, self._C2a)
            self._B21 = Geodesic._SinCosSeries(True, self._ssig1, self._csig1,
                                               self._C2a)

        if self.caps & Geodesic.CAP_C3:
            self._C3a = list(range(Geodesic.nC3_))
            geod._C3f(eps, self._C3a)
            self._A3c = -self.f * self._salp0 * geod._A3f(eps)
            self._B31 = Geodesic._SinCosSeries(True, self._ssig1, self._csig1,
                                               self._C3a)

        if self.caps & Geodesic.CAP_C4:
            self._C4a = list(range(Geodesic.nC4_))
            geod._C4f(eps, self._C4a)
            # Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0)
            self._A4 = Math.sq(self.a) * self._calp0 * self._salp0 * geod._e2
            self._B41 = Geodesic._SinCosSeries(False, self._ssig1, self._csig1,
                                               self._C4a)
        self.s13 = Math.nan
        """the distance between point 1 and point 3 in meters (readonly)"""
        self.a13 = Math.nan
        """the arc length between point 1 and point 3 in degrees (readonly)"""
Beispiel #20
0
 def CheckAzimuth(azi):
     if (azi < -540 or azi >= 540):
         raise ValueError("azimuth " + str(azi) + " not in [-540, 540)")
     return Math.AngNormalize(azi)
Beispiel #21
0
 def CheckAzimuth(azi):
     """Check that azi is legal and return normalized value"""
     if (azi < -540 or azi >= 540):
         raise ValueError("azimuth " + str(azi) + " not in [-540, 540)")
     return Math.AngNormalize(azi)
Beispiel #22
0
    def GenInverse(self, lat1, lon1, lat2, lon2, outmask):
        """Private: General version of the inverse problem"""
        a12 = s12 = azi1 = azi2 = m12 = M12 = M21 = S12 = Math.nan  # return vals

        outmask &= Geodesic.OUT_ALL
        # Compute longitude difference (AngDiff does this carefully).  Result is
        # in [-180, 180] but -180 is only for west-going geodesics.  180 is for
        # east-going and meridional geodesics.
        lon12 = Math.AngDiff(Math.AngNormalize(lon1), Math.AngNormalize(lon2))
        # If very close to being on the same half-meridian, then make it so.
        lon12 = Geodesic.AngRound(lon12)
        # Make longitude difference positive.
        lonsign = 1 if lon12 >= 0 else -1
        lon12 *= lonsign
        # If really close to the equator, treat as on equator.
        lat1 = Geodesic.AngRound(lat1)
        lat2 = Geodesic.AngRound(lat2)
        # Swap points so that point with higher (abs) latitude is point 1
        swapp = 1 if abs(lat1) >= abs(lat2) else -1
        if swapp < 0:
            lonsign *= -1
            lat2, lat1 = lat1, lat2
        # Make lat1 <= 0
        latsign = 1 if lat1 < 0 else -1
        lat1 *= latsign
        lat2 *= latsign
        # Now we have
        #
        #     0 <= lon12 <= 180
        #     -90 <= lat1 <= 0
        #     lat1 <= lat2 <= -lat1
        #
        # longsign, swapp, latsign register the transformation to bring the
        # coordinates to this canonical form.  In all cases, 1 means no change was
        # made.  We make these transformations so that there are few cases to
        # check, e.g., on verifying quadrants in atan2.  In addition, this
        # enforces some symmetries in the results returned.

        # real phi, sbet1, cbet1, sbet2, cbet2, s12x, m12x

        phi = lat1 * Math.degree
        # Ensure cbet1 = +epsilon at poles
        sbet1 = self._f1 * math.sin(phi)
        cbet1 = Geodesic.tiny_ if lat1 == -90 else math.cos(phi)
        sbet1, cbet1 = Geodesic.SinCosNorm(sbet1, cbet1)

        phi = lat2 * Math.degree
        # Ensure cbet2 = +epsilon at poles
        sbet2 = self._f1 * math.sin(phi)
        cbet2 = Geodesic.tiny_ if abs(lat2) == 90 else math.cos(phi)
        sbet2, cbet2 = Geodesic.SinCosNorm(sbet2, cbet2)

        # If cbet1 < -sbet1, then cbet2 - cbet1 is a sensitive measure of the
        # |bet1| - |bet2|.  Alternatively (cbet1 >= -sbet1), abs(sbet2) + sbet1 is
        # a better measure.  This logic is used in assigning calp2 in Lambda12.
        # Sometimes these quantities vanish and in that case we force bet2 = +/-
        # bet1 exactly.  An example where is is necessary is the inverse problem
        # 48.522876735459 0 -48.52287673545898293 179.599720456223079643
        # which failed with Visual Studio 10 (Release and Debug)

        if cbet1 < -sbet1:
            if cbet2 == cbet1:
                sbet2 = sbet1 if sbet2 < 0 else -sbet1
        else:
            if abs(sbet2) == -sbet1:
                cbet2 = cbet1

        dn1 = math.sqrt(1 + self._ep2 * Math.sq(sbet1))
        dn2 = math.sqrt(1 + self._ep2 * Math.sq(sbet2))

        lam12 = lon12 * Math.degree
        slam12 = 0 if lon12 == 180 else math.sin(lam12)
        clam12 = math.cos(lam12)  # lon12 == 90 isn't interesting

        # real a12, sig12, calp1, salp1, calp2, salp2
        # index zero elements of these arrays are unused
        C1a = list(range(Geodesic.nC1_ + 1))
        C2a = list(range(Geodesic.nC2_ + 1))
        C3a = list(range(Geodesic.nC3_))

        meridian = lat1 == -90 or slam12 == 0

        if meridian:

            # Endpoints are on a single full meridian, so the geodesic might lie on
            # a meridian.

            calp1 = clam12
            salp1 = slam12  # Head to the target longitude
            calp2 = 1
            salp2 = 0  # At the target we're heading north

            # tan(bet) = tan(sig) * cos(alp)
            ssig1 = sbet1
            csig1 = calp1 * cbet1
            ssig2 = sbet2
            csig2 = calp2 * cbet2

            # sig12 = sig2 - sig1
            sig12 = math.atan2(max(csig1 * ssig2 - ssig1 * csig2, 0.0),
                               csig1 * csig2 + ssig1 * ssig2)

            s12x, m12x, dummy, M12, M21 = self.Lengths(
                self._n, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, cbet1,
                cbet2, (outmask & Geodesic.GEODESICSCALE) != 0, C1a, C2a)

            # Add the check for sig12 since zero length geodesics might yield m12 <
            # 0.  Test case was
            #
            #    echo 20.001 0 20.001 0 | Geod -i
            #
            # In fact, we will have sig12 > pi/2 for meridional geodesic which is
            # not a shortest path.
            if sig12 < 1 or m12x >= 0:
                m12x *= self._b
                s12x *= self._b
                a12 = sig12 / Math.degree
            else:
                # m12 < 0, i.e., prolate and too close to anti-podal
                meridian = False
        # end if meridian:

        #real omg12
        if (not meridian and sbet1 == 0 and  # and sbet2 == 0
                # Mimic the way Lambda12 works with calp1 = 0
            (self._f <= 0 or lam12 <= math.pi - self._f * math.pi)):

            # Geodesic runs along equator
            calp1 = calp2 = 0
            salp1 = salp2 = 1
            s12x = self._a * lam12
            sig12 = omg12 = lam12 / self._f1
            m12x = self._b * math.sin(sig12)
            if outmask & Geodesic.GEODESICSCALE:
                M12 = M21 = math.cos(sig12)
            a12 = lon12 / self._f1

        elif not meridian:

            # Now point1 and point2 belong within a hemisphere bounded by a
            # meridian and geodesic is neither meridional or equatorial.

            # Figure a starting point for Newton's method
            sig12, salp1, calp1, salp2, calp2, dnm = self.InverseStart(
                sbet1, cbet1, dn1, sbet2, cbet2, dn2, lam12, C1a, C2a)

            if sig12 >= 0:
                # Short lines (InverseStart sets salp2, calp2, dnm)
                s12x = sig12 * self._b * dnm
                m12x = (Math.sq(dnm) * self._b * math.sin(sig12 / dnm))
                if outmask & Geodesic.GEODESICSCALE:
                    M12 = M21 = math.cos(sig12 / dnm)
                a12 = sig12 / Math.degree
                omg12 = lam12 / (self._f1 * dnm)
            else:

                # Newton's method.  This is a straightforward solution of f(alp1) =
                # lambda12(alp1) - lam12 = 0 with one wrinkle.  f(alp) has exactly one
                # root in the interval (0, pi) and its derivative is positive at the
                # root.  Thus f(alp) is positive for alp > alp1 and negative for alp <
                # alp1.  During the course of the iteration, a range (alp1a, alp1b) is
                # maintained which brackets the root and with each evaluation of f(alp)
                # the range is shrunk if possible.  Newton's method is restarted
                # whenever the derivative of f is negative (because the new value of
                # alp1 is then further from the solution) or if the new estimate of
                # alp1 lies outside (0,pi); in this case, the new starting guess is
                # taken to be (alp1a + alp1b) / 2.
                # real ssig1, csig1, ssig2, csig2, eps
                numit = 0
                tripn = tripb = False
                # Bracketing range
                salp1a = Geodesic.tiny_
                calp1a = 1
                salp1b = Geodesic.tiny_
                calp1b = -1

                while numit < Geodesic.maxit2_:
                    # the WGS84 test set: mean = 1.47, sd = 1.25, max = 16
                    # WGS84 and random input: mean = 2.85, sd = 0.60
                    (nlam12, salp2, calp2, sig12, ssig1, csig1, ssig2, csig2,
                     eps, omg12, dv) = self.Lambda12(sbet1, cbet1, dn1, sbet2,
                                                     cbet2, dn2, salp1, calp1,
                                                     numit < Geodesic.maxit1_,
                                                     C1a, C2a, C3a)
                    v = nlam12 - lam12
                    # 2 * tol0 is approximately 1 ulp for a number in [0, pi].
                    # Reversed test to allow escape with NaNs
                    if tripb or not (abs(v) >=
                                     (8 if tripn else 2) * Geodesic.tol0_):
                        break
                    # Update bracketing values
                    if v > 0 and (numit > Geodesic.maxit1_
                                  or calp1 / salp1 > calp1b / salp1b):
                        salp1b = salp1
                        calp1b = calp1
                    elif v < 0 and (numit > Geodesic.maxit1_
                                    or calp1 / salp1 < calp1a / salp1a):
                        salp1a = salp1
                        calp1a = calp1

                    numit += 1
                    if numit < Geodesic.maxit1_ and dv > 0:
                        dalp1 = -v / dv
                        sdalp1 = math.sin(dalp1)
                        cdalp1 = math.cos(dalp1)
                        nsalp1 = salp1 * cdalp1 + calp1 * sdalp1
                        if nsalp1 > 0 and abs(dalp1) < math.pi:
                            calp1 = calp1 * cdalp1 - salp1 * sdalp1
                            salp1 = nsalp1
                            salp1, calp1 = Geodesic.SinCosNorm(salp1, calp1)
                            # In some regimes we don't get quadratic convergence because
                            # slope -> 0.  So use convergence conditions based on epsilon
                            # instead of sqrt(epsilon).
                            tripn = abs(v) <= 16 * Geodesic.tol0_
                            continue
                    # Either dv was not postive or updated value was outside legal range.
                    # Use the midpoint of the bracket as the next estimate.  This
                    # mechanism is not needed for the WGS84 ellipsoid, but it does catch
                    # problems with more eccentric ellipsoids.  Its efficacy is such for
                    # the WGS84 test set with the starting guess set to alp1 = 90deg:
                    # the WGS84 test set: mean = 5.21, sd = 3.93, max = 24
                    # WGS84 and random input: mean = 4.74, sd = 0.99
                    salp1 = (salp1a + salp1b) / 2
                    calp1 = (calp1a + calp1b) / 2
                    salp1, calp1 = Geodesic.SinCosNorm(salp1, calp1)
                    tripn = False
                    tripb = (abs(salp1a - salp1) +
                             (calp1a - calp1) < Geodesic.tolb_
                             or abs(salp1 - salp1b) +
                             (calp1 - calp1b) < Geodesic.tolb_)

                s12x, m12x, dummy, M12, M21 = self.Lengths(
                    eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, cbet1,
                    cbet2, (outmask & Geodesic.GEODESICSCALE) != 0, C1a, C2a)

                m12x *= self._b
                s12x *= self._b
                a12 = sig12 / Math.degree
                omg12 = lam12 - omg12
        # end elif not meridian

        if outmask & Geodesic.DISTANCE:
            s12 = 0 + s12x  # Convert -0 to 0

        if outmask & Geodesic.REDUCEDLENGTH:
            m12 = 0 + m12x  # Convert -0 to 0

        if outmask & Geodesic.AREA:
            # From Lambda12: sin(alp1) * cos(bet1) = sin(alp0)
            salp0 = salp1 * cbet1
            calp0 = math.hypot(calp1, salp1 * sbet1)  # calp0 > 0
            # real alp12
            if calp0 != 0 and salp0 != 0:
                # From Lambda12: tan(bet) = tan(sig) * cos(alp)
                ssig1 = sbet1
                csig1 = calp1 * cbet1
                ssig2 = sbet2
                csig2 = calp2 * cbet2
                k2 = Math.sq(calp0) * self._ep2
                eps = k2 / (2 * (1 + math.sqrt(1 + k2)) + k2)
                # Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0).
                A4 = Math.sq(self._a) * calp0 * salp0 * self._e2
                ssig1, csig1 = Geodesic.SinCosNorm(ssig1, csig1)
                ssig2, csig2 = Geodesic.SinCosNorm(ssig2, csig2)
                C4a = list(range(Geodesic.nC4_))
                self.C4f(eps, C4a)
                B41 = Geodesic.SinCosSeries(False, ssig1, csig1, C4a,
                                            Geodesic.nC4_)
                B42 = Geodesic.SinCosSeries(False, ssig2, csig2, C4a,
                                            Geodesic.nC4_)
                S12 = A4 * (B42 - B41)
            else:
                # Avoid problems with indeterminate sig1, sig2 on equator
                S12 = 0
            if (not meridian and omg12 < 0.75 * math.pi
                    and  # Long difference too big
                    sbet2 - sbet1 < 1.75):  # Lat difference too big
                # Use tan(Gamma/2) = tan(omg12/2)
                # * (tan(bet1/2)+tan(bet2/2))/(1+tan(bet1/2)*tan(bet2/2))
                # with tan(x/2) = sin(x)/(1+cos(x))
                somg12 = math.sin(omg12)
                domg12 = 1 + math.cos(omg12)
                dbet1 = 1 + cbet1
                dbet2 = 1 + cbet2
                alp12 = 2 * math.atan2(
                    somg12 * (sbet1 * dbet2 + sbet2 * dbet1),
                    domg12 * (sbet1 * sbet2 + dbet1 * dbet2))
            else:
                # alp12 = alp2 - alp1, used in atan2 so no need to normalize
                salp12 = salp2 * calp1 - calp2 * salp1
                calp12 = calp2 * calp1 + salp2 * salp1
                # The right thing appears to happen if alp1 = +/-180 and alp2 = 0, viz
                # salp12 = -0 and alp12 = -180.  However this depends on the sign
                # being attached to 0 correctly.  The following ensures the correct
                # behavior.
                if salp12 == 0 and calp12 < 0:
                    salp12 = Geodesic.tiny_ * calp1
                    calp12 = -1
                alp12 = math.atan2(salp12, calp12)
            S12 += self._c2 * alp12
            S12 *= swapp * lonsign * latsign
            # Convert -0 to 0
            S12 += 0

        # Convert calp, salp to azimuth accounting for lonsign, swapp, latsign.
        if swapp < 0:
            salp2, salp1 = salp1, salp2
            calp2, calp1 = calp1, calp2
            if outmask & Geodesic.GEODESICSCALE:
                M21, M12 = M12, M21

        salp1 *= swapp * lonsign
        calp1 *= swapp * latsign
        salp2 *= swapp * lonsign
        calp2 *= swapp * latsign

        if outmask & Geodesic.AZIMUTH:
            # minus signs give range [-180, 180). 0- converts -0 to +0.
            azi1 = 0 - math.atan2(-salp1, calp1) / Math.degree
            azi2 = 0 - math.atan2(-salp2, calp2) / Math.degree

        # Returned value in [0, 180]
        return a12, s12, azi1, azi2, m12, M12, M21, S12