Point2S.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.Comparator;

  19. import org.apache.commons.geometry.core.Point;
  20. import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
  21. import org.apache.commons.geometry.euclidean.threed.SphericalCoordinates;
  22. import org.apache.commons.geometry.euclidean.threed.Vector3D;
  23. import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
  24. import org.apache.commons.numbers.core.Precision;

  25. /** This class represents a point on the 2-sphere.
  26.  * <p>Instances of this class are guaranteed to be immutable.</p>
  27.  */
  28. public final class Point2S implements Point<Point2S> {

  29.     /** +I (coordinates: ( azimuth = 0, polar = pi/2 )). */
  30.     public static final Point2S PLUS_I = new Point2S(0, 0.5 * Math.PI, Vector3D.Unit.PLUS_X);

  31.     /** +J (coordinates: ( azimuth = pi/2, polar = pi/2 ))). */
  32.     public static final Point2S PLUS_J = new Point2S(0.5 * Math.PI, 0.5 * Math.PI, Vector3D.Unit.PLUS_Y);

  33.     /** +K (coordinates: ( azimuth = any angle, polar = 0 )). */
  34.     public static final Point2S PLUS_K = new Point2S(0, 0, Vector3D.Unit.PLUS_Z);

  35.     /** -I (coordinates: ( azimuth = pi, polar = pi/2 )). */
  36.     public static final Point2S MINUS_I = new Point2S(Math.PI, 0.5 * Math.PI, Vector3D.Unit.MINUS_X);

  37.     /** -J (coordinates: ( azimuth = 3pi/2, polar = pi/2 )). */
  38.     public static final Point2S MINUS_J = new Point2S(1.5 * Math.PI, 0.5 * Math.PI, Vector3D.Unit.MINUS_Y);

  39.     /** -K (coordinates: ( azimuth = any angle, polar = pi )). */
  40.     public static final Point2S MINUS_K = new Point2S(0, Math.PI, Vector3D.Unit.MINUS_Z);

  41.     /** A point with all coordinates set to NaN. */
  42.     public static final Point2S NaN = new Point2S(Double.NaN, Double.NaN, null);

  43.     /** Comparator that sorts points in component-wise ascending order, first sorting
  44.      * by polar value and then by azimuth value. Points are only considered equal if
  45.      * their components match exactly. Null arguments are evaluated as being greater
  46.      * than non-null arguments.
  47.      */
  48.     public static final Comparator<Point2S> POLAR_AZIMUTH_ASCENDING_ORDER = (a, b) -> {
  49.         int cmp = 0;

  50.         if (a != null && b != null) {
  51.             cmp = Double.compare(a.getPolar(), b.getPolar());

  52.             if (cmp == 0) {
  53.                 cmp = Double.compare(a.getAzimuth(), b.getAzimuth());
  54.             }
  55.         } else if (a != null) {
  56.             cmp = -1;
  57.         } else if (b != null) {
  58.             cmp = 1;
  59.         }

  60.         return cmp;
  61.     };
  62.     /** Azimuthal angle in the x-y plane. */
  63.     private final double azimuth;

  64.     /** Polar angle. */
  65.     private final double polar;

  66.     /** Corresponding 3D normalized vector. */
  67.     private final Vector3D.Unit vector;

  68.     /** Build a point from its internal components.
  69.      * @param azimuth azimuthal angle in the x-y plane
  70.      * @param polar polar angle
  71.      * @param vector corresponding vector; if null, the vector is computed
  72.      */
  73.     private Point2S(final double azimuth, final double polar, final Vector3D.Unit vector) {
  74.         this.azimuth = SphericalCoordinates.normalizeAzimuth(azimuth);
  75.         this.polar = SphericalCoordinates.normalizePolar(polar);
  76.         this.vector = (vector != null) ?
  77.                 vector :
  78.                 computeVector(azimuth, polar);
  79.     }

  80.     /** Get the azimuth angle in the x-y plane in the range {@code [0, 2pi)}.
  81.      * @return azimuth angle in the x-y plane in the range {@code [0, 2pi)}.
  82.      * @see Point2S#of(double, double)
  83.      */
  84.     public double getAzimuth() {
  85.         return azimuth;
  86.     }

  87.     /** Get the polar angle in the range {@code [0, pi)}.
  88.      * @return polar angle in the range {@code [0, pi)}.
  89.      * @see Point2S#of(double, double)
  90.      */
  91.     public double getPolar() {
  92.         return polar;
  93.     }

  94.     /** Get the corresponding normalized vector in 3D Euclidean space.
  95.      * This value will be null if the spherical coordinates of the point
  96.      * are infinite or NaN.
  97.      * @return normalized vector
  98.      */
  99.     public Vector3D.Unit getVector() {
  100.         return vector;
  101.     }

  102.     /** {@inheritDoc} */
  103.     @Override
  104.     public int getDimension() {
  105.         return 2;
  106.     }

  107.     /** {@inheritDoc} */
  108.     @Override
  109.     public boolean isNaN() {
  110.         return Double.isNaN(azimuth) || Double.isNaN(polar);
  111.     }

  112.     /** {@inheritDoc} */
  113.     @Override
  114.     public boolean isInfinite() {
  115.         return !isNaN() && (Double.isInfinite(azimuth) || Double.isInfinite(polar));
  116.     }

  117.     /** {@inheritDoc} */
  118.     @Override
  119.     public boolean isFinite() {
  120.         return Double.isFinite(azimuth) && Double.isFinite(polar);
  121.     }

  122.     /** Get the point exactly opposite this point on the sphere. The returned
  123.      * point is {@code pi} distance away from the current instance.
  124.      * @return the point exactly opposite this point on the sphere
  125.      */
  126.     public Point2S antipodal() {
  127.         return from(vector.negate());
  128.     }

  129.     /** {@inheritDoc} */
  130.     @Override
  131.     public double distance(final Point2S point) {
  132.         return distance(this, point);
  133.     }

  134.     /** Spherically interpolate a point along the shortest arc between this point and
  135.      * the given point. The parameter {@code t} controls the interpolation and is expected
  136.      * to be in the range {@code [0, 1]}, with {@code 0} returning a point equivalent to the
  137.      * current instance {@code 1} returning a point equivalent to the given instance. If the
  138.      * points are antipodal, then an arbitrary arc is chosen from the infinite number available.
  139.      * @param other other point to interpolate with
  140.      * @param t interpolation parameter
  141.      * @return spherically interpolated point
  142.      * @see QuaternionRotation#slerp(QuaternionRotation)
  143.      * @see QuaternionRotation#createVectorRotation(Vector3D, Vector3D)
  144.      */
  145.     public Point2S slerp(final Point2S other, final double t) {
  146.         final QuaternionRotation start = QuaternionRotation.identity();
  147.         final QuaternionRotation end = QuaternionRotation.createVectorRotation(getVector(), other.getVector());

  148.         final QuaternionRotation quat = start.slerp(end).apply(t);

  149.         return Point2S.from(quat.apply(getVector()));
  150.     }

  151.     /** Return true if this point should be considered equivalent to the argument using the
  152.      * given precision context. This will be true if the distance between the points is
  153.      * equivalent to zero as evaluated by the precision context.
  154.      * @param point point to compare with
  155.      * @param precision precision context used to perform floating point comparisons
  156.      * @return true if this point should be considered equivalent to the argument using the
  157.      *      given precision context
  158.      */
  159.     public boolean eq(final Point2S point, final Precision.DoubleEquivalence precision) {
  160.         return precision.eqZero(distance(point));
  161.     }

  162.     /** Get a hashCode for the point.
  163.      * .
  164.      * <p>All NaN values have the same hash code.</p>
  165.      *
  166.      * @return a hash code value for this object
  167.      */
  168.     @Override
  169.     public int hashCode() {
  170.         if (isNaN()) {
  171.             return 542;
  172.         }
  173.         return 134 * (37 * Double.hashCode(azimuth) +  Double.hashCode(polar));
  174.     }

  175.     /** Test for the equality of two points.
  176.      *
  177.      * <p>If all spherical coordinates of two points are exactly the same, and none are
  178.      * <code>Double.NaN</code>, the two points are considered to be equal. Note
  179.      * that the comparison is made using the azimuth and polar coordinates only; the
  180.      * corresponding 3D vectors are not compared. This is significant at the poles,
  181.      * where an infinite number of points share the same underlying 3D vector but may
  182.      * have different spherical coordinates. For example, the points {@code (0, 0)}
  183.      * and {@code (1, 0)} (both located at a pole but with different azimuths) will
  184.      * <em>not</em> be considered equal by this method, even though they share the
  185.      * exact same underlying 3D vector.</p>
  186.      *
  187.      * <p>
  188.      * <code>NaN</code> coordinates are considered to affect the point globally
  189.      * and be equals to each other - i.e, if either (or all) coordinates of the
  190.      * point are equal to <code>Double.NaN</code>, the point is equal to
  191.      * {@link #NaN}.
  192.      * </p>
  193.      *
  194.      * @param other Object to test for equality to this
  195.      * @return true if two points on the 2-sphere objects are exactly equal, false if
  196.      *         object is null, not an instance of Point2S, or
  197.      *         not equal to this Point2S instance
  198.      */
  199.     @Override
  200.     public boolean equals(final Object other) {
  201.         if (this == other) {
  202.             return true;
  203.         }
  204.         if (!(other instanceof Point2S)) {
  205.             return false;
  206.         }

  207.         final Point2S rhs = (Point2S) other;
  208.         if (rhs.isNaN()) {
  209.             return this.isNaN();
  210.         }

  211.         return Double.compare(azimuth, rhs.azimuth) == 0 &&
  212.                 Double.compare(polar, rhs.polar) == 0;
  213.     }

  214.     /** {@inheritDoc} */
  215.     @Override
  216.     public String toString() {
  217.         return SimpleTupleFormat.getDefault().format(getAzimuth(), getPolar());
  218.     }

  219.     /** Build a vector from its spherical coordinates.
  220.      * @param azimuth azimuthal angle in the x-y plane
  221.      * @param polar polar angle
  222.      * @return point instance with the given coordinates
  223.      * @see #getAzimuth()
  224.      * @see #getPolar()
  225.      */
  226.     public static Point2S of(final double azimuth, final double polar) {
  227.         return new Point2S(azimuth, polar, null);
  228.     }

  229.     /** Build a point from its underlying 3D vector.
  230.      * @param vector 3D vector
  231.      * @return point instance with the coordinates determined by the given 3D vector
  232.      * @exception IllegalStateException if vector norm is zero
  233.      */
  234.     public static Point2S from(final Vector3D vector) {
  235.         final SphericalCoordinates coords = SphericalCoordinates.fromCartesian(vector);

  236.         return new Point2S(coords.getAzimuth(), coords.getPolar(), vector.normalize());
  237.     }

  238.     /** Parses the given string and returns a new point instance. The expected string
  239.      * format is the same as that returned by {@link #toString()}.
  240.      * @param str the string to parse
  241.      * @return point instance represented by the string
  242.      * @throws IllegalArgumentException if the given string has an invalid format
  243.      */
  244.     public static Point2S parse(final String str) {
  245.         return SimpleTupleFormat.getDefault().parse(str, Point2S::of);
  246.     }

  247.     /** Compute the distance (angular separation) between two points.
  248.      * @param p1 first vector
  249.      * @param p2 second vector
  250.      * @return the angular separation between p1 and p2
  251.      */
  252.     public static double distance(final Point2S p1, final Point2S p2) {
  253.         return p1.vector.angle(p2.vector);
  254.     }

  255.     /** Compute the 3D Euclidean vector associated with the given spherical coordinates.
  256.      * Null is returned if the coordinates are infinite or NaN.
  257.      * @param azimuth azimuth value
  258.      * @param polar polar value
  259.      * @return the 3D Euclidean vector associated with the given spherical coordinates
  260.      *      or null if either of the arguments are infinite or NaN.
  261.      */
  262.     private static Vector3D.Unit computeVector(final double azimuth, final double polar) {
  263.         if (Double.isFinite(azimuth) && Double.isFinite(polar)) {
  264.             return SphericalCoordinates.toCartesian(1, azimuth, polar).normalize();
  265.         }
  266.         return null;
  267.     }
  268. }