All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.geometry.spherical.oned.Point1S Maven / Gradle / Ivy

The newest version!
/*
 * 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 org.apache.commons.geometry.core.Point;
import org.apache.commons.geometry.core.internal.DoubleFunction1N;
import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
import org.apache.commons.geometry.euclidean.twod.PolarCoordinates;
import org.apache.commons.geometry.euclidean.twod.Vector2D;
import org.apache.commons.numbers.angle.Angle;
import org.apache.commons.numbers.core.Precision;

/** 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.
 *
 * 

Instances of this class are guaranteed to be immutable.

*/ public final class Point1S implements Point { /** A point with coordinates set to {@code 0*pi}. */ public static final Point1S ZERO = Point1S.of(0.0); /** A point with coordinates set to {@code pi}. */ public static final Point1S PI = Point1S.of(Math.PI); /** A point with all coordinates set to NaN. */ public static final Point1S NaN = Point1S.of(Double.NaN); /** 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 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} * *

The returned value is the shortest angular distance between * the two points, in the range {@code [0, pi]}.

*/ @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 * value in radians. The returned point has an azimuth value in the range * {@code [base, base + 2pi)}. * @param base base azimuth 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 IllegalArgumentException if the azimuth value is NaN or infinite and * cannot be normalized */ public Point1S above(final double base) { if (isFinite()) { final double az = Angle.Rad.normalizer(base).applyAsDouble(azimuth); return new Point1S(az, normalizedAzimuth); } throw new IllegalArgumentException("Cannot normalize azimuth value: " + azimuth); } /** 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 IllegalArgumentException if the azimuth value is NaN or infinite and * cannot be normalized */ public Point1S above(final Point1S base) { return above(base.getAzimuth()); } /** 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 + Math.PI; if (az >= Angle.TWO_PI) { az -= Angle.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 Precision.DoubleEquivalence 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. * *

All NaN values have the same hash code.

* * @return a hash code value for this object */ @Override public int hashCode() { if (isNaN()) { return 542; } return (Double.hashCode(azimuth) >> 17) ^ Double.hashCode(normalizedAzimuth); } /** Test for the exact equality of two points on the 1-sphere. * *

If all coordinates of the given points are exactly the same, and none are * Double.NaN, the points are considered to be equal. Points with * azimuth values separated by multiples of {@code 2pi} are not considered * equal.

* *

NaN 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 Double.NaN, the point is equal to * {@link #NaN}.

* * @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 * @return point instance with the given azimuth angle * @see #getAzimuth() */ public static Point1S of(final Angle azimuth) { return of(azimuth.toRad().getAsDouble()); } /** 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, (DoubleFunction1N) Point1S::of); } /** 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 < -Math.PI) { dist += Angle.TWO_PI; } if (dist >= Math.PI) { dist -= Angle.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)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy