Example #1
0
        /**
         * Reverse projection, from gnomonic to geographic.
         * <p>
         * @param lat0 latitude of center point of projection (degrees).
         * @param lon0 longitude of center point of projection (degrees).
         * @param x easting of point (meters).
         * @param y northing of point (meters).
         * @return {@link GnomonicData} object with the following fields:
         *   <i>lat0</i>, <i>lon0</i>, <i>lat</i>, <i>lon</i>, <i>x</i>, <i>y</i>,
         *   <i>azi</i>, <i>rk</i>.
         * <p>
         * <i>lat0</i> should be in the range [&minus;90&deg;, 90&deg;] and
         * <i>lon0</i> should be in the range [&minus;540&deg;, 540&deg;).
         * <i>lat</i> will be in the range [&minus;90&deg;, 90&deg;] and <i>lon</i>
         * will be in the range [&minus;180&deg;, 180&deg;]. The scale of the
         * projection is 1/<i>rk<sup>2</sup></i> in the "radial" direction,
         * <i>azi</i> clockwise from true north, and is 1/<i>rk</i> in the direction
         * perpendicular to this. Even though all inputs should return a valid
         * <i>lat</i> and <i>lon</i>, it's possible that the procedure fails to
         * converge for very large <i>x</i> or <i>y</i>; in this case NaNs are
         * returned for all the output arguments. A call to Reverse followed by a
         * call to Forward will return the original (<i>x</i>, <i>y</i>) (to
         * roundoff).
         */
        public GnomonicData Reverse(double lat0, double lon0, double x, double y)
        {
            var azi0   = GeoMath.Atan2d(x, y);
            var rho    = GeoMath.Hypot(x, y);
            var s      = this.MajorRadius * Math.Atan(rho / this.MajorRadius);
            var little = rho <= this.MajorRadius;

            if (!little)
            {
                rho = 1 / rho;
            }

            var line =
                Earth.Line(lat0, lon0, azi0, GeodesicMask.LATITUDE
                           | GeodesicMask.LONGITUDE | GeodesicMask.AZIMUTH
                           | GeodesicMask.DISTANCE_IN | GeodesicMask.REDUCEDLENGTH
                           | GeodesicMask.GEODESICSCALE);

            int          count = numit_, trip = 0;
            GeodesicData pos = GeodesicData.NaN;

            while (count-- > 0)
            {
                pos = line.Position(s,
                                    GeodesicMask.LONGITUDE | GeodesicMask.LATITUDE
                                    | GeodesicMask.AZIMUTH | GeodesicMask.DISTANCE_IN
                                    | GeodesicMask.REDUCEDLENGTH
                                    | GeodesicMask.GEODESICSCALE);

                if (trip > 0)
                {
                    break;
                }

                double ds = little ? ((pos.m12 / pos.M12) - rho) * pos.M12 * pos.M12
                    : (rho - (pos.M12 / pos.m12)) * pos.m12 * pos.m12;
                s -= ds;

                if (Math.Abs(ds) <= eps_ * this.MajorRadius)
                {
                    trip++;
                }
            }

            if (trip == 0)
            {
                return(new GnomonicData(lat0, lon0, Double.NaN, Double.NaN, x, y, Double.NaN, Double.NaN));
            }
            else
            {
                return(new GnomonicData(lat0, lon0, pos.lat2, pos.lon2, x, y, pos.azi2, pos.M12));
            }
        }
Example #2
0
        public void Add(double y)
        {
            // Here's Shewchuk's solution...
            double u;                       // hold exact sum as [s, t, u]

            // Accumulate starting at least significant end
            {
                Pair r = GeoMath.Sum(y, _t);
                y = r.First;
                u = r.Second;
            }
            {
                Pair r = GeoMath.Sum(y, _s);
                _s = r.First;
                _t = r.Second;
            }
            // Start is _s, _t decreasing and non-adjacent.  Sum is now (s + t + u)
            // exactly with s, t, u non-adjacent and in decreasing order (except for
            // possible zeros).  The following code tries to normalize the result.
            // Ideally, we want _s = round(s+t+u) and _u = round(s+t+u - _s).  The
            // following does an approximate job (and maintains the decreasing
            // non-adjacent property).  Here are two "failures" using 3-bit floats:
            //
            // Case 1: _s is not equal to round(s+t+u) -- off by 1 ulp
            // [12, -1] - 8 -> [4, 0, -1] -> [4, -1] = 3 should be [3, 0] = 3
            //
            // Case 2: _s+_t is not as close to s+t+u as it shold be
            // [64, 5] + 4 -> [64, 8, 1] -> [64,  8] = 72 (off by 1)
            //                    should be [80, -7] = 73 (exact)
            //
            // "Fixing" these problems is probably not worth the expense.  The
            // representation inevitably leads to small errors in the accumulated
            // values.  The additional errors illustrated here amount to 1 ulp of the
            // less significant word during each addition to the Accumulator and an
            // additional possible error of 1 ulp in the reported sum.
            //
            // Incidentally, the "ideal" representation described above is not
            // canonical, because _s = round(_s + _t) may not be true.  For example,
            // with 3-bit floats:
            //
            // [128, 16] + 1 -> [160, -16] -- 160 = round(145).
            // But [160, 0] - 16 -> [128, 16] -- 128 = round(144).
            //
            if (_s == 0)              // This implies t == 0,
            {
                _s = u;               // so result is u
            }
            else
            {
                _t += u;                // otherwise just accumulate u to t.
            }
        }
Example #3
0
        private static int transit(double lon1, double lon2)
        {
            // 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 = GeoMath.AngNormalize(lon1);
            lon2 = GeoMath.AngNormalize(lon2);
            double lon12 = GeoMath.AngDiff(lon1, lon2);
            int    cross =
                lon1 < 0 && lon2 >= 0 && lon12 > 0 ? 1 :
                (lon2 < 0 && lon1 >= 0 && lon12 < 0 ? -1 : 0);

            return(cross);
        }
Example #4
0
        /**
         * Constructor for a geodesic line staring at latitude <i>lat1</i>, longitude
         * <i>lon1</i>, and azimuth <i>azi1</i> (all in degrees) with a subset of the
         * capabilities included.
         * <p>
         * @param g A {@link Geodesic} object used to compute the necessary
         *   information about the GeodesicLine.
         * @param lat1 latitude of point 1 (degrees).
         * @param lon1 longitude of point 1 (degrees).
         * @param azi1 azimuth at point 1 (degrees).
         * @param caps bitor'ed combination of {@link GeodesicMask} values
         *   specifying the capabilities the GeodesicLine object should possess,
         *   i.e., which quantities can be returned in calls to {@link #Position
         *   Position}.
         * <p>
         * The {@link GeodesicMask} values are
         * <ul>
         * <li>
         *   <i>caps</i> |= {@link GeodesicMask#LATITUDE} for the latitude
         *   <i>lat2</i>; this is added automatically;
         * <li>
         *   <i>caps</i> |= {@link GeodesicMask#LONGITUDE} for the latitude
         *   <i>lon2</i>;
         * <li>
         *   <i>caps</i> |= {@link GeodesicMask#AZIMUTH} for the latitude
         *   <i>azi2</i>; this is added automatically;
         * <li>
         *   <i>caps</i> |= {@link GeodesicMask#DISTANCE} for the distance
         *   <i>s12</i>;
         * <li>
         *   <i>caps</i> |= {@link GeodesicMask#REDUCEDLENGTH} for the reduced length
         *   <i>m12</i>;
         * <li>
         *   <i>caps</i> |= {@link GeodesicMask#GEODESICSCALE} for the geodesic
         *   scales <i>M12</i> and <i>M21</i>;
         * <li>
         *   <i>caps</i> |= {@link GeodesicMask#AREA} for the area <i>S12</i>;
         * <li>
         *   <i>caps</i> |= {@link GeodesicMask#DISTANCE_IN} permits the length of
         *   the geodesic to be given in terms of <i>s12</i>; without this capability
         *   the length can only be specified in terms of arc length;
         * <li>
         *   <i>caps</i> |= {@link GeodesicMask#ALL} for all of the above.
         * </ul>
         **********************************************************************/
        public GeodesicLine(Geodesic g,
                            double lat1, double lon1, double azi1,
                            int caps)
        {
            azi1 = GeoMath.AngNormalize(azi1);
            double salp1, calp1;

            // Guard against underflow in salp0
            {
                Pair p = GeoMath.Sincosd(GeoMath.AngRound(azi1));
                salp1 = p.First; calp1 = p.Second;
            }
            LineInit(g, lat1, lon1, azi1, salp1, calp1, caps);
        }
Example #5
0
        /**
         * Forward projection, from geographic to gnomonic.
         * <p>
         * @param lat0 latitude of center point of projection (degrees).
         * @param lon0 longitude of center point of projection (degrees).
         * @param lat latitude of point (degrees).
         * @param lon longitude of point (degrees).
         * @return {@link GnomonicData} object with the following fields:
         *   <i>lat0</i>, <i>lon0</i>, <i>lat</i>, <i>lon</i>, <i>x</i>, <i>y</i>,
         *   <i>azi</i>, <i>rk</i>.
         * <p>
         * <i>lat0</i> and <i>lat</i> should be in the range [&minus;90&deg;,
         * 90&deg;] and <i>lon0</i> and <i>lon</i> should be in the range
         * [&minus;540&deg;, 540&deg;). The scale of the projection is
         * 1/<i>rk<sup>2</sup></i> in the "radial" direction, <i>azi</i> clockwise
         * from true north, and is 1/<i>rk</i> in the direction perpendicular to
         * this. If the point lies "over the horizon", i.e., if <i>rk</i> &le; 0,
         * then NaNs are returned for <i>x</i> and <i>y</i> (the correct values are
         * returned for <i>azi</i> and <i>rk</i>). A call to Forward followed by a
         * call to Reverse will return the original (<i>lat</i>, <i>lon</i>) (to
         * within roundoff) provided the point in not over the horizon.
         */
        public GnomonicData Forward(double lat0, double lon0, double lat, double lon)
        {
            GeodesicData inv = Earth.Inverse(
                lat0, lon0, lat, lon,
                GeodesicMask.AZIMUTH | GeodesicMask.GEODESICSCALE | GeodesicMask.REDUCEDLENGTH);

            if (inv.M12 > 0)
            {
                var rho = inv.m12 / inv.M12;
                var p   = GeoMath.Sincosd(inv.azi1);
                var x   = rho * p.First;
                var y   = rho * p.Second;
                return(new GnomonicData(lat0, lon0, lat, lon, x, y, inv.azi2, inv.M12));
            }
            else
            {
                return(new GnomonicData(lat0, lon0, lat, lon, Double.NaN, Double.NaN, inv.azi2, inv.M12));
            }
        }
