S2Point.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.  *      https://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. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.geometry.spherical.twod;

  22. import org.hipparchus.exception.MathIllegalArgumentException;
  23. import org.hipparchus.exception.MathRuntimeException;
  24. import org.hipparchus.geometry.Point;
  25. import org.hipparchus.geometry.Space;
  26. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  27. import org.hipparchus.util.FastMath;
  28. import org.hipparchus.util.MathUtils;
  29. import org.hipparchus.util.SinCos;

  30. /** This class represents a point on the 2-sphere.
  31.  * <p>
  32.  * We use the mathematical convention to use the azimuthal angle \( \theta \)
  33.  * in the x-y plane as the first coordinate, and the polar angle \( \varphi \)
  34.  * as the second coordinate (see <a
  35.  * href="http://mathworld.wolfram.com/SphericalCoordinates.html">Spherical
  36.  * Coordinates</a> in MathWorld).
  37.  * </p>
  38.  * <p>Instances of this class are guaranteed to be immutable.</p>
  39.  */
  40. public class S2Point implements Point<Sphere2D, S2Point> {

  41.     /** +I (coordinates: \( \theta = 0, \varphi = \pi/2 \)). */
  42.     public static final S2Point PLUS_I = new S2Point(0, MathUtils.SEMI_PI, Vector3D.PLUS_I);

  43.     /** +J (coordinates: \( \theta = \pi/2, \varphi = \pi/2 \))). */
  44.     public static final S2Point PLUS_J = new S2Point(MathUtils.SEMI_PI, MathUtils.SEMI_PI, Vector3D.PLUS_J);

  45.     /** +K (coordinates: \( \theta = any angle, \varphi = 0 \)). */
  46.     public static final S2Point PLUS_K = new S2Point(0, 0, Vector3D.PLUS_K);

  47.     /** -I (coordinates: \( \theta = \pi, \varphi = \pi/2 \)). */
  48.     public static final S2Point MINUS_I = new S2Point(FastMath.PI, MathUtils.SEMI_PI, Vector3D.MINUS_I);

  49.     /** -J (coordinates: \( \theta = 3\pi/2, \varphi = \pi/2 \)). */
  50.     public static final S2Point MINUS_J = new S2Point(1.5 * FastMath.PI, MathUtils.SEMI_PI, Vector3D.MINUS_J);

  51.     /** -K (coordinates: \( \theta = any angle, \varphi = \pi \)). */
  52.     public static final S2Point MINUS_K = new S2Point(0, FastMath.PI, Vector3D.MINUS_K);

  53.     // CHECKSTYLE: stop ConstantName
  54.     /** A vector with all coordinates set to NaN. */
  55.     public static final S2Point NaN = new S2Point(Double.NaN, Double.NaN, Vector3D.NaN);
  56.     // CHECKSTYLE: resume ConstantName

  57.     /** Serializable UID. */
  58.     private static final long serialVersionUID = 20131218L;

  59.     /** Azimuthal angle \( \theta \) in the x-y plane. */
  60.     private final double theta;

  61.     /** Polar angle \( \varphi \). */
  62.     private final double phi;

  63.     /** Corresponding 3D normalized vector. */
  64.     private final Vector3D vector;

  65.     /** Simple constructor.
  66.      * Build a vector from its spherical coordinates
  67.      * @param theta azimuthal angle \( \theta \) in the x-y plane
  68.      * @param phi polar angle \( \varphi \)
  69.      * @see #getTheta()
  70.      * @see #getPhi()
  71.      * @exception MathIllegalArgumentException if \( \varphi \) is not in the [\( 0; \pi \)] range
  72.      */
  73.     public S2Point(final double theta, final double phi)
  74.         throws MathIllegalArgumentException {
  75.         this(theta, phi, vector(theta, phi));
  76.     }

  77.     /** Simple constructor.
  78.      * Build a vector from its underlying 3D vector
  79.      * @param vector 3D vector
  80.      * @exception MathRuntimeException if vector norm is zero
  81.      */
  82.     public S2Point(final Vector3D vector) throws MathRuntimeException {
  83.         this(FastMath.atan2(vector.getY(), vector.getX()), Vector3D.angle(Vector3D.PLUS_K, vector),
  84.              vector.normalize());
  85.     }

  86.     /** Build a point from its internal components.
  87.      * @param theta azimuthal angle \( \theta \) in the x-y plane
  88.      * @param phi polar angle \( \varphi \)
  89.      * @param vector corresponding vector
  90.      */
  91.     private S2Point(final double theta, final double phi, final Vector3D vector) {
  92.         this.theta  = theta;
  93.         this.phi    = phi;
  94.         this.vector = vector;
  95.     }

  96.     /** Build the normalized vector corresponding to spherical coordinates.
  97.      * @param theta azimuthal angle \( \theta \) in the x-y plane
  98.      * @param phi polar angle \( \varphi \)
  99.      * @return normalized vector
  100.      * @exception MathIllegalArgumentException if \( \varphi \) is not in the [\( 0; \pi \)] range
  101.      */
  102.     private static Vector3D vector(final double theta, final double phi)
  103.        throws MathIllegalArgumentException {

  104.         MathUtils.checkRangeInclusive(phi, 0, FastMath.PI);

  105.         final SinCos scTheta = FastMath.sinCos(theta);
  106.         final SinCos scPhi   = FastMath.sinCos(phi);

  107.         return new Vector3D(scTheta.cos() * scPhi.sin(), scTheta.sin() * scPhi.sin(), scPhi.cos());

  108.     }

  109.     /** Get the azimuthal angle \( \theta \) in the x-y plane.
  110.      * @return azimuthal angle \( \theta \) in the x-y plane
  111.      * @see #S2Point(double, double)
  112.      */
  113.     public double getTheta() {
  114.         return theta;
  115.     }

  116.     /** Get the polar angle \( \varphi \).
  117.      * @return polar angle \( \varphi \)
  118.      * @see #S2Point(double, double)
  119.      */
  120.     public double getPhi() {
  121.         return phi;
  122.     }

  123.     /** Get the corresponding normalized vector in the 3D euclidean space.
  124.      * @return normalized vector
  125.      */
  126.     public Vector3D getVector() {
  127.         return vector;
  128.     }

  129.     /** {@inheritDoc} */
  130.     @Override
  131.     public Space getSpace() {
  132.         return Sphere2D.getInstance();
  133.     }

  134.     /** {@inheritDoc} */
  135.     @Override
  136.     public boolean isNaN() {
  137.         return Double.isNaN(theta) || Double.isNaN(phi);
  138.     }

  139.     /** Get the opposite of the instance.
  140.      * @return a new vector which is opposite to the instance
  141.      */
  142.     public S2Point negate() {
  143.         return new S2Point(FastMath.PI + theta, FastMath.PI - phi, vector.negate());
  144.     }

  145.     /** {@inheritDoc} */
  146.     @Override
  147.     public double distance(final S2Point point) {
  148.         return distance(this, point);
  149.     }

  150.     /** Compute the distance (angular separation) between two points.
  151.      * @param p1 first vector
  152.      * @param p2 second vector
  153.      * @return the angular separation between p1 and p2
  154.      */
  155.     public static double distance(S2Point p1, S2Point p2) {
  156.         return Vector3D.angle(p1.vector, p2.vector);
  157.     }

  158.     /** {@inheritDoc} */
  159.     @Override
  160.     public S2Point moveTowards(final S2Point other, final double ratio) {
  161.         final double alpha = Vector3D.angle(vector, other.vector);
  162.         if (alpha == 0) {
  163.             // special case to avoid division by zero in normalization below
  164.             return this;
  165.         }
  166.         else {
  167.             final double sA = (FastMath.sin((1 - ratio) * alpha));
  168.             final double sB = FastMath.sin(ratio * alpha);
  169.             return new S2Point(new Vector3D(sA, vector, sB, other.vector));
  170.         }
  171.     }

  172.     /**
  173.      * Test for the equality of two points on the 2-sphere.
  174.      * <p>
  175.      * If all coordinates of two points are exactly the same, and none are
  176.      * {@code Double.NaN}, the two points are considered to be equal.
  177.      * </p>
  178.      * <p>
  179.      * {@code NaN} coordinates are considered to affect globally the point
  180.      * and be equals to each other - i.e, if either (or all) coordinates of the
  181.      * point are equal to {@code Double.NaN}, the point is equal to
  182.      * {@link #NaN}.
  183.      * </p>
  184.      *
  185.      * @param other Object to test for equality to this
  186.      * @return true if two points on the 2-sphere objects are equal, false if
  187.      *         object is null, not an instance of S2Point, or
  188.      *         not equal to this S2Point instance
  189.      *
  190.      */
  191.     @Override
  192.     public boolean equals(Object other) {

  193.         if (this == other) {
  194.             return true;
  195.         }

  196.         if (other instanceof S2Point) {
  197.             final S2Point rhs = (S2Point) other;
  198.             return theta == rhs.theta && phi == rhs.phi || isNaN() && rhs.isNaN();
  199.         }

  200.         return false;

  201.     }

  202.     /**
  203.      * Test for the equality of two points on the 2-sphere.
  204.      * <p>
  205.      * If all coordinates of two points are exactly the same, and none are
  206.      * {@code Double.NaN}, the two points are considered to be equal.
  207.      * </p>
  208.      * <p>
  209.      * In compliance with IEEE754 handling, if any coordinates of any of the
  210.      * two points are {@code NaN}, then the points are considered different.
  211.      * This implies that {@link #NaN S2Point.NaN}.equals({@link #NaN S2Point.NaN})
  212.      * returns {@code false} despite the instance is checked against itself.
  213.      * </p>
  214.      *
  215.      * @param other Object to test for equality to this
  216.      * @return true if two points objects are equal, false if
  217.      *         object is null, not an instance of S2Point, or
  218.      *         not equal to this S2Point instance
  219.      * @since 2.1
  220.      */
  221.     public boolean equalsIeee754(Object other) {

  222.         if (this == other && !isNaN()) {
  223.             return true;
  224.         }

  225.         if (other instanceof S2Point) {
  226.             final S2Point rhs = (S2Point) other;
  227.             return phi == rhs.phi && theta == rhs.theta;
  228.         }

  229.         return false;

  230.     }

  231.     /**
  232.      * Get a hashCode for the point.
  233.      * <p>
  234.      * All NaN values have the same hash code.</p>
  235.      *
  236.      * @return a hash code value for this object
  237.      */
  238.     @Override
  239.     public int hashCode() {
  240.         if (isNaN()) {
  241.             return 542;
  242.         }
  243.         return 134 * (37 * MathUtils.hash(theta) +  MathUtils.hash(phi));
  244.     }

  245.     @Override
  246.     public String toString() {
  247.         return "S2Point{" +
  248.                 "theta=" + theta +
  249.                 ", phi=" + phi +
  250.                 '}';
  251.     }

  252. }