GreatCircle.java

  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. import java.util.Objects;

  19. import org.apache.commons.geometry.core.Transform;
  20. import org.apache.commons.geometry.core.partitioning.AbstractHyperplane;
  21. import org.apache.commons.geometry.core.partitioning.EmbeddingHyperplane;
  22. import org.apache.commons.geometry.core.partitioning.Hyperplane;
  23. import org.apache.commons.geometry.euclidean.threed.Vector3D;
  24. import org.apache.commons.geometry.spherical.oned.AngularInterval;
  25. import org.apache.commons.geometry.spherical.oned.Point1S;
  26. import org.apache.commons.numbers.angle.Angle;
  27. import org.apache.commons.numbers.core.Precision;

  28. /** Class representing a great circle on the 2-sphere. A great circle is the
  29.  * intersection of a sphere with a plane that passes through its center. It is
  30.  * the largest diameter circle that can be drawn on the sphere and partitions the
  31.  * sphere into two hemispheres. The vectors {@code u} and {@code v} lie in the great
  32.  * circle plane, while the vector {@code w} (the pole) is perpendicular to it. The
  33.  * pole vector points toward the <em>minus</em> side of the hyperplane.
  34.  *
  35.  * <p>Instances of this class are guaranteed to be immutable.</p>
  36.  * @see GreatCircles
  37.  */
  38. public final class GreatCircle extends AbstractHyperplane<Point2S>
  39.     implements EmbeddingHyperplane<Point2S, Point1S> {
  40.     /** Pole or circle center. */
  41.     private final Vector3D.Unit pole;

  42.     /** First axis in the equator plane, origin of the azimuth angles. */
  43.     private final Vector3D.Unit u;

  44.     /** Second axis in the equator plane, in quadrature with respect to u. */
  45.     private final Vector3D.Unit v;

  46.     /** Simple constructor. Callers are responsible for ensuring the inputs are valid.
  47.      * @param pole pole vector of the great circle
  48.      * @param u u axis in the equator plane
  49.      * @param v v axis in the equator plane
  50.      * @param precision precision context used for floating point comparisons
  51.      */
  52.     GreatCircle(final Vector3D.Unit pole, final Vector3D.Unit u, final Vector3D.Unit v,
  53.             final Precision.DoubleEquivalence precision) {
  54.         super(precision);

  55.         this.pole = pole;
  56.         this.u = u;
  57.         this.v = v;
  58.     }

  59.     /** Get the pole of the great circle. This vector is perpendicular to the
  60.      * equator plane of the instance.
  61.      * @return pole of the great circle
  62.      */
  63.     public Vector3D.Unit getPole() {
  64.         return pole;
  65.     }

  66.     /** Get the spherical point located at the positive pole of the instance.
  67.      * @return the spherical point located at the positive pole of the instance
  68.      */
  69.     public Point2S getPolePoint() {
  70.         return Point2S.from(pole);
  71.     }

  72.     /** Get the u axis of the great circle. This vector is located in the equator plane and defines
  73.      * the {@code 0pi} location of the embedded subspace.
  74.      * @return u axis of the great circle
  75.      */
  76.     public Vector3D.Unit getU() {
  77.         return u;
  78.     }

  79.     /** Get the v axis of the great circle. This vector lies in the equator plane,
  80.      * perpendicular to the u-axis.
  81.      * @return v axis of the great circle
  82.      */
  83.     public Vector3D.Unit getV() {
  84.         return v;
  85.     }

  86.     /** Get the w (pole) axis of the great circle. The method is equivalent to {@code #getPole()}.
  87.      * @return the w (pole) axis of the great circle.
  88.      * @see #getPole()
  89.      */
  90.     public Vector3D.Unit getW() {
  91.         return getPole();
  92.     }

  93.     /** {@inheritDoc}
  94.      *
  95.      * <p>The returned offset values are in the range {@code [-pi/2, +pi/2]},
  96.      * with a point directly on the circle's pole vector having an offset of
  97.      * {@code -pi/2} and its antipodal point having an offset of {@code +pi/2}.
  98.      * Thus, the circle's pole vector points toward the <em>minus</em> side of
  99.      * the hyperplane.</p>
  100.      *
  101.      * @see #offset(Vector3D)
  102.      */
  103.     @Override
  104.     public double offset(final Point2S point) {
  105.         return offset(point.getVector());
  106.     }

  107.     /** Get the offset (oriented distance) of a direction.
  108.      *
  109.      * <p>The offset computed here is equal to the angle between the circle's
  110.      * pole and the given vector minus {@code pi/2}. Thus, the pole vector
  111.      * has an offset of {@code -pi/2}, a point on the circle itself has an
  112.      * offset of {@code 0}, and the negation of the pole vector has an offset
  113.      * of {@code +pi/2}.</p>
  114.      * @param vec vector to compute the offset for
  115.      * @return the offset (oriented distance) of a direction
  116.      */
  117.     public double offset(final Vector3D vec) {
  118.         return pole.angle(vec) - Angle.PI_OVER_TWO;
  119.     }

  120.     /** Get the azimuth angle of a point relative to this great circle instance,
  121.      *  in the range {@code [0, 2pi)}.
  122.      * @param pt point to compute the azimuth for
  123.      * @return azimuth angle of the point in the range {@code [0, 2pi)}
  124.      */
  125.     public double azimuth(final Point2S pt) {
  126.         return azimuth(pt.getVector());
  127.     }

  128.     /** Get the azimuth angle of a vector in the range {@code [0, 2pi)}.
  129.      * The azimuth angle is the angle of the projection of the argument on the
  130.      * equator plane relative to the plane's u-axis. Since the vector is
  131.      * projected onto the equator plane, it does not need to belong to the circle.
  132.      * Vectors parallel to the great circle's pole do not have a defined azimuth angle.
  133.      * In these cases, the method follows the rules of the
  134.      * {@code Math#atan2(double, double)} method and returns {@code 0}.
  135.      * @param vector vector to compute the great circle azimuth of
  136.      * @return azimuth angle of the vector around the great circle in the range
  137.      *      {@code [0, 2pi)}
  138.      * @see #toSubspace(Point2S)
  139.      */
  140.     public double azimuth(final Vector3D vector) {
  141.         double az = Math.atan2(vector.dot(v), vector.dot(u));

  142.         // adjust range
  143.         if (az < 0) {
  144.             az += Angle.TWO_PI;
  145.         }

  146.         return az;
  147.     }

  148.     /** Get the vector on the great circle with the given azimuth angle.
  149.      * @param azimuth azimuth angle in radians
  150.      * @return the point on the great circle with the given phase angle
  151.      */
  152.     public Vector3D vectorAt(final double azimuth) {
  153.         return Vector3D.Sum.create()
  154.                 .addScaled(Math.cos(azimuth), u)
  155.                 .addScaled(Math.sin(azimuth), v).get();
  156.     }

  157.     /** {@inheritDoc} */
  158.     @Override
  159.     public Point2S project(final Point2S point) {
  160.         final double az = azimuth(point.getVector());
  161.         return Point2S.from(vectorAt(az));
  162.     }

  163.     /** {@inheritDoc}
  164.      *
  165.      * <p>The returned instance has the same u-axis but opposite pole and v-axis
  166.      * as this instance.</p>
  167.      */
  168.     @Override
  169.     public GreatCircle reverse() {
  170.         return new GreatCircle(pole.negate(), u, v.negate(), getPrecision());
  171.     }

  172.     /** {@inheritDoc} */
  173.     @Override
  174.     public GreatCircle transform(final Transform<Point2S> transform) {
  175.         final Point2S tu = transform.apply(Point2S.from(u));
  176.         final Point2S tv = transform.apply(Point2S.from(v));

  177.         return GreatCircles.fromPoints(tu, tv, getPrecision());
  178.     }

  179.     /** {@inheritDoc} */
  180.     @Override
  181.     public boolean similarOrientation(final Hyperplane<Point2S> other) {
  182.         final GreatCircle otherCircle = (GreatCircle) other;
  183.         return pole.dot(otherCircle.pole) > 0.0;
  184.     }

  185.     /** {@inheritDoc} */
  186.     @Override
  187.     public GreatArc span() {
  188.         return GreatCircles.arcFromInterval(this, AngularInterval.full());
  189.     }

  190.     /** Create an arc on this circle between the given points.
  191.      * @param start start point
  192.      * @param end end point
  193.      * @return an arc on this circle between the given points
  194.      * @throws IllegalArgumentException if the specified interval is not
  195.      *      convex (ie, the angle between the points is greater than {@code pi}
  196.      */
  197.     public GreatArc arc(final Point2S start, final Point2S end) {
  198.         return arc(toSubspace(start), toSubspace(end));
  199.     }

  200.     /** Create an arc on this circle between the given subspace points.
  201.      * @param start start subspace point
  202.      * @param end end subspace point
  203.      * @return an arc on this circle between the given subspace points
  204.      * @throws IllegalArgumentException if the specified interval is not
  205.      *      convex (ie, the angle between the points is greater than {@code pi}
  206.      */
  207.     public GreatArc arc(final Point1S start, final Point1S end) {
  208.         return arc(start.getAzimuth(), end.getAzimuth());
  209.     }

  210.     /** Create an arc on this circle between the given subspace azimuth values.
  211.      * @param start start subspace azimuth
  212.      * @param end end subspace azimuth
  213.      * @return an arc on this circle between the given subspace azimuths
  214.      * @throws IllegalArgumentException if the specified interval is not
  215.      *      convex (ie, the angle between the points is greater than {@code pi}
  216.      */
  217.     public GreatArc arc(final double start, final double end) {
  218.         return arc(AngularInterval.Convex.of(start, end, getPrecision()));
  219.     }

  220.     /** Create an arc on this circle consisting of the given subspace interval.
  221.      * @param interval subspace interval
  222.      * @return an arc on this circle consisting of the given subspace interval
  223.      */
  224.     public GreatArc arc(final AngularInterval.Convex interval) {
  225.         return GreatCircles.arcFromInterval(this, interval);
  226.     }

  227.     /** Return one of the two intersection points between this instance and the argument.
  228.      * If the circles occupy the same space (ie, their poles are parallel or anti-parallel),
  229.      * then null is returned. Otherwise, the intersection located at the cross product of
  230.      * the pole of this instance and that of the argument is returned (ie, {@code thisPole.cross(otherPole)}.
  231.      * The other intersection point of the pair is antipodal to this point.
  232.      * @param other circle to intersect with
  233.      * @return one of the two intersection points between this instance and the argument
  234.      */
  235.     public Point2S intersection(final GreatCircle other) {
  236.         final Vector3D cross = pole.cross(other.pole);
  237.         if (!cross.eq(Vector3D.ZERO, getPrecision())) {
  238.             return Point2S.from(cross);
  239.         }

  240.         return null;
  241.     }

  242.     /** Compute the angle between this great circle and the argument.
  243.      * The return value is the angle between the poles of the two circles,
  244.      * in the range {@code [0, pi]}.
  245.      * @param other great circle to compute the angle with
  246.      * @return the angle between this great circle and the argument in the
  247.      *      range {@code [0, pi]}
  248.      * @see #angle(GreatCircle, Point2S)
  249.      */
  250.     public double angle(final GreatCircle other) {
  251.         return pole.angle(other.pole);
  252.     }

  253.     /** Compute the angle between this great circle and the argument, measured
  254.      * at the intersection point closest to the given point. The value is computed
  255.      * as if a tangent line was drawn from each great circle at the intersection
  256.      * point closest to {@code pt}, and the angle required to rotate the tangent
  257.      * line representing the current instance to align with that of the given
  258.      * instance was measured. The return value lies in the range {@code [-pi, pi)} and
  259.      * has an absolute value equal to that returned by {@link #angle(GreatCircle)}, but
  260.      * possibly a different sign. If the given point is equidistant from both intersection
  261.      * points (as evaluated by this instance's precision context), then the point is assumed
  262.      * to be closest to the point opposite the cross product of the two poles.
  263.      * @param other great circle to compute the angle with
  264.      * @param pt point determining the circle intersection to compute the angle at
  265.      * @return the angle between this great circle and the argument as measured at the
  266.      *      intersection point closest to the given point; the value is in the range
  267.      *      {@code [-pi, pi)}
  268.      * @see #angle(GreatCircle)
  269.      */
  270.     public double angle(final GreatCircle other, final Point2S pt) {
  271.         final double theta = angle(other);
  272.         final Vector3D cross = pole.cross(other.pole);

  273.         return getPrecision().gt(pt.getVector().dot(cross), 0) ?
  274.                 theta :
  275.                 -theta;
  276.     }

  277.     /** {@inheritDoc} */
  278.     @Override
  279.     public Point1S toSubspace(final Point2S point) {
  280.         return Point1S.of(azimuth(point.getVector()));
  281.     }

  282.     /** {@inheritDoc} */
  283.     @Override
  284.     public Point2S toSpace(final Point1S point) {
  285.         return Point2S.from(vectorAt(point.getAzimuth()));
  286.     }

  287.     /** Return true if this instance should be considered equivalent to the argument, using the
  288.      * given precision context for comparison. Instances are considered equivalent if have equivalent
  289.      * {@code pole}, {@code u}, and {@code v} vectors.
  290.      * @param other great circle to compare with
  291.      * @param precision precision context to use for the comparison
  292.      * @return true if this instance should be considered equivalent to the argument
  293.      * @see Vector3D#eq(Vector3D, Precision.DoubleEquivalence)
  294.      */
  295.     public boolean eq(final GreatCircle other, final Precision.DoubleEquivalence precision) {
  296.         return pole.eq(other.pole, precision) &&
  297.                 u.eq(other.u, precision) &&
  298.                 v.eq(other.v, precision);
  299.     }

  300.     /** {@inheritDoc} */
  301.     @Override
  302.     public int hashCode() {
  303.         return Objects.hash(pole, u, v, getPrecision());
  304.     }

  305.     /** {@inheritDoc} */
  306.     @Override
  307.     public boolean equals(final Object obj) {
  308.         if (this == obj) {
  309.             return true;
  310.         } else if (!(obj instanceof GreatCircle)) {
  311.             return false;
  312.         }

  313.         final GreatCircle other = (GreatCircle) obj;

  314.         return Objects.equals(this.pole, other.pole) &&
  315.                 Objects.equals(this.u, other.u) &&
  316.                 Objects.equals(this.v, other.v) &&
  317.                 Objects.equals(this.getPrecision(), other.getPrecision());
  318.     }

  319.     /** {@inheritDoc} */
  320.     @Override
  321.     public String toString() {
  322.         final StringBuilder sb = new StringBuilder();
  323.         sb.append(getClass().getSimpleName())
  324.             .append("[pole= ")
  325.             .append(pole)
  326.             .append(", u= ")
  327.             .append(u)
  328.             .append(", v= ")
  329.             .append(v)
  330.             .append(']');

  331.         return sb.toString();
  332.     }
  333. }