Example #6
0
 /**
  * Add a point to the polygon or polyline.
  * <p>
  * @param lat the latitude of the point (degrees).
  * @param lon the latitude of the point (degrees).
  * <p>
  * <i>lat</i> should be in the range [&minus;90&deg;, 90&deg;] and <i>lon</i>
  * should be in the range [&minus;540&deg;, 540&deg;).
  **********************************************************************/
 public void AddPoint(double lat, double lon)
 {
     lon = GeoMath.AngNormalize(lon);
     if (_num == 0)
     {
         _lat0 = _lat1 = lat;
         _lon0 = _lon1 = lon;
     }
     else
     {
         GeodesicData g = _earth.Inverse(_lat1, _lon1, lat, lon, _mask);
         _perimetersum.Add(g.s12);
         if (!_polyline)
         {
             _areasum.Add(g.S12);
             _crossings += transit(_lon1, lon);
         }
         _lat1 = lat; _lon1 = lon;
     }
     ++_num;
 }
Example #7
0
 public double EquatorialArc() =>
 Init?GeoMath.Atan2d(_ssig1, _csig1) : double.NaN;
Example #8
0
 public double EquatorialAzimuth() =>
 Init?GeoMath.Atan2d(_salp0, _calp0) : Double.NaN;
Example #9
0
        /**
         * The general position function.  {@link #Position(double, int) Position}
         * and {@link #ArcPosition(double, int) ArcPosition} are defined in terms of
         * this function.
         * <p>
         * @param arcmode bool flag determining the meaning of the second
         *   parameter; if arcmode is false, then the GeodesicLine object must have
         *   been constructed with <i>caps</i> |= {@link GeodesicMask#DISTANCE_IN}.
         * @param s12_a12 if <i>arcmode</i> is false, this is the distance between
         *   point 1 and point 2 (meters); otherwise it is the arc length between
         *   point 1 and point 2 (degrees); it can be negative.
         * @param outmask a bitor'ed combination of {@link GeodesicMask} values
         *   specifying which results should be returned.
         * @return a {@link GeodesicData} object with the requested results.
         * <p>
         * The {@link GeodesicMask} values possible for <i>outmask</i> are
         * <ul>
         * <li>
         *   <i>outmask</i> |= {@link GeodesicMask#LATITUDE} for the latitude
         *   <i>lat2</i>;
         * <li>
         *   <i>outmask</i> |= {@link GeodesicMask#LONGITUDE} for the latitude
         *   <i>lon2</i>;
         * <li>
         *   <i>outmask</i> |= {@link GeodesicMask#AZIMUTH} for the latitude
         *   <i>azi2</i>;
         * <li>
         *   <i>outmask</i> |= {@link GeodesicMask#DISTANCE} for the distance
         *   <i>s12</i>;
         * <li>
         *   <i>outmask</i> |= {@link GeodesicMask#REDUCEDLENGTH} for the reduced
         *   length <i>m12</i>;
         * <li>
         *   <i>outmask</i> |= {@link GeodesicMask#GEODESICSCALE} for the geodesic
         *   scales <i>M12</i> and <i>M21</i>;
         * <li>
         *   <i>outmask</i> |= {@link GeodesicMask#ALL} for all of the above;
         * <li>
         *   <i>outmask</i> |= {@link GeodesicMask#LONG_UNROLL} to unroll <i>lon2</i>
         *   (instead of reducing it to the range [&minus;180&deg;, 180&deg;]).
         * </ul>
         * <p>
         * Requesting a value which the GeodesicLine object is not capable of
         * computing is not an error; Double.NaN is returned instead.
         **********************************************************************/
        public GeodesicData Position(bool arcmode, double s12_a12, int outmask)
        {
            outmask &= _caps & GeodesicMask.OUT_MASK;
            GeodesicData r = GeodesicData.NaN;

            if (!(Init && (arcmode || (_caps & (GeodesicMask.OUT_MASK & GeodesicMask.DISTANCE_IN)) != 0)))
            {
                // Uninitialized or impossible distance calculation requested
                return(r);
            }
            r.lat1 = _lat1;
            r.azi1 = _azi1;
            r.lon1 = ((outmask & GeodesicMask.LONG_UNROLL) != 0) ? _lon1 :
                     GeoMath.AngNormalize(_lon1);

            // Avoid warning about uninitialized B12.
            double sig12, ssig12, csig12, B12 = 0, AB1 = 0;

            if (arcmode)
            {
                // Interpret s12_a12 as spherical arc length
                r.a12 = s12_a12;
                sig12 = GeoMath.ToRadians(s12_a12);
                {
                    Pair p = GeoMath.Sincosd(s12_a12);
                    ssig12 = p.First; csig12 = p.Second;
                }
            }
            else
            {
                // Interpret s12_a12 as distance
                r.s12 = s12_a12;
                double
                    tau12 = s12_a12 / (_b * (1 + _A1m1)),
                    s     = Math.Sin(tau12),
                    c     = Math.Cos(tau12);
                // tau2 = tau1 + tau12
                B12 = -Geodesic.SinCosSeries(true,
                                             _stau1 * c + _ctau1 * s,
                                             _ctau1 * c - _stau1 * s,
                                             _C1pa);
                sig12  = tau12 - (B12 - _B11);
                ssig12 = Math.Sin(sig12); csig12 = Math.Cos(sig12);
                if (Math.Abs(_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
                    double
                        ssig2_ = _ssig1 * csig12 + _csig1 * ssig12,
                        csig2_ = _csig1 * csig12 - _ssig1 * ssig12;
                    B12 = Geodesic.SinCosSeries(true, ssig2_, csig2_, _C1a);
                    double serr = (1 + _A1m1) * (sig12 + (B12 - _B11)) - s12_a12 / _b;
                    sig12  = sig12 - serr / Math.Sqrt(1 + _k2 * GeoMath.Sq(ssig2_));
                    ssig12 = Math.Sin(sig12); csig12 = Math.Cos(sig12);
                    // Update B12 below
                }
                r.a12 = GeoMath.ToDegrees(sig12);
            }

            double ssig2, csig2, sbet2, cbet2, salp2, calp2;

            // sig2 = sig1 + sig12
            ssig2 = _ssig1 * csig12 + _csig1 * ssig12;
            csig2 = _csig1 * csig12 - _ssig1 * ssig12;
            double dn2 = Math.Sqrt(1 + _k2 * GeoMath.Sq(ssig2));

            if ((outmask & (GeodesicMask.DISTANCE | GeodesicMask.REDUCEDLENGTH |
                            GeodesicMask.GEODESICSCALE)) != 0)
            {
                if (arcmode || Math.Abs(_f) > 0.01)
                {
                    B12 = Geodesic.SinCosSeries(true, ssig2, csig2, _C1a);
                }
                AB1 = (1 + _A1m1) * (B12 - _B11);
            }
            // sin(bet2) = cos(alp0) * sin(sig2)
            sbet2 = _calp0 * ssig2;
            // Alt: cbet2 = hypot(csig2, salp0 * ssig2);
            cbet2 = GeoMath.Hypot(_salp0, _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 = _salp0; calp2 = _calp0 * csig2; // No need to normalize

            if ((outmask & GeodesicMask.DISTANCE) != 0 && arcmode)
            {
                r.s12 = _b * ((1 + _A1m1) * sig12 + AB1);
            }

            if ((outmask & GeodesicMask.LONGITUDE) != 0)
            {
                // tan(omg2) = sin(alp0) * tan(sig2)
                double somg2 = _salp0 * ssig2, comg2 = csig2, // No need to normalize
                       E = GeoMath.CopySign(1, _salp0);       // east or west going?
                                                              // omg12 = omg2 - omg1
                double omg12 = ((outmask & GeodesicMask.LONG_UNROLL) != 0)
                  ? E * (sig12
                         - (Math.Atan2(ssig2, csig2) - Math.Atan2(_ssig1, _csig1))
                         + (Math.Atan2(E * somg2, comg2) - Math.Atan2(E * _somg1, _comg1)))
                  : Math.Atan2(somg2 * _comg1 - comg2 * _somg1,
                               comg2 * _comg1 + somg2 * _somg1);
                double lam12 = omg12 + _A3c *
                               (sig12 + (Geodesic.SinCosSeries(true, ssig2, csig2, _C3a)
                                         - _B31));
                double lon12 = GeoMath.ToDegrees(lam12);
                r.lon2 = ((outmask & GeodesicMask.LONG_UNROLL) != 0) ? _lon1 + lon12 :
                         GeoMath.AngNormalize(r.lon1 + GeoMath.AngNormalize(lon12));
            }

            if ((outmask & GeodesicMask.LATITUDE) != 0)
            {
                r.lat2 = GeoMath.Atan2d(sbet2, _f1 * cbet2);
            }

            if ((outmask & GeodesicMask.AZIMUTH) != 0)
            {
                r.azi2 = GeoMath.Atan2d(salp2, calp2);
            }

            if ((outmask &
                 (GeodesicMask.REDUCEDLENGTH | GeodesicMask.GEODESICSCALE)) != 0)
            {
                double
                    B22 = Geodesic.SinCosSeries(true, ssig2, csig2, _C2a),
                    AB2 = (1 + _A2m1) * (B22 - _B21),
                    J12 = (_A1m1 - _A2m1) * sig12 + (AB1 - AB2);
                if ((outmask & GeodesicMask.REDUCEDLENGTH) != 0)
                {
                    // Add parens around (_csig1 * ssig2) and (_ssig1 * csig2) to ensure
                    // accurate cancellation in the case of coincident points.
                    r.m12 = _b * ((dn2 * (_csig1 * ssig2) - _dn1 * (_ssig1 * csig2))
                                  - _csig1 * csig2 * J12);
                }
                if ((outmask & GeodesicMask.GEODESICSCALE) != 0)
                {
                    double t = _k2 * (ssig2 - _ssig1) * (ssig2 + _ssig1) / (_dn1 + dn2);
                    r.M12 = csig12 + (t * ssig2 - csig2 * J12) * _ssig1 / _dn1;
                    r.M21 = csig12 - (t * _ssig1 - _csig1 * J12) * ssig2 / dn2;
                }
            }

            if ((outmask & GeodesicMask.AREA) != 0)
            {
                double
                       B42 = Geodesic.SinCosSeries(false, ssig2, csig2, _C4a);
                double salp12, calp12;
                if (_calp0 == 0 || _salp0 == 0)
                {
                    // alp12 = alp2 - alp1, used in atan2 so no need to normalize
                    salp12 = salp2 * _calp1 - calp2 * _salp1;
                    calp12 = calp2 * _calp1 + salp2 * _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 = _calp0 * _salp0 *
                             (csig12 <= 0 ? _csig1 * (1 - csig12) + ssig12 * _ssig1 :
                              ssig12 * (_csig1 * ssig12 / (1 + csig12) + _ssig1));
                    calp12 = GeoMath.Sq(_salp0) + GeoMath.Sq(_calp0) * _csig1 * csig2;
                }
                r.S12 = _c2 * Math.Atan2(salp12, calp12) + _A4 * (B42 - _B41);
            }

            return(r);
        }
Example #10
0
        private void LineInit(Geodesic g,
                              double lat1, double lon1,
                              double azi1, double salp1, double calp1,
                              int caps)
        {
            _a  = g._a;
            _f  = g._f;
            _b  = g._b;
            _c2 = g._c2;
            _f1 = g._f1;
            // Always allow latitude and azimuth and unrolling the longitude
            _caps = caps | GeodesicMask.LATITUDE | GeodesicMask.AZIMUTH |
                    GeodesicMask.LONG_UNROLL;

            _lat1 = GeoMath.LatFix(lat1);
            _lon1 = lon1;
            _azi1 = azi1; _salp1 = salp1; _calp1 = calp1;
            double cbet1, sbet1;

            {
                Pair p = GeoMath.Sincosd(GeoMath.AngRound(_lat1));
                sbet1 = _f1 * p.First; cbet1 = p.Second;
            }
            // Ensure cbet1 = +epsilon at poles
            {
                Pair p = GeoMath.Norm(sbet1, cbet1);
                sbet1 = p.First; cbet1 = Math.Max(Geodesic.tiny_, p.Second);
            }
            _dn1 = Math.Sqrt(1 + g._ep2 * GeoMath.Sq(sbet1));

            // Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0),
            _salp0 = _salp1 * cbet1; // alp0 in [0, pi/2 - |bet1|]
                                     // Alt: calp0 = hypot(sbet1, calp1 * cbet1).  The following
                                     // is slightly better (consider the case salp1 = 0).
            _calp0 = GeoMath.Hypot(_calp1, _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.
            _ssig1 = sbet1; _somg1 = _salp0 * sbet1;
            _csig1 = _comg1 = sbet1 != 0 || _calp1 != 0 ? cbet1 * _calp1 : 1;
            {
                Pair p = GeoMath.Norm(_ssig1, _csig1);
                _ssig1 = p.First; _csig1 = p.Second;
            } // sig1 in (-pi, pi]
              // GeoMath.norm(_somg1, _comg1); -- don't need to normalize!

            _k2 = GeoMath.Sq(_calp0) * g._ep2;
            double eps = _k2 / (2 * (1 + Math.Sqrt(1 + _k2)) + _k2);

            if ((_caps & GeodesicMask.CAP_C1) != 0)
            {
                _A1m1 = Geodesic.A1m1f(eps);
                _C1a  = new double[nC1_ + 1];
                Geodesic.C1f(eps, _C1a);
                _B11 = Geodesic.SinCosSeries(true, _ssig1, _csig1, _C1a);
                double s = Math.Sin(_B11), c = Math.Cos(_B11);
                // tau1 = sig1 + B11
                _stau1 = _ssig1 * c + _csig1 * s;
                _ctau1 = _csig1 * c - _ssig1 * s;
                // Not necessary because C1pa reverts C1a
                //    _B11 = -SinCosSeries(true, _stau1, _ctau1, _C1pa, nC1p_);
            }

            if ((_caps & GeodesicMask.CAP_C1p) != 0)
            {
                _C1pa = new double[nC1p_ + 1];
                Geodesic.C1pf(eps, _C1pa);
            }

            if ((_caps & GeodesicMask.CAP_C2) != 0)
            {
                _C2a  = new double[nC2_ + 1];
                _A2m1 = Geodesic.A2m1f(eps);
                Geodesic.C2f(eps, _C2a);
                _B21 = Geodesic.SinCosSeries(true, _ssig1, _csig1, _C2a);
            }

            if ((_caps & GeodesicMask.CAP_C3) != 0)
            {
                _C3a = new double[nC3_];
                g.C3f(eps, _C3a);
                _A3c = -_f *_salp0 *g.A3f(eps);

                _B31 = Geodesic.SinCosSeries(true, _ssig1, _csig1, _C3a);
            }

            if ((_caps & GeodesicMask.CAP_C4) != 0)
            {
                _C4a = new double[nC4_];
                g.C4f(eps, _C4a);
                // Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0)
                _A4  = GeoMath.Sq(_a) * _calp0 * _salp0 * g._e2;
                _B41 = Geodesic.SinCosSeries(false, _ssig1, _csig1, _C4a);
            }
        }
Example #11
0
        /**
         * The general position function.  {@link #Position(double, int) Position}
         * and {@link #ArcPosition(double, int) ArcPosition} are defined in terms of
         * this function.
         * <p>
         * @param arcmode bool flag determining the meaning of the Second
         *   parameter; if arcmode is false, then the GeodesicLine object must have
         *   been constructed with <i>caps</i> |= {@link GeodesicMask#DISTANCE_IN}.
         * @param s12_a12 if <i>arcmode</i> is false, this is the distance between
         *   point 1 and point 2 (meters); otherwise it is the arc length between
         *   point 1 and point 2 (degrees); it can be negative.
         * @param outmask a bitor'ed combination of {@link GeodesicMask} values
         *   specifying which results should be returned.
         * @return a {@link GeodesicData} object with the requested results.
         * <p>
         * The {@link GeodesicMask} values possible for <i>outmask</i> are
         * <ul>
         * <li>
         *   <i>outmask</i> |= GeodesicMask.LATITUDE for the latitude <i>lat2</i>.
         * <li>
         *   <i>outmask</i> |= GeodesicMask.LONGITUDE for the latitude <i>lon2</i>.
         * <li>
         *   <i>outmask</i> |= GeodesicMask.AZIMUTH for the latitude <i>azi2</i>.
         * <li>
         *   <i>outmask</i> |= GeodesicMask.DISTANCE for the distance <i>s12</i>.
         * <li>
         *   <i>outmask</i> |= GeodesicMask.REDUCEDLENGTH for the reduced length
         *   <i>m12</i>.
         * <li>
         *   <i>outmask</i> |= GeodesicMask.GEODESICSCALE for the geodesic scales
         *   <i>M12</i> and <i>M21</i>.
         * <li>
         *   <i>outmask</i> |= GeodesicMask.AREA for the Area <i>S12</i>.
         * </ul>
         * <p>
         * Requesting a value which the GeodesicLine object is not capable of
         * computing is not an error; Double.NaN is returned instead.
         **********************************************************************/
        public GeodesicData Position(bool arcmode, double s12_a12, int outmask)
        {
            outmask &= _caps & GeodesicMask.OUT_ALL;
            GeodesicData r = new GeodesicData();

            if (!(Init() &&
                  (arcmode ||
                   (_caps & GeodesicMask.DISTANCE_IN & GeodesicMask.OUT_ALL) != 0)))
            {
                // Uninitialized or impossible distance calculation requested
                return(r);
            }
            r.lat1 = _lat1; r.lon1 = _lon1; r.azi1 = _azi1;

            // Avoid warning about uninitialized B12.
            double sig12, ssig12, csig12, B12 = 0, AB1 = 0;

            if (arcmode)
            {
                // Interpret s12_a12 as spherical arc length
                r.a12 = s12_a12;
                sig12 = s12_a12 * GeoMath.Degree;
                double s12a = Math.Abs(s12_a12);
                s12a  -= 180 * Math.Floor(s12a / 180);
                ssig12 = s12a == 0 ? 0 : Math.Sin(sig12);
                csig12 = s12a == 90 ? 0 : Math.Cos(sig12);
            }
            else
            {
                // Interpret s12_a12 as distance
                r.s12 = s12_a12;
                double
                    tau12 = s12_a12 / (_b * (1 + _A1m1)),
                    s     = Math.Sin(tau12),
                    c     = Math.Cos(tau12);
                // tau2 = tau1 + tau12
                B12 = -Geodesic.SinCosSeries(true,
                                             _stau1 * c + _ctau1 * s,
                                             _ctau1 * c - _stau1 * s,
                                             _C1pa);
                sig12  = tau12 - (B12 - _B11);
                r.a12  = sig12 / GeoMath.Degree;
                ssig12 = Math.Sin(sig12); csig12 = Math.Cos(sig12);
                if (Math.Abs(_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
                    double
                        ssig2 = _ssig1 * csig12 + _csig1 * ssig12,
                        csig2 = _csig1 * csig12 - _ssig1 * ssig12;
                    B12 = Geodesic.SinCosSeries(true, ssig2, csig2, _C1a);
                    double serr = (1 + _A1m1) * (sig12 + (B12 - _B11)) - s12_a12 / _b;
                    sig12  = sig12 - serr / Math.Sqrt(1 + _k2 * GeoMath.Sq(ssig2));
                    ssig12 = Math.Sin(sig12); csig12 = Math.Cos(sig12);
                    // Update B12 below
                }
            }

            double omg12, lam12, lon12;
            double ssig3, csig3, sbet3, cbet3, somg3, comg3, salp3, calp3;

            // sig2 = sig1 + sig12
            ssig3 = _ssig1 * csig12 + _csig1 * ssig12;
            csig3 = _csig1 * csig12 - _ssig1 * ssig12;
            double dn2 = Math.Sqrt(1 + _k2 * GeoMath.Sq(ssig3));

            if ((outmask & (GeodesicMask.DISTANCE | GeodesicMask.REDUCEDLENGTH |
                            GeodesicMask.GEODESICSCALE)) != 0)
            {
                if (arcmode || Math.Abs(_f) > 0.01)
                {
                    B12 = Geodesic.SinCosSeries(true, ssig3, csig3, _C1a);
                }
                AB1 = (1 + _A1m1) * (B12 - _B11);
            }
            // sin(bet2) = cos(alp0) * sin(sig2)
            sbet3 = _calp0 * ssig3;
            // Alt: cbet3 = Hypot(csig3, salp0 * ssig3);
            cbet3 = GeoMath.Hypot(_salp0, _calp0 * csig3);
            if (cbet3 == 0)
            {
                // I.e., salp0 = 0, csig3 = 0.  Break the degeneracy in this case
                cbet3 = csig3 = Geodesic.tiny_;
            }
            // tan(omg2) = sin(alp0) * tan(sig2)
            somg3 = _salp0 * ssig3; comg3 = csig3;  // No need to normalize
            // tan(alp0) = cos(sig2)*tan(alp2)
            salp3 = _salp0; calp3 = _calp0 * csig3; // No need to normalize
            // omg12 = omg2 - omg1
            omg12 = Math.Atan2(somg3 * _comg1 - comg3 * _somg1,
                               comg3 * _comg1 + somg3 * _somg1);

            if ((outmask & GeodesicMask.DISTANCE) != 0 && arcmode)
            {
                r.s12 = _b * ((1 + _A1m1) * sig12 + AB1);
            }

            if ((outmask & GeodesicMask.LONGITUDE) != 0)
            {
                lam12 = omg12 + _A3c *
                        (sig12 + (Geodesic.SinCosSeries(true, ssig3, csig3, _C3a)
                                  - _B31));
                lon12 = lam12 / GeoMath.Degree;
                // Use GeoMath.AngNormalize2 because longitude might have wrapped
                // multiple times.
                lon12  = GeoMath.AngNormalize2(lon12);
                r.lon2 = GeoMath.AngNormalize(_lon1 + lon12);
            }

            if ((outmask & GeodesicMask.LATITUDE) != 0)
            {
                r.lat2 = Math.Atan2(sbet3, _f1 * cbet3) / GeoMath.Degree;
            }

            if ((outmask & GeodesicMask.AZIMUTH) != 0)
            {
                // minus signs give range [-180, 180). 0- converts -0 to +0.
                r.azi2 = 0 - Math.Atan2(-salp3, calp3) / GeoMath.Degree;
            }

            if ((outmask &
                 (GeodesicMask.REDUCEDLENGTH | GeodesicMask.GEODESICSCALE)) != 0)
            {
                double
                    B22 = Geodesic.SinCosSeries(true, ssig3, csig3, _C2a),
                    AB2 = (1 + _A2m1) * (B22 - _B21),
                    J12 = (_A1m1 - _A2m1) * sig12 + (AB1 - AB2);
                if ((outmask & GeodesicMask.REDUCEDLENGTH) != 0)
                {
                    // Add parens around (_csig1 * ssig3) and (_ssig1 * csig3) to ensure
                    // accurate cancellation in the case of coincident points.
                    r.m12 = _b * ((dn2 * (_csig1 * ssig3) - _dn1 * (_ssig1 * csig3))
                                  - _csig1 * csig3 * J12);
                }
                if ((outmask & GeodesicMask.GEODESICSCALE) != 0)
                {
                    double t = _k2 * (ssig3 - _ssig1) * (ssig3 + _ssig1) / (_dn1 + dn2);
                    r.M12 = csig12 + (t * ssig3 - csig3 * J12) * _ssig1 / _dn1;
                    r.M21 = csig12 - (t * _ssig1 - _csig1 * J12) * ssig3 / dn2;
                }
            }

            if ((outmask & GeodesicMask.AREA) != 0)
            {
                double
                       B42 = Geodesic.SinCosSeries(false, ssig3, csig3, _C4a);
                double salp12, calp12;
                if (_calp0 == 0 || _salp0 == 0)
                {
                    // alp12 = alp2 - alp1, used in atan2 so no need to normalized
                    salp12 = salp3 * _calp1 - calp3 * _salp1;
                    calp12 = calp3 * _calp1 + salp3 * _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 && calp12 < 0)
                    {
                        salp12 = Geodesic.tiny_ * _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-csig3) / (salp0^2 + calp0^2 * csig1*csig3)
                    // If csig12 > 0, write
                    //   csig1 - csig3 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1)
                    // else
                    //   csig1 - csig3 = csig1 * (1 - csig12) + ssig12 * ssig1
                    // No need to normalize
                    salp12 = _calp0 * _salp0 *
                             (csig12 <= 0 ? _csig1 * (1 - csig12) + ssig12 * _ssig1 :
                              ssig12 * (_csig1 * ssig12 / (1 + csig12) + _ssig1));
                    calp12 = GeoMath.Sq(_salp0) + GeoMath.Sq(_calp0) * _csig1 * csig3;
                }
                r.S12 = _c2 * Math.Atan2(salp12, calp12) + _A4 * (B42 - _B41);
            }

            return(r);
        }
Example #12
0
        /**
         * Constructor for a geodesic line staring at latitude <i>lat1</i>, longitude
         * <i>lon1</i>, and azimuth <i>azi1</i> (all in degrees) with a subset of the
         * capabilities included.
         * <p>
         * @param g A {@link Geodesic} object used to compute the necessary
         *   information about the GeodesicLine.
         * @param lat1 latitude of point 1 (degrees).
         * @param lon1 longitude of point 1 (degrees).
         * @param azi1 azimuth at point 1 (degrees).
         * @param caps bitor'ed combination of {@link GeodesicMask} values
         *   specifying the capabilities the GeodesicLine object should possess,
         *   i.e., which quantities can be returned in calls to {@link #Position
         *   Position}.
         * <p>
         * The {@link GeodesicMask} values are
         * <ul>
         * <li>
         *   <i>caps</i> |= GeodesicMask.LATITUDE for the latitude <i>lat2</i>; this
         *   is added automatically;
         * <li>
         *   <i>caps</i> |= GeodesicMask.LONGITUDE for the latitude <i>lon2</i>;
         * <li>
         *   <i>caps</i> |= GeodesicMask.AZIMUTH for the latitude <i>azi2</i>; this
         *   is added automatically;
         * <li>
         *   <i>caps</i> |= GeodesicMask.DISTANCE for the distance <i>s12</i>;
         * <li>
         *   <i>caps</i> |= GeodesicMask.REDUCEDLENGTH for the reduced length
         *   <i>m12</i>;
         * <li>
         *   <i>caps</i> |= GeodesicMask.GEODESICSCALE for the geodesic scales
         *   <i>M12</i> and <i>M21</i>;
         * <li>
         *   <i>caps</i> |= GeodesicMask.AREA for the Area <i>S12</i>;
         * <li>
         *   <i>caps</i> |= GeodesicMask.DISTANCE_IN permits the length of the
         *   geodesic to be given in terms of <i>s12</i>; without this capability the
         *   length can only be specified in terms of arc length;
         * <li>
         *   <i>caps</i> |= GeodesicMask.ALL for all of the above;
         * </ul>
         **********************************************************************/
        public GeodesicLine(Geodesic g,
                            double lat1, double lon1, double azi1,
                            int caps)
        {
            _a  = g._a;
            _f  = g._f;
            _b  = g._b;
            _c2 = g._c2;
            _f1 = g._f1;
            // Always allow latitude and azimuth
            _caps = caps | GeodesicMask.LATITUDE | GeodesicMask.AZIMUTH;

            // Guard against underflow in salp0
            azi1  = Geodesic.AngRound(GeoMath.AngNormalize(azi1));
            lon1  = GeoMath.AngNormalize(lon1);
            _lat1 = lat1;
            _lon1 = lon1;
            _azi1 = azi1;
            // alp1 is in [0, pi]
            double alp1 = azi1 * GeoMath.Degree;

            // Enforce sin(pi) == 0 and cos(pi/2) == 0.  Better to face the ensuing
            // problems directly than to skirt them.
            _salp1 = azi1 == -180 ? 0 : Math.Sin(alp1);
            _calp1 = Math.Abs(azi1) == 90 ? 0 : Math.Cos(alp1);
            double cbet1, sbet1, phi;

            phi = lat1 * GeoMath.Degree;
            // Ensure cbet1 = +Epsilon at poles
            sbet1 = _f1 * Math.Sin(phi);
            cbet1 = Math.Abs(lat1) == 90 ? Geodesic.tiny_ : Math.Cos(phi);
            {
                Pair p = Geodesic.SinCosNorm(sbet1, cbet1);
                sbet1 = p.First; cbet1 = p.Second;
            }
            _dn1 = Math.Sqrt(1 + g._ep2 * GeoMath.Sq(sbet1));

            // Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0),
            _salp0 = _salp1 * cbet1; // alp0 in [0, pi/2 - |bet1|]
            // Alt: calp0 = Hypot(sbet1, calp1 * cbet1).  The following
            // is slightly better (consider the case salp1 = 0).
            _calp0 = GeoMath.Hypot(_calp1, _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.
            _ssig1 = sbet1; _somg1 = _salp0 * sbet1;
            _csig1 = _comg1 = sbet1 != 0 || _calp1 != 0 ? cbet1 * _calp1 : 1;
            {
                Pair p = Geodesic.SinCosNorm(_ssig1, _csig1);
                _ssig1 = p.First; _csig1 = p.Second;
            } // sig1 in (-pi, pi]
              // Geodesic.SinCosNorm(_somg1, _comg1); -- don't need to normalize!

            _k2 = GeoMath.Sq(_calp0) * g._ep2;
            double eps = _k2 / (2 * (1 + Math.Sqrt(1 + _k2)) + _k2);

            if ((_caps & GeodesicMask.CAP_C1) != 0)
            {
                _A1m1 = Geodesic.A1m1f(eps);
                _C1a  = new double[nC1_ + 1];
                Geodesic.C1f(eps, _C1a);
                _B11 = Geodesic.SinCosSeries(true, _ssig1, _csig1, _C1a);
                double s = Math.Sin(_B11), c = Math.Cos(_B11);
                // tau1 = sig1 + B11
                _stau1 = _ssig1 * c + _csig1 * s;
                _ctau1 = _csig1 * c - _ssig1 * s;
                // Not necessary because C1pa reverts C1a
                //    _B11 = -SinCosSeries(true, _stau1, _ctau1, _C1pa, nC1p_);
            }

            if ((_caps & GeodesicMask.CAP_C1p) != 0)
            {
                _C1pa = new double[nC1p_ + 1];
                Geodesic.C1pf(eps, _C1pa);
            }

            if ((_caps & GeodesicMask.CAP_C2) != 0)
            {
                _C2a  = new double[nC2_ + 1];
                _A2m1 = Geodesic.A2m1f(eps);
                Geodesic.C2f(eps, _C2a);
                _B21 = Geodesic.SinCosSeries(true, _ssig1, _csig1, _C2a);
            }

            if ((_caps & GeodesicMask.CAP_C3) != 0)
            {
                _C3a = new double[nC3_];
                g.C3f(eps, _C3a);
                _A3c = -_f *_salp0 *g.A3f(eps);

                _B31 = Geodesic.SinCosSeries(true, _ssig1, _csig1, _C3a);
            }

            if ((_caps & GeodesicMask.CAP_C4) != 0)
            {
                _C4a = new double[nC4_];
                g.C4f(eps, _C4a);
                // Multiplier = a^2 * e^2 * cos(alpha0) * sin(alpha0)
                _A4  = GeoMath.Sq(_a) * _calp0 * _salp0 * g._e2;
                _B41 = Geodesic.SinCosSeries(false, _ssig1, _csig1, _C4a);
            }
        }