001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.geometry.spherical.twod;
018
019import java.util.Objects;
020
021import org.apache.commons.geometry.core.Transform;
022import org.apache.commons.geometry.core.partitioning.AbstractHyperplane;
023import org.apache.commons.geometry.core.partitioning.EmbeddingHyperplane;
024import org.apache.commons.geometry.core.partitioning.Hyperplane;
025import org.apache.commons.geometry.euclidean.threed.Vector3D;
026import org.apache.commons.geometry.spherical.oned.AngularInterval;
027import org.apache.commons.geometry.spherical.oned.Point1S;
028import org.apache.commons.numbers.angle.Angle;
029import org.apache.commons.numbers.core.Precision;
030
031/** Class representing a great circle on the 2-sphere. A great circle is the
032 * intersection of a sphere with a plane that passes through its center. It is
033 * the largest diameter circle that can be drawn on the sphere and partitions the
034 * sphere into two hemispheres. The vectors {@code u} and {@code v} lie in the great
035 * circle plane, while the vector {@code w} (the pole) is perpendicular to it. The
036 * pole vector points toward the <em>minus</em> side of the hyperplane.
037 *
038 * <p>Instances of this class are guaranteed to be immutable.</p>
039 * @see GreatCircles
040 */
041public final class GreatCircle extends AbstractHyperplane<Point2S>
042    implements EmbeddingHyperplane<Point2S, Point1S> {
043    /** Pole or circle center. */
044    private final Vector3D.Unit pole;
045
046    /** First axis in the equator plane, origin of the azimuth angles. */
047    private final Vector3D.Unit u;
048
049    /** Second axis in the equator plane, in quadrature with respect to u. */
050    private final Vector3D.Unit v;
051
052    /** Simple constructor. Callers are responsible for ensuring the inputs are valid.
053     * @param pole pole vector of the great circle
054     * @param u u axis in the equator plane
055     * @param v v axis in the equator plane
056     * @param precision precision context used for floating point comparisons
057     */
058    GreatCircle(final Vector3D.Unit pole, final Vector3D.Unit u, final Vector3D.Unit v,
059            final Precision.DoubleEquivalence precision) {
060        super(precision);
061
062        this.pole = pole;
063        this.u = u;
064        this.v = v;
065    }
066
067    /** Get the pole of the great circle. This vector is perpendicular to the
068     * equator plane of the instance.
069     * @return pole of the great circle
070     */
071    public Vector3D.Unit getPole() {
072        return pole;
073    }
074
075    /** Get the spherical point located at the positive pole of the instance.
076     * @return the spherical point located at the positive pole of the instance
077     */
078    public Point2S getPolePoint() {
079        return Point2S.from(pole);
080    }
081
082    /** Get the u axis of the great circle. This vector is located in the equator plane and defines
083     * the {@code 0pi} location of the embedded subspace.
084     * @return u axis of the great circle
085     */
086    public Vector3D.Unit getU() {
087        return u;
088    }
089
090    /** Get the v axis of the great circle. This vector lies in the equator plane,
091     * perpendicular to the u-axis.
092     * @return v axis of the great circle
093     */
094    public Vector3D.Unit getV() {
095        return v;
096    }
097
098    /** Get the w (pole) axis of the great circle. The method is equivalent to {@code #getPole()}.
099     * @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}