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 }