View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.commons.geometry.spherical.twod;
    18
    19  import java.util.Objects;
    20
    21  import org.apache.commons.geometry.core.Transform;
    22  import org.apache.commons.geometry.core.partitioning.AbstractHyperplane;
    23  import org.apache.commons.geometry.core.partitioning.EmbeddingHyperplane;
    24  import org.apache.commons.geometry.core.partitioning.Hyperplane;
    25  import org.apache.commons.geometry.euclidean.threed.Vector3D;
    26  import org.apache.commons.geometry.spherical.oned.AngularInterval;
    27  import org.apache.commons.geometry.spherical.oned.Point1S;
    28  import org.apache.commons.numbers.angle.Angle;
    29  import org.apache.commons.numbers.core.Precision;
    30
    31  /** Class representing a great circle on the 2-sphere. A great circle is the
    32   * intersection of a sphere with a plane that passes through its center. It is
    33   * the largest diameter circle that can be drawn on the sphere and partitions the
    34   * sphere into two hemispheres. The vectors {@code u} and {@code v} lie in the great
    35   * circle plane, while the vector {@code w} (the pole) is perpendicular to it. The
    36   * pole vector points toward the <em>minus</em> side of the hyperplane.
    37   *
    38   * <p>Instances of this class are guaranteed to be immutable.</p>
    39   * @see GreatCircles
    40   */
    41  public final class GreatCircle extends AbstractHyperplane<Point2S>
    42      implements EmbeddingHyperplane<Point2S, Point1S> {
    43      /** Pole or circle center. */
    44      private final Vector3D.Unit pole;
    45
    46      /** First axis in the equator plane, origin of the azimuth angles. */
    47      private final Vector3D.Unit u;
    48
    49      /** Second axis in the equator plane, in quadrature with respect to u. */
    50      private final Vector3D.Unit v;
    51
    52      /** Simple constructor. Callers are responsible for ensuring the inputs are valid.
    53       * @param pole pole vector of the great circle
    54       * @param u u axis in the equator plane
    55       * @param v v axis in the equator plane
    56       * @param precision precision context used for floating point comparisons
    57       */
    58      GreatCircle(final Vector3D.Unit pole, final Vector3D.Unit u, final Vector3D.Unit v,
    59              final Precision.DoubleEquivalence precision) {
    60          super(precision);
    61
    62          this.pole = pole;
    63          this.u = u;
    64          this.v = v;
    65      }
    66
    67      /** Get the pole of the great circle. This vector is perpendicular to the
    68       * equator plane of the instance.
    69       * @return pole of the great circle
    70       */
    71      public Vector3D.Unit getPole() {
    72          return pole;
    73      }
    74
    75      /** Get the spherical point located at the positive pole of the instance.
    76       * @return the spherical point located at the positive pole of the instance
    77       */
    78      public Point2S getPolePoint() {
    79          return Point2S.from(pole);
    80      }
    81
    82      /** Get the u axis of the great circle. This vector is located in the equator plane and defines
    83       * the {@code 0pi} location of the embedded subspace.
    84       * @return u axis of the great circle
    85       */
    86      public Vector3D.Unit getU() {
    87          return u;
    88      }
    89
    90      /** Get the v axis of the great circle. This vector lies in the equator plane,
    91       * perpendicular to the u-axis.
    92       * @return v axis of the great circle
    93       */
    94      public Vector3D.Unit getV() {
    95          return v;
    96      }
    97
    98      /** Get the w (pole) axis of the great circle. The method is equivalent to {@code #getPole()}.
    99       * @return the w (pole) axis of the great circle.
    100      * @see #getPole()
    101      */
    102     public Vector3D.Unit getW() {
    103         return getPole();
    104     }
    105
    106     /** {@inheritDoc}
    107      *
    108      * <p>The returned offset values are in the range {@code [-pi/2, +pi/2]},
    109      * with a point directly on the circle's pole vector having an offset of
    110      * {@code -pi/2} and its antipodal point having an offset of {@code +pi/2}.
    111      * Thus, the circle's pole vector points toward the <em>minus</em> side of
    112      * the hyperplane.</p>
    113      *
    114      * @see #offset(Vector3D)
    115      */
    116     @Override
    117     public double offset(final Point2S point) {
    118         return offset(point.getVector());
    119     }
    120
    121     /** Get the offset (oriented distance) of a direction.
    122      *
    123      * <p>The offset computed here is equal to the angle between the circle's
    124      * pole and the given vector minus {@code pi/2}. Thus, the pole vector
    125      * has an offset of {@code -pi/2}, a point on the circle itself has an
    126      * offset of {@code 0}, and the negation of the pole vector has an offset
    127      * of {@code +pi/2}.</p>
    128      * @param vec vector to compute the offset for
    129      * @return the offset (oriented distance) of a direction
    130      */
    131     public double offset(final Vector3D vec) {
    132         return pole.angle(vec) - Angle.PI_OVER_TWO;
    133     }
    134
    135     /** Get the azimuth angle of a point relative to this great circle instance,
    136      *  in the range {@code [0, 2pi)}.
    137      * @param pt point to compute the azimuth for
    138      * @return azimuth angle of the point in the range {@code [0, 2pi)}
    139      */
    140     public double azimuth(final Point2S pt) {
    141         return azimuth(pt.getVector());
    142     }
    143
    144     /** Get the azimuth angle of a vector in the range {@code [0, 2pi)}.
    145      * The azimuth angle is the angle of the projection of the argument on the
    146      * equator plane relative to the plane's u-axis. Since the vector is
    147      * projected onto the equator plane, it does not need to belong to the circle.
    148      * Vectors parallel to the great circle's pole do not have a defined azimuth angle.
    149      * In these cases, the method follows the rules of the
    150      * {@code Math#atan2(double, double)} method and returns {@code 0}.
    151      * @param vector vector to compute the great circle azimuth of
    152      * @return azimuth angle of the vector around the great circle in the range
    153      *      {@code [0, 2pi)}
    154      * @see #toSubspace(Point2S)
    155      */
    156     public double azimuth(final Vector3D vector) {
    157         double az = Math.atan2(vector.dot(v), vector.dot(u));
    158
    159         // adjust range
    160         if (az < 0) {
    161             az += Angle.TWO_PI;
    162         }
    163
    164         return az;
    165     }
    166
    167     /** Get the vector on the great circle with the given azimuth angle.
    168      * @param azimuth azimuth angle in radians
    169      * @return the point on the great circle with the given phase angle
    170      */
    171     public Vector3D vectorAt(final double azimuth) {
    172         return Vector3D.Sum.create()
    173                 .addScaled(Math.cos(azimuth), u)
    174                 .addScaled(Math.sin(azimuth), v).get();
    175     }
    176
    177     /** {@inheritDoc} */
    178     @Override
    179     public Point2S project(final Point2S point) {
    180         final double az = azimuth(point.getVector());
    181         return Point2S.from(vectorAt(az));
    182     }
    183
    184     /** {@inheritDoc}
    185      *
    186      * <p>The returned instance has the same u-axis but opposite pole and v-axis
    187      * as this instance.</p>
    188      */
    189     @Override
    190     public GreatCircle reverse() {
    191         return new GreatCircle(pole.negate(), u, v.negate(), getPrecision());
    192     }
    193
    194     /** {@inheritDoc} */
    195     @Override
    196     public GreatCircle transform(final Transform<Point2S> transform) {
    197         final Point2S tu = transform.apply(Point2S.from(u));
    198         final Point2S tv = transform.apply(Point2S.from(v));
    199
    200         return GreatCircles.fromPoints(tu, tv, getPrecision());
    201     }
    202
    203     /** {@inheritDoc} */
    204     @Override
    205     public boolean similarOrientation(final Hyperplane<Point2S> other) {
    206         final GreatCircle otherCircle = (GreatCircle) other;
    207         return pole.dot(otherCircle.pole) > 0.0;
    208     }
    209
    210     /** {@inheritDoc} */
    211     @Override
    212     public GreatArc span() {
    213         return GreatCircles.arcFromInterval(this, AngularInterval.full());
    214     }
    215
    216     /** Create an arc on this circle between the given points.
    217      * @param start start point
    218      * @param end end point
    219      * @return an arc on this circle between the given points
    220      * @throws IllegalArgumentException if the specified interval is not
    221      *      convex (ie, the angle between the points is greater than {@code pi}
    222      */
    223     public GreatArc arc(final Point2S start, final Point2S end) {
    224         return arc(toSubspace(start), toSubspace(end));
    225     }
    226
    227     /** Create an arc on this circle between the given subspace points.
    228      * @param start start subspace point
    229      * @param end end subspace point
    230      * @return an arc on this circle between the given subspace points
    231      * @throws IllegalArgumentException if the specified interval is not
    232      *      convex (ie, the angle between the points is greater than {@code pi}
    233      */
    234     public GreatArc arc(final Point1S start, final Point1S end) {
    235         return arc(start.getAzimuth(), end.getAzimuth());
    236     }
    237
    238     /** Create an arc on this circle between the given subspace azimuth values.
    239      * @param start start subspace azimuth
    240      * @param end end subspace azimuth
    241      * @return an arc on this circle between the given subspace azimuths
    242      * @throws IllegalArgumentException if the specified interval is not
    243      *      convex (ie, the angle between the points is greater than {@code pi}
    244      */
    245     public GreatArc arc(final double start, final double end) {
    246         return arc(AngularInterval.Convex.of(start, end, getPrecision()));
    247     }
    248
    249     /** Create an arc on this circle consisting of the given subspace interval.
    250      * @param interval subspace interval
    251      * @return an arc on this circle consisting of the given subspace interval
    252      */
    253     public GreatArc arc(final AngularInterval.Convex interval) {
    254         return GreatCircles.arcFromInterval(this, interval);
    255     }
    256
    257     /** Return one of the two intersection points between this instance and the argument.
    258      * If the circles occupy the same space (ie, their poles are parallel or anti-parallel),
    259      * then null is returned. Otherwise, the intersection located at the cross product of
    260      * the pole of this instance and that of the argument is returned (ie, {@code thisPole.cross(otherPole)}.
    261      * The other intersection point of the pair is antipodal to this point.
    262      * @param other circle to intersect with
    263      * @return one of the two intersection points between this instance and the argument
    264      */
    265     public Point2S intersection(final GreatCircle other) {
    266         final Vector3D cross = pole.cross(other.pole);
    267         if (!cross.eq(Vector3D.ZERO, getPrecision())) {
    268             return Point2S.from(cross);
    269         }
    270
    271         return null;
    272     }
    273
    274     /** Compute the angle between this great circle and the argument.
    275      * The return value is the angle between the poles of the two circles,
    276      * in the range {@code [0, pi]}.
    277      * @param other great circle to compute the angle with
    278      * @return the angle between this great circle and the argument in the
    279      *      range {@code [0, pi]}
    280      * @see #angle(GreatCircle, Point2S)
    281      */
    282     public double angle(final GreatCircle other) {
    283         return pole.angle(other.pole);
    284     }
    285
    286     /** Compute the angle between this great circle and the argument, measured
    287      * at the intersection point closest to the given point. The value is computed
    288      * as if a tangent line was drawn from each great circle at the intersection
    289      * point closest to {@code pt}, and the angle required to rotate the tangent
    290      * line representing the current instance to align with that of the given
    291      * instance was measured. The return value lies in the range {@code [-pi, pi)} and
    292      * has an absolute value equal to that returned by {@link #angle(GreatCircle)}, but
    293      * possibly a different sign. If the given point is equidistant from both intersection
    294      * points (as evaluated by this instance's precision context), then the point is assumed
    295      * to be closest to the point opposite the cross product of the two poles.
    296      * @param other great circle to compute the angle with
    297      * @param pt point determining the circle intersection to compute the angle at
    298      * @return the angle between this great circle and the argument as measured at the
    299      *      intersection point closest to the given point; the value is in the range
    300      *      {@code [-pi, pi)}
    301      * @see #angle(GreatCircle)
    302      */
    303     public double angle(final GreatCircle other, final Point2S pt) {
    304         final double theta = angle(other);
    305         final Vector3D cross = pole.cross(other.pole);
    306
    307         return getPrecision().gt(pt.getVector().dot(cross), 0) ?
    308                 theta :
    309                 -theta;
    310     }
    311
    312     /** {@inheritDoc} */
    313     @Override
    314     public Point1S toSubspace(final Point2S point) {
    315         return Point1S.of(azimuth(point.getVector()));
    316     }
    317
    318     /** {@inheritDoc} */
    319     @Override
    320     public Point2S toSpace(final Point1S point) {
    321         return Point2S.from(vectorAt(point.getAzimuth()));
    322     }
    323
    324     /** Return true if this instance should be considered equivalent to the argument, using the
    325      * given precision context for comparison. Instances are considered equivalent if have equivalent
    326      * {@code pole}, {@code u}, and {@code v} vectors.
    327      * @param other great circle to compare with
    328      * @param precision precision context to use for the comparison
    329      * @return true if this instance should be considered equivalent to the argument
    330      * @see Vector3D#eq(Vector3D, Precision.DoubleEquivalence)
    331      */
    332     public boolean eq(final GreatCircle other, final Precision.DoubleEquivalence precision) {
    333         return pole.eq(other.pole, precision) &&
    334                 u.eq(other.u, precision) &&
    335                 v.eq(other.v, precision);
    336     }
    337
    338     /** {@inheritDoc} */
    339     @Override
    340     public int hashCode() {
    341         return Objects.hash(pole, u, v, getPrecision());
    342     }
    343
    344     /** {@inheritDoc} */
    345     @Override
    346     public boolean equals(final Object obj) {
    347         if (this == obj) {
    348             return true;
    349         } else if (!(obj instanceof GreatCircle)) {
    350             return false;
    351         }
    352
    353         final GreatCircle other = (GreatCircle) obj;
    354
    355         return Objects.equals(this.pole, other.pole) &&
    356                 Objects.equals(this.u, other.u) &&
    357                 Objects.equals(this.v, other.v) &&
    358                 Objects.equals(this.getPrecision(), other.getPrecision());
    359     }
    360
    361     /** {@inheritDoc} */
    362     @Override
    363     public String toString() {
    364         final StringBuilder sb = new StringBuilder();
    365         sb.append(getClass().getSimpleName())
    366             .append("[pole= ")
    367             .append(pole)
    368             .append(", u= ")
    369             .append(u)
    370             .append(", v= ")
    371             .append(v)
    372             .append(']');
    373
    374         return sb.toString();
    375     }
    376 }