Ejemplo n.º 1
0
  def InverseLine(self, lat1, lon1, lat2, lon2,
                  caps = GeodesicCapability.STANDARD |
                  GeodesicCapability.DISTANCE_IN):
    """Define a GeodesicLine object in terms of the invese 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 caps: the :ref:`capabilities <outmask>`
    :return: a :class:`~geographiclib.geodesicline.GeodesicLine`

    This function sets point 3 of the GeodesicLine to correspond to
    point 2 of the inverse geodesic problem.  The default value of *caps*
    is STANDARD | DISTANCE_IN, allowing direct geodesic problem to be
    solved.

    """

    from geographiclib.geodesicline import GeodesicLine
    a12, _, salp1, calp1, _, _, _, _, _, _ = self._GenInverse(
      lat1, lon1, lat2, lon2, 0)
    azi1 = Math.atan2d(salp1, calp1)
    if caps & (Geodesic.OUT_MASK & Geodesic.DISTANCE_IN):
      caps |= Geodesic.DISTANCE
    line = GeodesicLine(self, lat1, lon1, azi1, caps, salp1, calp1)
    line.SetArc(a12)
    return line
Ejemplo n.º 2
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
Ejemplo n.º 3
0
  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:
      # minus signs give range [-180, 180). 0- converts -0 to +0.
      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
Ejemplo n.º 4
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_MASK
    # 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.
    # If very close to being on the same half-meridian, then make it so.
    lon12 = Math.AngRound(Math.AngDiff(lon1, lon2))
    # Make longitude difference positive.
    lonsign = 1 if lon12 >= 0 else -1
    lon12 *= lonsign
    # If really close to the equator, treat as on equator.
    lat1 = Math.AngRound(Math.LatFix(lat1))
    lat2 = Math.AngRound(Math.LatFix(lat2))
    # Swap points so that point with higher (abs) latitude is point 1
    # If one latitude is a nan, then it becomes lat1.
    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

    sbet1, cbet1 = Math.sincosd(lat1); sbet1 *= self._f1
    # Ensure cbet1 = +epsilon at poles
    sbet1, cbet1 = Math.norm(sbet1, cbet1); cbet1 = max(Geodesic.tiny_, cbet1)

    sbet2, cbet2 = Math.sincosd(lat2); sbet2 *= self._f1
    # Ensure cbet2 = +epsilon at poles
    sbet2, cbet2 = Math.norm(sbet2, cbet2); cbet2 = max(Geodesic.tiny_, 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 = math.radians(lon12)
    slam12, clam12 = Math.sincosd(lon12)

    # 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(0.0, csig1 * ssig2 - ssig1 * csig2),
                         csig1 * csig2 + ssig1 * ssig2)

      s12x, m12x, dummy, M12, M21 = self.Lengths(
        self._n, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, cbet1, cbet2,
        outmask | Geodesic.DISTANCE | Geodesic.REDUCEDLENGTH, 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 | GeodSolve -i
      #
      # In fact, we will have sig12 > pi/2 for meridional geodesic which is
      # not a shortest path.
      if sig12 < 1 or m12x >= 0:
        if sig12 < 3 * Geodesic.tiny_:
          sig12 = m12x = s12x = 0
        m12x *= self._b
        s12x *= self._b
        a12 = math.degrees(sig12)
      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 = math.degrees(sig12)
        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 = Math.norm(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 = Math.norm(salp1, calp1)
          tripn = False
          tripb = (abs(salp1a - salp1) + (calp1a - calp1) < Geodesic.tolb_ or
                   abs(salp1 - salp1b) + (calp1 - calp1b) < Geodesic.tolb_)

        lengthmask = (outmask |
                      (Geodesic.DISTANCE
                       if (outmask & (Geodesic.REDUCEDLENGTH |
                                      Geodesic.GEODESICSCALE))
                       else Geodesic.EMPTY))
        s12x, m12x, dummy, M12, M21 = self.Lengths(
          eps, sig12, ssig1, csig1, dn1, ssig2, csig2, dn2, cbet1, cbet2,
          lengthmask, C1a, C2a)

        m12x *= self._b
        s12x *= self._b
        a12 = math.degrees(sig12)
        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 = Math.norm(ssig1, csig1)
        ssig2, csig2 = Math.norm(ssig2, csig2)
        C4a = list(range(Geodesic.nC4_))
        self.C4f(eps, C4a)
        B41 = Geodesic.SinCosSeries(False, ssig1, csig1, C4a)
        B42 = Geodesic.SinCosSeries(False, ssig2, csig2, C4a)
        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 = Math.atan2d(salp1, calp1)
      azi2 = Math.atan2d(salp2, calp2)

    # Returned value in [0, 180]
    return a12, s12, azi1, azi2, m12, M12, M21, S12
Ejemplo n.º 5
0
    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:
            # minus signs give range [-180, 180). 0- converts -0 to +0.
            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