| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.geometry.spherical.oned; |
| |
| import java.util.Comparator; |
| import java.util.Objects; |
| |
| import org.apache.commons.geometry.core.Geometry; |
| import org.apache.commons.geometry.core.Point; |
| import org.apache.commons.geometry.core.exception.GeometryValueException; |
| import org.apache.commons.geometry.core.internal.SimpleTupleFormat; |
| import org.apache.commons.geometry.core.precision.DoublePrecisionContext; |
| import org.apache.commons.geometry.euclidean.twod.PolarCoordinates; |
| import org.apache.commons.geometry.euclidean.twod.Vector2D; |
| import org.apache.commons.numbers.angle.PlaneAngle; |
| import org.apache.commons.numbers.angle.PlaneAngleRadians; |
| |
| /** This class represents a point on the 1-sphere, or in other words, an |
| * azimuth angle on a circle. The value of the azimuth angle is not normalized |
| * by default, meaning that instances can be constructed representing negative |
| * values or values greater than {@code 2pi}. However, instances separated by a |
| * multiple of {@code 2pi} are considered equivalent for most methods, with the |
| * exceptions being {@link #equals(Object)} and {@link #hashCode()}, where the |
| * azimuth values must match exactly in order for instances to be considered |
| * equal. |
| * |
| * <p>Instances of this class are guaranteed to be immutable.</p> |
| */ |
| public final class Point1S implements Point<Point1S> { |
| |
| /** A point with coordinates set to {@code 0*pi}. */ |
| public static final Point1S ZERO = Point1S.of(Geometry.ZERO_PI); |
| |
| /** A point with coordinates set to {@code pi}. */ |
| public static final Point1S PI = Point1S.of(Geometry.PI); |
| |
| // CHECKSTYLE: stop ConstantName |
| /** A point with all coordinates set to NaN. */ |
| public static final Point1S NaN = Point1S.of(Double.NaN); |
| // CHECKSTYLE: resume ConstantName |
| |
| /** Comparator that sorts points by normalized azimuth in ascending order. |
| * Points are only considered equal if their normalized azimuths match exactly. |
| * Null arguments are evaluated as being greater than non-null arguments. |
| * @see #getNormalizedAzimuth() |
| */ |
| public static final Comparator<Point1S> NORMALIZED_AZIMUTH_ASCENDING_ORDER = (a, b) -> { |
| int cmp = 0; |
| |
| if (a != null && b != null) { |
| cmp = Double.compare(a.getNormalizedAzimuth(), b.getNormalizedAzimuth()); |
| } else if (a != null) { |
| cmp = -1; |
| } else if (b != null) { |
| cmp = 1; |
| } |
| |
| return cmp; |
| }; |
| |
| /** Azimuthal angle in radians. */ |
| private final double azimuth; |
| |
| /** Normalized azimuth value in the range {@code [0, 2pi)}. */ |
| private final double normalizedAzimuth; |
| |
| /** Build a point from its internal components. |
| * @param azimuth azimuth angle |
| * @param normalizedAzimuth azimuth angle normalized to the range {@code [0, 2pi)} |
| */ |
| private Point1S(final double azimuth, final double normalizedAzimuth) { |
| this.azimuth = azimuth; |
| this.normalizedAzimuth = normalizedAzimuth; |
| } |
| |
| /** Get the azimuth angle in radians. This value is not normalized and |
| * can be any floating point number. |
| * @return azimuth angle |
| * @see Point1S#of(double) |
| */ |
| public double getAzimuth() { |
| return azimuth; |
| } |
| |
| /** Get the azimuth angle normalized to the range {@code [0, 2pi)}. |
| * @return the azimuth angle normalized to the range {@code [0, 2pi)}. |
| */ |
| public double getNormalizedAzimuth() { |
| return normalizedAzimuth; |
| } |
| |
| /** Get the normalized vector corresponding to this azimuth angle in 2D Euclidean space. |
| * @return normalized vector |
| */ |
| public Vector2D getVector() { |
| if (isFinite()) { |
| return PolarCoordinates.toCartesian(1, azimuth); |
| } |
| |
| return null; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public int getDimension() { |
| return 1; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean isNaN() { |
| return Double.isNaN(azimuth); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean isInfinite() { |
| return !isNaN() && Double.isInfinite(azimuth); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean isFinite() { |
| return Double.isFinite(azimuth); |
| } |
| |
| /** {@inheritDoc} |
| * |
| * <p>The returned value is the shortest angular distance between |
| * the two points, in the range {@code [0, pi]}.</p> |
| */ |
| @Override |
| public double distance(final Point1S point) { |
| return distance(this, point); |
| } |
| |
| /** Return the signed distance (angular separation) between this instance and the |
| * given point in the range {@code [-pi, pi)}. If {@code p1} is the current instance, |
| * {@code p2} the given point, and {@code d} the signed distance, then |
| * {@code p1.getAzimuth() + d} is an angle equivalent to {@code p2.getAzimuth()}. |
| * @param point point to compute the signed distance to |
| * @return the signed distance between this instance and the given point in the range |
| * {@code [-pi, pi)} |
| */ |
| public double signedDistance(final Point1S point) { |
| return signedDistance(this, point); |
| } |
| |
| /** Return an equivalent point with an azimuth value at or above the given base. |
| * The returned point has an azimuth value in the range {@code [base, base + 2pi)}. |
| * @param base point to place this instance's azimuth value above |
| * @return a point equivalent to the current instance but with an azimuth |
| * value in the range {@code [base, base + 2pi)} |
| * @throws GeometryValueException if the azimuth value is NaN or infinite and |
| * cannot be normalized |
| */ |
| public Point1S above(final Point1S base) { |
| return normalize(base.getAzimuth() + Geometry.PI); |
| } |
| |
| /** Return an equivalent point with an azimuth value strictly below the given base. |
| * The returned point has an azimuth value in the range {@code [base - 2pi, base)}. |
| * @param base point to place this instance's azimuth value below |
| * @return a point equivalent to the current instance but with an azimuth |
| * value in the range {@code [base - 2pi, base)} |
| * @throws GeometryValueException if the azimuth value is NaN or infinite and |
| * cannot be normalized |
| */ |
| public Point1S below(final Point1S base) { |
| return normalize(base.getAzimuth() - Geometry.PI); |
| } |
| |
| /** Normalize this point around the given center point. The azimuth value of |
| * the returned point is in the range {@code [center - pi, center + pi)}. |
| * @param center point to center this instance around |
| * @return a point equivalent to this instance but with an azimuth value |
| * in the range {@code [center - pi, center + pi)}. |
| * @throws GeometryValueException if the azimuth value is NaN or infinite and |
| * cannot be normalized |
| */ |
| public Point1S normalize(final Point1S center) { |
| return normalize(center.getAzimuth()); |
| } |
| |
| /** Return an equivalent point with an azimuth value normalized around the given center |
| * angle. The azimuth value of the returned point is in the range |
| * {@code [center - pi, center + pi)}. |
| * @param center angle to center this instance around |
| * @return a point equivalent to this instance but with an azimuth value |
| * in the range {@code [center - pi, center + pi)}. |
| * @throws GeometryValueException if the azimuth value is NaN or infinite and |
| * cannot be normalized |
| */ |
| public Point1S normalize(final double center) { |
| if (isFinite()) { |
| final double az = PlaneAngleRadians.normalize(azimuth, center); |
| return new Point1S(az, normalizedAzimuth); |
| } |
| throw new GeometryValueException("Cannot normalize azimuth value: " + azimuth); |
| } |
| |
| /** Get the point exactly opposite this point on the circle, {@code pi} distance away. |
| * The azimuth of the antipodal point is in the range {@code [0, 2pi)}. |
| * @return the point exactly opposite this point on the circle |
| */ |
| public Point1S antipodal() { |
| double az = normalizedAzimuth + Geometry.PI; |
| if (az >= Geometry.TWO_PI) { |
| az -= Geometry.TWO_PI; |
| } |
| |
| return Point1S.of(az); |
| } |
| |
| /** Return true if this instance is equivalent to the argument. The points are |
| * considered equivalent if the shortest angular distance between them is equal to |
| * zero as evaluated by the given precision context. This means that points that differ |
| * in azimuth by multiples of {@code 2pi} are considered equivalent. |
| * @param other point to compare with |
| * @param precision precision context used for floating point comparisons |
| * @return true if this instance is equivalent to the argument |
| */ |
| public boolean eq(final Point1S other, final DoublePrecisionContext precision) { |
| final double dist = signedDistance(other); |
| return precision.eqZero(dist); |
| } |
| |
| /** |
| * Get a hashCode for the point. Points normally must have exactly the |
| * same azimuth angles in order to have the same hash code. Points |
| * will angles that differ by multiples of {@code 2pi} will not |
| * necessarily have the same hash code. |
| * |
| * <p>All NaN values have the same hash code.</p> |
| * |
| * @return a hash code value for this object |
| */ |
| @Override |
| public int hashCode() { |
| if (isNaN()) { |
| return 542; |
| } |
| return 1759 * Objects.hash(azimuth, normalizedAzimuth); |
| } |
| |
| /** Test for the exact equality of two points on the 1-sphere. |
| * |
| * <p>If all coordinates of the given points are exactly the same, and none are |
| * <code>Double.NaN</code>, the points are considered to be equal. Points with |
| * azimuth values separated by multiples of {@code 2pi} are <em>not</em> considered |
| * equal.</p> |
| * |
| * <p><code>NaN</code> coordinates are considered to affect globally the vector |
| * and be equals to each other - i.e, if either (or all) coordinates of the |
| * point are equal to <code>Double.NaN</code>, the point is equal to |
| * {@link #NaN}.</p> |
| * |
| * @param other Object to test for equality to this |
| * @return true if two points on the 1-sphere objects are exactly equal, false if |
| * object is null, not an instance of Point1S, or |
| * not equal to this Point1S instance |
| * |
| */ |
| @Override |
| public boolean equals(final Object other) { |
| if (this == other) { |
| return true; |
| } |
| |
| if (other instanceof Point1S) { |
| final Point1S rhs = (Point1S) other; |
| |
| if (rhs.isNaN()) { |
| return this.isNaN(); |
| } |
| |
| return Double.compare(azimuth, rhs.azimuth) == 0 && |
| Double.compare(normalizedAzimuth, rhs.normalizedAzimuth) == 0; |
| } |
| |
| return false; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public String toString() { |
| return SimpleTupleFormat.getDefault().format(getAzimuth()); |
| } |
| |
| /** Create a new point instance from the given azimuth angle. |
| * @param azimuth azimuth angle in radians |
| * @return point instance with the given azimuth angle |
| * @see #getAzimuth() |
| */ |
| public static Point1S of(final double azimuth) { |
| final double normalizedAzimuth = PolarCoordinates.normalizeAzimuth(azimuth); |
| |
| return new Point1S(azimuth, normalizedAzimuth); |
| } |
| |
| /** Create a new point instance from the given azimuth angle. |
| * @param azimuth azimuth azimuth angle in radians |
| * @return point instance with the given azimuth angle |
| * @see #getAzimuth() |
| */ |
| public static Point1S of(final PlaneAngle azimuth) { |
| return of(azimuth.toRadians()); |
| } |
| |
| /** Create a new point instance from the given Euclidean 2D vector. The returned point |
| * will have an azimuth value equal to the angle between the positive x-axis and the |
| * given vector, measured in a counter-clockwise direction. |
| * @param vector 3D vector to create the point from |
| * @return a new point instance with an azimuth value equal to the angle between the given |
| * vector and the positive x-axis, measured in a counter-clockwise direction |
| */ |
| public static Point1S from(final Vector2D vector) { |
| final PolarCoordinates polar = PolarCoordinates.fromCartesian(vector); |
| final double az = polar.getAzimuth(); |
| |
| return new Point1S(az, az); |
| } |
| |
| /** Create a new point instance containing an azimuth value equal to that of the |
| * given set of polar coordinates. |
| * @param polar polar coordinates to convert to a point |
| * @return a new point instance containing an azimuth value equal to that of |
| * the given set of polar coordinates. |
| */ |
| public static Point1S from(final PolarCoordinates polar) { |
| final double az = polar.getAzimuth(); |
| |
| return new Point1S(az, az); |
| } |
| |
| /** Parse the given string and returns a new point instance. The expected string |
| * format is the same as that returned by {@link #toString()}. |
| * @param str the string to parse |
| * @return point instance represented by the string |
| * @throws IllegalArgumentException if the given string has an invalid format |
| */ |
| public static Point1S parse(final String str) { |
| return SimpleTupleFormat.getDefault().parse(str, az -> Point1S.of(az)); |
| } |
| |
| /** Compute the signed shortest distance (angular separation) between two points. The return |
| * value is in the range {@code [-pi, pi)} and is such that {@code p1.getAzimuth() + d} |
| * (where {@code d} is the signed distance) is an angle equivalent to {@code p2.getAzimuth()}. |
| * @param p1 first point |
| * @param p2 second point |
| * @return the signed angular separation between p1 and p2, in the range {@code [-pi, pi)}. |
| */ |
| public static double signedDistance(final Point1S p1, final Point1S p2) { |
| double dist = p2.normalizedAzimuth - p1.normalizedAzimuth; |
| if (dist < -Geometry.PI) { |
| dist += Geometry.TWO_PI; |
| } |
| if (dist >= Geometry.PI) { |
| dist -= Geometry.TWO_PI; |
| } |
| return dist; |
| } |
| |
| /** Compute the shortest distance (angular separation) between two points. The returned |
| * value is in the range {@code [0, pi]}. This method is equal to the absolute value of |
| * the {@link #signedDistance(Point1S, Point1S) signed distance}. |
| * @param p1 first point |
| * @param p2 second point |
| * @return the angular separation between p1 and p2, in the range {@code [0, pi]}. |
| * @see #signedDistance(Point1S, Point1S) |
| */ |
| public static double distance(final Point1S p1, final Point1S p2) { |
| return Math.abs(signedDistance(p1, p2)); |
| } |
| } |