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.core.precision.DoublePrecisionContext;
26  import org.apache.commons.geometry.euclidean.threed.Vector3D;
27  import org.apache.commons.geometry.spherical.oned.AngularInterval;
28  import org.apache.commons.geometry.spherical.oned.Point1S;
29  import org.apache.commons.numbers.angle.PlaneAngleRadians;
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 DoublePrecisionContext 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) - PlaneAngleRadians.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 += PlaneAngleRadians.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.linearCombination(Math.cos(azimuth), u, Math.sin(azimuth), v);
173     }
174 
175     /** {@inheritDoc} */
176     @Override
177     public Point2S project(final Point2S point) {
178         final double az = azimuth(point.getVector());
179         return Point2S.from(vectorAt(az));
180     }
181 
182     /** {@inheritDoc}
183      *
184      * <p>The returned instance has the same u-axis but opposite pole and v-axis
185      * as this instance.</p>
186      */
187     @Override
188     public GreatCircle reverse() {
189         return new GreatCircle(pole.negate(), u, v.negate(), getPrecision());
190     }
191 
192     /** {@inheritDoc} */
193     @Override
194     public GreatCircle transform(final Transform<Point2S> transform) {
195         final Point2S tu = transform.apply(Point2S.from(u));
196         final Point2S tv = transform.apply(Point2S.from(v));
197 
198         return GreatCircles.fromPoints(tu, tv, getPrecision());
199     }
200 
201     /** {@inheritDoc} */
202     @Override
203     public boolean similarOrientation(final Hyperplane<Point2S> other) {
204         final GreatCircle otherCircle = (GreatCircle) other;
205         return pole.dot(otherCircle.pole) > 0.0;
206     }
207 
208     /** {@inheritDoc} */
209     @Override
210     public GreatArc span() {
211         return GreatCircles.arcFromInterval(this, AngularInterval.full());
212     }
213 
214     /** Create an arc on this circle between the given points.
215      * @param start start point
216      * @param end end point
217      * @return an arc on this circle between the given points
218      * @throws IllegalArgumentException if the specified interval is not
219      *      convex (ie, the angle between the points is greater than {@code pi}
220      */
221     public GreatArc arc(final Point2S start, final Point2S end) {
222         return arc(toSubspace(start), toSubspace(end));
223     }
224 
225     /** Create an arc on this circle between the given subspace points.
226      * @param start start subspace point
227      * @param end end subspace point
228      * @return an arc on this circle between the given subspace points
229      * @throws IllegalArgumentException if the specified interval is not
230      *      convex (ie, the angle between the points is greater than {@code pi}
231      */
232     public GreatArc arc(final Point1S start, final Point1S end) {
233         return arc(start.getAzimuth(), end.getAzimuth());
234     }
235 
236     /** Create an arc on this circle between the given subspace azimuth values.
237      * @param start start subspace azimuth
238      * @param end end subspace azimuth
239      * @return an arc on this circle between the given subspace azimuths
240      * @throws IllegalArgumentException if the specified interval is not
241      *      convex (ie, the angle between the points is greater than {@code pi}
242      */
243     public GreatArc arc(final double start, final double end) {
244         return arc(AngularInterval.Convex.of(start, end, getPrecision()));
245     }
246 
247     /** Create an arc on this circle consisting of the given subspace interval.
248      * @param interval subspace interval
249      * @return an arc on this circle consisting of the given subspace interval
250      */
251     public GreatArc arc(final AngularInterval.Convex interval) {
252         return GreatCircles.arcFromInterval(this, interval);
253     }
254 
255     /** Return one of the two intersection points between this instance and the argument.
256      * If the circles occupy the same space (ie, their poles are parallel or anti-parallel),
257      * then null is returned. Otherwise, the intersection located at the cross product of
258      * the pole of this instance and that of the argument is returned (ie, {@code thisPole.cross(otherPole)}.
259      * The other intersection point of the pair is antipodal to this point.
260      * @param other circle to intersect with
261      * @return one of the two intersection points between this instance and the argument
262      */
263     public Point2S intersection(final GreatCircle other) {
264         final Vector3D cross = pole.cross(other.pole);
265         if (!cross.eq(Vector3D.ZERO, getPrecision())) {
266             return Point2S.from(cross);
267         }
268 
269         return null;
270     }
271 
272     /** Compute the angle between this great circle and the argument.
273      * The return value is the angle between the poles of the two circles,
274      * in the range {@code [0, pi]}.
275      * @param other great circle to compute the angle with
276      * @return the angle between this great circle and the argument in the
277      *      range {@code [0, pi]}
278      * @see #angle(GreatCircle, Point2S)
279      */
280     public double angle(final GreatCircle other) {
281         return pole.angle(other.pole);
282     }
283 
284     /** Compute the angle between this great circle and the argument, measured
285      * at the intersection point closest to the given point. The value is computed
286      * as if a tangent line was drawn from each great circle at the intersection
287      * point closest to {@code pt}, and the angle required to rotate the tangent
288      * line representing the current instance to align with that of the given
289      * instance was measured. The return value lies in the range {@code [-pi, pi)} and
290      * has an absolute value equal to that returned by {@link #angle(GreatCircle)}, but
291      * possibly a different sign. If the given point is equidistant from both intersection
292      * points (as evaluated by this instance's precision context), then the point is assumed
293      * to be closest to the point opposite the cross product of the two poles.
294      * @param other great circle to compute the angle with
295      * @param pt point determining the circle intersection to compute the angle at
296      * @return the angle between this great circle and the argument as measured at the
297      *      intersection point closest to the given point; the value is in the range
298      *      {@code [-pi, pi)}
299      * @see #angle(GreatCircle)
300      */
301     public double angle(final GreatCircle other, final Point2S pt) {
302         final double theta = angle(other);
303         final Vector3D cross = pole.cross(other.pole);
304 
305         return getPrecision().gt(pt.getVector().dot(cross), 0) ?
306                 theta :
307                 -theta;
308     }
309 
310     /** {@inheritDoc} */
311     @Override
312     public Point1S toSubspace(final Point2S point) {
313         return Point1S.of(azimuth(point.getVector()));
314     }
315 
316     /** {@inheritDoc} */
317     @Override
318     public Point2S toSpace(final Point1S point) {
319         return Point2S.from(vectorAt(point.getAzimuth()));
320     }
321 
322     /** Return true if this instance should be considered equivalent to the argument, using the
323      * given precision context for comparison. Instances are considered equivalent if have equivalent
324      * {@code pole}, {@code u}, and {@code v} vectors.
325      * @param other great circle to compare with
326      * @param precision precision context to use for the comparison
327      * @return true if this instance should be considered equivalent to the argument
328      * @see Vector3D#eq(Vector3D, DoublePrecisionContext)
329      */
330     public boolean eq(final GreatCircle other, final DoublePrecisionContext precision) {
331         return pole.eq(other.pole, precision) &&
332                 u.eq(other.u, precision) &&
333                 v.eq(other.v, precision);
334     }
335 
336     /** {@inheritDoc} */
337     @Override
338     public int hashCode() {
339         return Objects.hash(pole, u, v, getPrecision());
340     }
341 
342     /** {@inheritDoc} */
343     @Override
344     public boolean equals(final Object obj) {
345         if (this == obj) {
346             return true;
347         } else if (!(obj instanceof GreatCircle)) {
348             return false;
349         }
350 
351         final GreatCircle other = (GreatCircle) obj;
352 
353         return Objects.equals(this.pole, other.pole) &&
354                 Objects.equals(this.u, other.u) &&
355                 Objects.equals(this.v, other.v) &&
356                 Objects.equals(this.getPrecision(), other.getPrecision());
357     }
358 
359     /** {@inheritDoc} */
360     @Override
361     public String toString() {
362         final StringBuilder sb = new StringBuilder();
363         sb.append(getClass().getSimpleName())
364             .append("[pole= ")
365             .append(pole)
366             .append(", u= ")
367             .append(u)
368             .append(", v= ")
369             .append(v)
370             .append(']');
371 
372         return sb.toString();
373     }
374 }