This is an automated email from the ASF dual-hosted git repository. erans pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-geometry.git
commit 259360397420c3bea7767d7947383fd0d31789f4 Author: Matt Juntunen <[email protected]> AuthorDate: Tue Jul 10 23:32:19 2018 -0400 GEOMETRY-7: creating static methods for azimuth and polar angle normalization; using polar and spherical naming conventions and conversion methods in S1Point and S2Point --- .../euclidean/threed/SphericalCoordinates.java | 67 ++++++----- .../geometry/euclidean/twod/PolarCoordinates.java | 50 +++++---- .../euclidean/threed/SphericalCoordinatesTest.java | 49 ++++++++- .../euclidean/twod/PolarCoordinatesTest.java | 24 ++++ .../commons/geometry/spherical/oned/ArcsSet.java | 4 +- .../geometry/spherical/oned/LimitAngle.java | 2 +- .../commons/geometry/spherical/oned/S1Point.java | 54 +++++---- .../commons/geometry/spherical/twod/Circle.java | 2 +- .../commons/geometry/spherical/twod/S2Point.java | 122 +++++++++------------ .../geometry/spherical/SphericalTestUtils.java | 2 +- .../geometry/spherical/oned/LimitAngleTest.java | 2 +- .../geometry/spherical/oned/S1PointTest.java | 4 +- .../geometry/spherical/twod/CircleTest.java | 10 +- .../geometry/spherical/twod/S2PointTest.java | 10 +- .../spherical/twod/SphericalPolygonsSetTest.java | 4 +- 15 files changed, 236 insertions(+), 170 deletions(-) diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinates.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinates.java index d3b3a25..37d7ea8 100644 --- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinates.java +++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinates.java @@ -22,11 +22,12 @@ import org.apache.commons.geometry.core.Geometry; import org.apache.commons.geometry.core.Spatial; import org.apache.commons.geometry.core.util.Coordinates; import org.apache.commons.geometry.core.util.SimpleCoordinateFormat; +import org.apache.commons.geometry.euclidean.twod.PolarCoordinates; import org.apache.commons.numbers.angle.PlaneAngleRadians; -/** Class representing <a href="https://en.wikipedia.org/wiki/Spherical_coordinate_system">spherical coordinates</a> +/** Class representing <a href="https://en.wikipedia.org/wiki/Spherical_coordinate_system">spherical coordinates</a> * in 3 dimensional Euclidean space. - * + * * <p>Spherical coordinates for a point are defined by three values: * <ol> * <li><em>Radius</em> - The distance from the point to a fixed referenced point.</li> @@ -36,14 +37,14 @@ import org.apache.commons.numbers.angle.PlaneAngleRadians; *direction must be orthogonal to the reference plane.</li> * </ol> * This class follows the convention of using the origin as the reference point; the positive x-axis as the - * reference direction for the azimuth angle, measured in the x-y plane with positive angles moving counter-clockwise + * reference direction for the azimuth angle, measured in the x-y plane with positive angles moving counter-clockwise * toward the positive y-axis; and the positive z-axis as the zenith direction. Spherical coordinates are * related to Cartesian coordinates as follows: * <pre> * x = r cos(θ) sin(Φ) * y = r sin(θ) sin(Φ) * z = r cos(Φ) - * + * * r = √(x<sup>2</sup>+y<sup>2</sup>+z<sup>2</sup>) * θ = atan2(y, x) * Φ = acos(z/r) @@ -51,19 +52,19 @@ import org.apache.commons.numbers.angle.PlaneAngleRadians; * where <em>r</em> is the radius, <em>θ</em> is the azimuth angle, and <em>Φ</em> is the polar angle * of the spherical coordinates. * </p> - * + * * <p>There are numerous, competing conventions for the symbols used to represent spherical coordinate values. For * example, the mathematical convention is to use <em>(r, θ, Φ)</em> to represent radius, azimuth angle, and * polar angle, whereas the physics convention flips the angle values and uses <em>(r, Φ, θ)</em>. As such, * this class avoids the use of these symbols altogether in favor of the less ambiguous formal names of the values, * e.g. {@code radius}, {@code azimuth}, and {@code polar}. * </p> - * - * <p>In order to ensure the uniqueness of coordinate sets, coordinate values - * are normalized so that {@code radius} is in the range {@code [0, +Infinity)}, + * + * <p>In order to ensure the uniqueness of coordinate sets, coordinate values + * are normalized so that {@code radius} is in the range {@code [0, +Infinity)}, * {@code azimuth} is in the range {@code (-pi, pi]}, and {@code polar} is in the * range {@code [0, pi]}.</p> - * + * * @see <a href="https://en.wikipedia.org/wiki/Spherical_coordinate_system">Spherical Coordinate System</a> */ public final class SphericalCoordinates implements Spatial, Serializable { @@ -103,25 +104,9 @@ public final class SphericalCoordinates implements Spatial, Serializable { polar += Geometry.PI; } - if (Double.isFinite(azimuth) && (azimuth <= Geometry.MINUS_PI || azimuth > Geometry.PI)) { - azimuth = PlaneAngleRadians.normalizeBetweenMinusPiAndPi(azimuth); - - // azimuth is now in the range [-pi, pi] but we want it to be in the range - // (-pi, pi] in order to have completely unique coordinates - if (azimuth <= -Geometry.PI) { - azimuth += Geometry.TWO_PI; - } - } - - // normalize the polar angle; this is the angle between the polar vector and the point ray - // so it is unsigned (unlike the azimuth) and should be in the range [0, pi] - if (Double.isFinite(polar)) { - polar = Math.abs(PlaneAngleRadians.normalizeBetweenMinusPiAndPi(polar)); - } - this.radius = radius; - this.azimuth = azimuth; - this.polar = polar; + this.azimuth = normalizeAzimuth(azimuth); + this.polar = normalizePolar(polar); } /** Return the radius value. The value is in the range {@code [0, +Infinity)}. @@ -288,6 +273,34 @@ public final class SphericalCoordinates implements Spatial, Serializable { return SimpleCoordinateFormat.getPointFormat().parse(input, FACTORY); } + /** Normalize an azimuth value to be within the range {@code (-pi, +pi]}. This + * is exactly equivalent to {@link PolarCoordinates#normalizeAzimuth(double)}. + * @param azimuth azimuth value in radians + * @return equivalent azimuth value in the range {@code (-pi, +pi]}. + * @see PolarCoordinates#normalizeAzimuth(double) + */ + public static double normalizeAzimuth(double azimuth) { + return PolarCoordinates.normalizeAzimuth(azimuth); + } + + /** Normalize a polar value to be within the range {@code [0, +pi]}. Since the + * polar angle is the angle between two vectors (the zenith direction and the + * point vector), the sign of the angle is not significant as in the azimuth angle. + * For example, a polar angle of {@code -pi/2} and one of {@code +pi/2} will both + * normalize to {@code pi/2}. + * @param polar polar value in radians + * @return equalivalent polar value in the range {@code [0, +pi]} + */ + public static double normalizePolar(double polar) { + // normalize the polar angle; this is the angle between the polar vector and the point ray + // so it is unsigned (unlike the azimuth) and should be in the range [0, pi] + if (Double.isFinite(polar)) { + polar = Math.abs(PlaneAngleRadians.normalizeBetweenMinusPiAndPi(polar)); + } + + return polar; + } + /** Convert the given set of spherical coordinates to Cartesian coordinates. * The Cartesian coordinates are computed and passed to the given * factory instance. The factory's return value is returned. diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinates.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinates.java index 9bc9c96..3ea6779 100644 --- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinates.java +++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinates.java @@ -24,30 +24,30 @@ import org.apache.commons.geometry.core.util.Coordinates; import org.apache.commons.geometry.core.util.SimpleCoordinateFormat; import org.apache.commons.numbers.angle.PlaneAngleRadians; -/** Class representing <a href="https://en.wikipedia.org/wiki/Polar_coordinate_system">polar coordinates</a> - * in 2 dimensional Euclidean space. - * +/** Class representing <a href="https://en.wikipedia.org/wiki/Polar_coordinate_system">polar coordinates</a> + * in 2 dimensional Euclidean space. + * * <p>Polar coordinates are defined by a distance from a reference point - * and an angle from a reference direction. The distance value is called + * and an angle from a reference direction. The distance value is called * the radial coordinate, or <em>radius</em>, and the angle is called the angular coordinate, * or <em>azimuth</em>. This class follows the standard * mathematical convention of using the positive x-axis as the reference - * direction and measuring positive angles counter-clockwise, toward the + * direction and measuring positive angles counter-clockwise, toward the * positive y-axis. The origin is used as the reference point. Polar coordinate * are related to Cartesian coordinates as follows: - * <pre> + * <pre> * x = r * cos(θ) * y = r * sin(θ) - * + * * r = √(x<sup>2</sup> + y<sup>2</sup>) * θ = atan2(y, x) * </pre> * where <em>r</em> is the radius and <em>θ</em> is the azimuth of the polar coordinates. * </p> - * <p>In order to ensure the uniqueness of coordinate sets, coordinate values - * are normalized so that {@code radius} is in the range {@code [0, +Infinity)} + * <p>In order to ensure the uniqueness of coordinate sets, coordinate values + * are normalized so that {@code radius} is in the range {@code [0, +Infinity)} * and {@code azimuth} is in the range {@code (-pi, pi]}.</p> - * + * * @see <a href="https://en.wikipedia.org/wiki/Polar_coordinate_system">Polar Coordinate System</a> */ public final class PolarCoordinates implements Spatial, Serializable { @@ -82,18 +82,8 @@ public final class PolarCoordinates implements Spatial, Serializable { azimuth += Geometry.PI; } - if (Double.isFinite(azimuth) && (azimuth <= Geometry.MINUS_PI || azimuth > Geometry.PI)) { - azimuth = PlaneAngleRadians.normalizeBetweenMinusPiAndPi(azimuth); - - // azimuth is now in the range [-pi, pi] but we want it to be in the range - // (-pi, pi] in order to have completely unique coordinates - if (azimuth <= -Geometry.PI) { - azimuth += Geometry.TWO_PI; - } - } - this.radius = radius; - this.azimuth = azimuth; + this.azimuth = normalizeAzimuth(azimuth);; } /** Return the radius value. The value will be greater than or equal to 0. @@ -246,6 +236,24 @@ public final class PolarCoordinates implements Spatial, Serializable { return SimpleCoordinateFormat.getPointFormat().parse(input, FACTORY); } + /** Normalize an azimuth value to be within the range {@code (-pi, +pi]}. + * @param azimuth azimuth value in radians + * @return equivalent azimuth value in the range {@code (-pi, +pi]}. + */ + public static double normalizeAzimuth(double azimuth) { + if (Double.isFinite(azimuth) && (azimuth <= Geometry.MINUS_PI || azimuth > Geometry.PI)) { + azimuth = PlaneAngleRadians.normalizeBetweenMinusPiAndPi(azimuth); + + // azimuth is now in the range [-pi, pi] but we want it to be in the range + // (-pi, pi] in order to have completely unique coordinates + if (azimuth <= -Geometry.PI) { + azimuth += Geometry.TWO_PI; + } + } + + return azimuth; + } + /** Convert the given set of polar coordinates to Cartesian coordinates. * The Cartesian coordinates are computed and passed to the given * factory instance. The factory's return value is returned. diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinatesTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinatesTest.java index 1acfde7..241093a 100644 --- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinatesTest.java +++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinatesTest.java @@ -23,7 +23,6 @@ import org.apache.commons.geometry.core.util.Coordinates; import org.junit.Assert; import org.junit.Test; - public class SphericalCoordinatesTest { private static final double EPS = 1e-10; @@ -355,6 +354,54 @@ public class SphericalCoordinatesTest { } @Test + public void testNormalizeAzimuth() { + // act/assert + Assert.assertEquals(SphericalCoordinates.normalizeAzimuth(0), 0.0, EPS); + + Assert.assertEquals(SphericalCoordinates.normalizeAzimuth(Geometry.HALF_PI), Geometry.HALF_PI, EPS); + Assert.assertEquals(SphericalCoordinates.normalizeAzimuth(Geometry.PI), Geometry.PI, EPS); + Assert.assertEquals(SphericalCoordinates.normalizeAzimuth(Geometry.PI + Geometry.HALF_PI), Geometry.MINUS_HALF_PI, EPS); + Assert.assertEquals(SphericalCoordinates.normalizeAzimuth(Geometry.TWO_PI), 0.0, EPS); + + Assert.assertEquals(SphericalCoordinates.normalizeAzimuth(Geometry.MINUS_HALF_PI), Geometry.MINUS_HALF_PI, EPS); + Assert.assertEquals(SphericalCoordinates.normalizeAzimuth(-Geometry.PI), Geometry.PI, EPS); + Assert.assertEquals(SphericalCoordinates.normalizeAzimuth(-Geometry.PI - Geometry.HALF_PI), Geometry.HALF_PI, EPS); + Assert.assertEquals(SphericalCoordinates.normalizeAzimuth(-Geometry.TWO_PI), 0.0, EPS); + } + + @Test + public void testNormalizeAzimuth_NaNAndInfinite() { + // act/assert + Assert.assertEquals(SphericalCoordinates.normalizeAzimuth(Double.NaN), Double.NaN, EPS); + Assert.assertEquals(SphericalCoordinates.normalizeAzimuth(Double.NEGATIVE_INFINITY), Double.NEGATIVE_INFINITY, EPS); + Assert.assertEquals(SphericalCoordinates.normalizeAzimuth(Double.POSITIVE_INFINITY), Double.POSITIVE_INFINITY, EPS); + } + + @Test + public void testNormalizePolar() { + // act/assert + Assert.assertEquals(SphericalCoordinates.normalizePolar(0), 0.0, EPS); + + Assert.assertEquals(SphericalCoordinates.normalizePolar(Geometry.HALF_PI), Geometry.HALF_PI, EPS); + Assert.assertEquals(SphericalCoordinates.normalizePolar(Geometry.PI), Geometry.PI, EPS); + Assert.assertEquals(SphericalCoordinates.normalizePolar(Geometry.PI + Geometry.HALF_PI), Geometry.HALF_PI, EPS); + Assert.assertEquals(SphericalCoordinates.normalizePolar(Geometry.TWO_PI), 0.0, EPS); + + Assert.assertEquals(SphericalCoordinates.normalizePolar(Geometry.MINUS_HALF_PI), Geometry.HALF_PI, EPS); + Assert.assertEquals(SphericalCoordinates.normalizePolar(-Geometry.PI), Geometry.PI, EPS); + Assert.assertEquals(SphericalCoordinates.normalizePolar(-Geometry.PI - Geometry.HALF_PI), Geometry.HALF_PI, EPS); + Assert.assertEquals(SphericalCoordinates.normalizePolar(-Geometry.TWO_PI), 0.0, EPS); + } + + @Test + public void testNormalizePolar_NaNAndInfinite() { + // act/assert + Assert.assertEquals(SphericalCoordinates.normalizePolar(Double.NaN), Double.NaN, EPS); + Assert.assertEquals(SphericalCoordinates.normalizePolar(Double.NEGATIVE_INFINITY), Double.NEGATIVE_INFINITY, EPS); + Assert.assertEquals(SphericalCoordinates.normalizePolar(Double.POSITIVE_INFINITY), Double.POSITIVE_INFINITY, EPS); + } + + @Test public void testGetFactory() { // act Coordinates.Factory3D<SphericalCoordinates> factory = SphericalCoordinates.getFactory(); diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinatesTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinatesTest.java index 2b28c19..d8636f0 100644 --- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinatesTest.java +++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinatesTest.java @@ -344,6 +344,30 @@ public class PolarCoordinatesTest { } @Test + public void testNormalizeAzimuth() { + // act/assert + Assert.assertEquals(PolarCoordinates.normalizeAzimuth(0), 0.0, EPS); + + Assert.assertEquals(PolarCoordinates.normalizeAzimuth(Geometry.HALF_PI), Geometry.HALF_PI, EPS); + Assert.assertEquals(PolarCoordinates.normalizeAzimuth(Geometry.PI), Geometry.PI, EPS); + Assert.assertEquals(PolarCoordinates.normalizeAzimuth(Geometry.PI + Geometry.HALF_PI), Geometry.MINUS_HALF_PI, EPS); + Assert.assertEquals(PolarCoordinates.normalizeAzimuth(Geometry.TWO_PI), 0.0, EPS); + + Assert.assertEquals(PolarCoordinates.normalizeAzimuth(Geometry.MINUS_HALF_PI), Geometry.MINUS_HALF_PI, EPS); + Assert.assertEquals(PolarCoordinates.normalizeAzimuth(-Geometry.PI), Geometry.PI, EPS); + Assert.assertEquals(PolarCoordinates.normalizeAzimuth(-Geometry.PI - Geometry.HALF_PI), Geometry.HALF_PI, EPS); + Assert.assertEquals(PolarCoordinates.normalizeAzimuth(-Geometry.TWO_PI), 0.0, EPS); + } + + @Test + public void testNormalizeAzimuth_NaNAndInfinite() { + // act/assert + Assert.assertEquals(PolarCoordinates.normalizeAzimuth(Double.NaN), Double.NaN, EPS); + Assert.assertEquals(PolarCoordinates.normalizeAzimuth(Double.NEGATIVE_INFINITY), Double.NEGATIVE_INFINITY, EPS); + Assert.assertEquals(PolarCoordinates.normalizeAzimuth(Double.POSITIVE_INFINITY), Double.POSITIVE_INFINITY, EPS); + } + + @Test public void testGetFactory() { // act Coordinates.Factory2D<PolarCoordinates> factory = PolarCoordinates.getFactory(); diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java index 813dae9..311950f 100644 --- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java +++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java @@ -435,7 +435,7 @@ public class ArcsSet extends AbstractRegion<S1Point, S1Point> implements Iterabl * @return limit angle */ private double getAngle(final BSPTree<S1Point> node) { - return ((LimitAngle) node.getCut().getHyperplane()).getLocation().getAlpha(); + return ((LimitAngle) node.getCut().getHyperplane()).getLocation().getAzimuth(); } /** {@inheritDoc} */ @@ -475,7 +475,7 @@ public class ArcsSet extends AbstractRegion<S1Point, S1Point> implements Iterabl public BoundaryProjection<S1Point> projectToBoundary(final S1Point point) { // get position of test point - final double alpha = point.getAlpha(); + final double alpha = point.getAzimuth(); boolean wrapFirst = false; double first = Double.NaN; diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/LimitAngle.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/LimitAngle.java index bc80a21..605a649 100644 --- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/LimitAngle.java +++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/LimitAngle.java @@ -58,7 +58,7 @@ public class LimitAngle implements Hyperplane<S1Point> { /** {@inheritDoc} */ @Override public double getOffset(final S1Point point) { - final double delta = point.getAlpha() - location.getAlpha(); + final double delta = point.getAzimuth() - location.getAzimuth(); return direct ? delta : -delta; } diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java index fdabb13..4d3f87e 100644 --- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java +++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java @@ -21,6 +21,7 @@ import java.io.Serializable; import org.apache.commons.geometry.core.Point; import org.apache.commons.geometry.core.util.Coordinates; import org.apache.commons.geometry.core.util.SimpleCoordinateFormat; +import org.apache.commons.geometry.euclidean.twod.PolarCoordinates; import org.apache.commons.geometry.euclidean.twod.Vector2D; import org.apache.commons.numbers.angle.PlaneAngleRadians; @@ -29,13 +30,13 @@ import org.apache.commons.numbers.angle.PlaneAngleRadians; */ public final class S1Point implements Point<S1Point>, Serializable { - // CHECKSTYLE: stop ConstantName - /** A vector with all coordinates set to NaN. */ - public static final S1Point NaN = new S1Point(Double.NaN, Vector2D.NaN); + // CHECKSTYLE: stop ConstantName + /** A point with all coordinates set to NaN. */ + public static final S1Point NaN = new S1Point(Double.NaN); // CHECKSTYLE: resume ConstantName /** Serializable UID. */ - private static final long serialVersionUID = 20131218L; + private static final long serialVersionUID = 20180710L; /** Factory for delegating instance creation. */ private static Coordinates.Factory1D<S1Point> FACTORY = new Coordinates.Factory1D<S1Point>() { @@ -43,31 +44,31 @@ public final class S1Point implements Point<S1Point>, Serializable { /** {@inheritDoc} */ @Override public S1Point create(double a) { - return S1Point.of(a); + return new S1Point(a); } }; - /** Azimuthal angle in radians \( \alpha \). */ - private final double alpha; + /** Azimuthal angle in radians. */ + private final double azimuth; /** Corresponding 2D normalized vector. */ private final Vector2D vector; /** Build a point from its internal components. - * @param alpha azimuthal angle \( \alpha \) + * @param azimuth azimuthal angle * @param vector corresponding vector */ - private S1Point(final double alpha, final Vector2D vector) { - this.alpha = alpha; - this.vector = vector; + private S1Point(final double azimuth) { + this.azimuth = PlaneAngleRadians.normalizeBetweenZeroAndTwoPi(azimuth); + this.vector = Double.isFinite(azimuth) ? Vector2D.ofPolar(1.0, azimuth) : Vector2D.NaN; } - /** Get the azimuthal angle in radians \( \alpha \). - * @return azimuthal angle \( \alpha \) + /** Get the azimuthal angle in radians. + * @return azimuthal angle * @see S1Point#of(double) */ - public double getAlpha() { - return alpha; + public double getAzimuth() { + return azimuth; } /** Get the corresponding normalized vector in the 2D Euclidean space. @@ -86,13 +87,13 @@ public final class S1Point implements Point<S1Point>, Serializable { /** {@inheritDoc} */ @Override public boolean isNaN() { - return Double.isNaN(alpha); + return Double.isNaN(azimuth); } /** {@inheritDoc} */ @Override public boolean isInfinite() { - return !isNaN() && Double.isInfinite(alpha); + return !isNaN() && Double.isInfinite(azimuth); } /** {@inheritDoc} */ @@ -131,7 +132,6 @@ public final class S1Point implements Point<S1Point>, Serializable { */ @Override public boolean equals(Object other) { - if (this == other) { return true; } @@ -142,11 +142,10 @@ public final class S1Point implements Point<S1Point>, Serializable { return this.isNaN(); } - return alpha == rhs.alpha; + return azimuth == rhs.azimuth; } return false; - } /** @@ -161,25 +160,22 @@ public final class S1Point implements Point<S1Point>, Serializable { if (isNaN()) { return 542; } - return 1759 * Double.hashCode(alpha); + return 1759 * Double.hashCode(azimuth); } /** {@inheritDoc} */ @Override public String toString() { - return SimpleCoordinateFormat.getPointFormat().format(getAlpha()); + return SimpleCoordinateFormat.getPointFormat().format(getAzimuth()); } /** Creates a new point instance from the given azimuthal coordinate value. - * @param alpha azimuthal angle in radians \( \alpha \) + * @param azimuth azimuthal angle in radians * @return point instance with the given azimuth coordinate value - * @see #getAlpha() + * @see #getAzimuth() */ - public static S1Point of(double alpha) { - double normalizedAlpha = PlaneAngleRadians.normalizeBetweenZeroAndTwoPi(alpha); - Vector2D vector = Vector2D.of(Math.cos(normalizedAlpha), Math.sin(normalizedAlpha)); - - return new S1Point(normalizedAlpha, vector); + public static S1Point of(double azimuth) { + return new S1Point(azimuth); } /** Parses the given string and returns a new point instance. The expected string diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java index 31cc5c9..e0aaba0 100644 --- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java +++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java @@ -168,7 +168,7 @@ public class Circle implements Hyperplane<S2Point>, Embedding<S2Point, S1Point> */ @Override public S2Point toSpace(final S1Point point) { - return S2Point.of(getPointAt(point.getAlpha())); + return S2Point.of(getPointAt(point.getAzimuth())); } /** Get a circle point from its phase around the circle. diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java index 93cec93..1b67205 100644 --- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java +++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java @@ -21,45 +21,39 @@ import java.io.Serializable; import org.apache.commons.geometry.core.Point; import org.apache.commons.geometry.core.util.Coordinates; import org.apache.commons.geometry.core.util.SimpleCoordinateFormat; +import org.apache.commons.geometry.euclidean.threed.SphericalCoordinates; import org.apache.commons.geometry.euclidean.threed.Vector3D; /** This class represents a point on the 2-sphere. - * <p> - * We use the mathematical convention to use the azimuthal angle \( \theta \) - * in the x-y plane as the first coordinate, and the polar angle \( \varphi \) - * as the second coordinate (see <a - * href="http://mathworld.wolfram.com/SphericalCoordinates.html">Spherical - * Coordinates</a> in MathWorld). - * </p> * <p>Instances of this class are guaranteed to be immutable.</p> */ public final class S2Point implements Point<S2Point>, Serializable { - /** +I (coordinates: \( \theta = 0, \varphi = \pi/2 \)). */ + /** +I (coordinates: ( azimuth = 0, polar = pi/2 )). */ public static final S2Point PLUS_I = new S2Point(0, 0.5 * Math.PI, Vector3D.PLUS_X); - /** +J (coordinates: \( \theta = \pi/2, \varphi = \pi/2 \))). */ + /** +J (coordinates: ( azimuth = pi/2, polar = pi/2 ))). */ public static final S2Point PLUS_J = new S2Point(0.5 * Math.PI, 0.5 * Math.PI, Vector3D.PLUS_Y); - /** +K (coordinates: \( \theta = any angle, \varphi = 0 \)). */ + /** +K (coordinates: ( azimuth = any angle, polar = 0 )). */ public static final S2Point PLUS_K = new S2Point(0, 0, Vector3D.PLUS_Z); - /** -I (coordinates: \( \theta = \pi, \varphi = \pi/2 \)). */ + /** -I (coordinates: ( azimuth = pi, polar = pi/2 )). */ public static final S2Point MINUS_I = new S2Point(Math.PI, 0.5 * Math.PI, Vector3D.MINUS_X); - /** -J (coordinates: \( \theta = 3\pi/2, \varphi = \pi/2 \)). */ + /** -J (coordinates: ( azimuth = 3pi/2, polar = pi/2 )). */ public static final S2Point MINUS_J = new S2Point(1.5 * Math.PI, 0.5 * Math.PI, Vector3D.MINUS_Y); - /** -K (coordinates: \( \theta = any angle, \varphi = \pi \)). */ + /** -K (coordinates: ( azimuth = any angle, polar = pi )). */ public static final S2Point MINUS_K = new S2Point(0, Math.PI, Vector3D.MINUS_Z); // CHECKSTYLE: stop ConstantName - /** A vector with all coordinates set to NaN. */ + /** A point with all coordinates set to NaN. */ public static final S2Point NaN = new S2Point(Double.NaN, Double.NaN, Vector3D.NaN); // CHECKSTYLE: resume ConstantName /** Serializable UID. */ - private static final long serialVersionUID = 20131218L; + private static final long serialVersionUID = 20180710L; /** Factory for delegating instance creation. */ private static Coordinates.Factory2D<S2Point> FACTORY = new Coordinates.Factory2D<S2Point>() { @@ -67,44 +61,46 @@ public final class S2Point implements Point<S2Point>, Serializable { /** {@inheritDoc} */ @Override public S2Point create(double a1, double a2) { + // use the factory method for full input validation return S2Point.of(a1, a2); } }; - /** Azimuthal angle \( \theta \) in the x-y plane. */ - private final double theta; + /** Azimuthal angle in the x-y plane. */ + private final double azimuth; - /** Polar angle \( \varphi \). */ - private final double phi; + /** Polar angle. */ + private final double polar; /** Corresponding 3D normalized vector. */ private final Vector3D vector; /** Build a point from its internal components. - * @param theta azimuthal angle \( \theta \) in the x-y plane - * @param phi polar angle \( \varphi \) - * @param vector corresponding vector + * @param azimuth azimuthal angle in the x-y plane + * @param polar polar angle + * @param vector corresponding vector; if null, the vector is computed + * @throws IllegalArgumentException */ - private S2Point(final double theta, final double phi, final Vector3D vector) { - this.theta = theta; - this.phi = phi; - this.vector = vector; + private S2Point(final double azimuth, final double polar, final Vector3D vector) { + this.azimuth = azimuth; + this.polar = polar; + this.vector = (vector != null) ? vector : Vector3D.ofSpherical(1.0, azimuth, polar); } - /** Get the azimuthal angle \( \theta \) in the x-y plane. - * @return azimuthal angle \( \theta \) in the x-y plane + /** Get the azimuthal angle in the x-y plane in radians. + * @return azimuthal angle in the x-y plane * @see S2Point#of(double, double) */ - public double getTheta() { - return theta; + public double getAzimuth() { + return azimuth; } - /** Get the polar angle \( \varphi \). - * @return polar angle \( \varphi \) + /** Get the polar angle in radians. + * @return polar angle * @see S2Point#of(double, double) */ - public double getPhi() { - return phi; + public double getPolar() { + return polar; } /** Get the corresponding normalized vector in the 3D Euclidean space. @@ -123,20 +119,20 @@ public final class S2Point implements Point<S2Point>, Serializable { /** {@inheritDoc} */ @Override public boolean isNaN() { - return Double.isNaN(theta) || Double.isNaN(phi); + return Double.isNaN(azimuth) || Double.isNaN(polar); } /** {@inheritDoc} */ @Override public boolean isInfinite() { - return !isNaN() && (Double.isInfinite(theta) || Double.isInfinite(phi)); + return !isNaN() && (Double.isInfinite(azimuth) || Double.isInfinite(polar)); } /** Get the opposite of the instance. * @return a new vector which is opposite to the instance */ public S2Point negate() { - return new S2Point(-theta, Math.PI - phi, vector.negate()); + return new S2Point(-azimuth, Math.PI - polar, vector.negate()); } /** {@inheritDoc} */ @@ -175,7 +171,6 @@ public final class S2Point implements Point<S2Point>, Serializable { */ @Override public boolean equals(Object other) { - if (this == other) { return true; } @@ -186,7 +181,7 @@ public final class S2Point implements Point<S2Point>, Serializable { return this.isNaN(); } - return (theta == rhs.theta) && (phi == rhs.phi); + return (azimuth == rhs.azimuth) && (polar == rhs.polar); } return false; } @@ -203,57 +198,40 @@ public final class S2Point implements Point<S2Point>, Serializable { if (isNaN()) { return 542; } - return 134 * (37 * Double.hashCode(theta) + Double.hashCode(phi)); + return 134 * (37 * Double.hashCode(azimuth) + Double.hashCode(polar)); } /** {@inheritDoc} */ @Override public String toString() { - return SimpleCoordinateFormat.getPointFormat().format(getTheta(), getPhi()); + return SimpleCoordinateFormat.getPointFormat().format(getAzimuth(), getPolar()); } /** Build a vector from its spherical coordinates - * @param theta azimuthal angle \( \theta \) in the x-y plane - * @param phi polar angle \( \varphi \) + * @param azimuth azimuthal angle in the x-y plane + * @param polar polar angle * @return point instance with the given coordinates - * @see #getTheta() - * @see #getPhi() - * @exception IllegalArgumentException if \( \varphi \) is not in the [\( 0; \pi \)] range + * @see #getAzimuth() + * @see #getPolar() + * @exception IllegalArgumentException if polar is not in the {@code [0, +pi]} range */ - public static S2Point of(final double theta, final double phi) { - return new S2Point(theta, phi, vector(theta, phi)); + public static S2Point of(final double azimuth, final double polar) throws IllegalArgumentException { + if (polar < 0 || polar > Math.PI) { + throw new IllegalArgumentException(polar + " is out of [" + 0 + ", " + Math.PI + "] range"); + } + + return new S2Point(azimuth, polar, null); } /** Build a point from its underlying 3D vector * @param vector 3D vector * @return point instance with the coordinates determined by the given 3D vector - * @exception IllegalArgumentException if vector norm is zero + * @exception IllegalStateException if vector norm is zero */ public static S2Point of(final Vector3D vector) { - return new S2Point(Math.atan2(vector.getY(), vector.getX()), - Vector3D.PLUS_Z.angle(vector), - vector.normalize()); - } - - /** Build the normalized vector corresponding to spherical coordinates. - * @param theta azimuthal angle \( \theta \) in the x-y plane - * @param phi polar angle \( \varphi \) - * @return normalized vector - * @exception IllegalArgumentException if \( \varphi \) is not in the [\( 0; \pi \)] range - */ - private static Vector3D vector(final double theta, final double phi) - throws IllegalArgumentException { - - if (phi < 0 || phi > Math.PI) { - throw new IllegalArgumentException(phi + " is out of [" + 0 + ", " + Math.PI + "] range"); - } - - final double cosTheta = Math.cos(theta); - final double sinTheta = Math.sin(theta); - final double cosPhi = Math.cos(phi); - final double sinPhi = Math.sin(phi); + SphericalCoordinates coords = vector.toSpherical(); - return Vector3D.of(cosTheta * sinPhi, sinTheta * sinPhi, cosPhi); + return new S2Point(coords.getAzimuth(), coords.getPolar(), vector.normalize()); } /** Parses the given string and returns a new point instance. The expected string diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/SphericalTestUtils.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/SphericalTestUtils.java index a43bc3d..6baa3b1 100644 --- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/SphericalTestUtils.java +++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/SphericalTestUtils.java @@ -46,7 +46,7 @@ public class SphericalTestUtils { protected void formatHyperplane(final Hyperplane<S1Point> hyperplane) { final LimitAngle h = (LimitAngle) hyperplane; getFormatter().format("%22.15e %b %22.15e", - h.getLocation().getAlpha(), h.isDirect(), h.getTolerance()); + h.getLocation().getAzimuth(), h.isDirect(), h.getTolerance()); } }; diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/LimitAngleTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/LimitAngleTest.java index f9a9b3b..629f2b6 100644 --- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/LimitAngleTest.java +++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/LimitAngleTest.java @@ -26,7 +26,7 @@ public class LimitAngleTest { public void testReversedLimit() { for (int k = -2; k < 3; ++k) { LimitAngle l = new LimitAngle(S1Point.of(1.0 + k * Geometry.TWO_PI), false, 1.0e-10); - Assert.assertEquals(l.getLocation().getAlpha(), l.getReverse().getLocation().getAlpha(), 1.0e-10); + Assert.assertEquals(l.getLocation().getAzimuth(), l.getReverse().getLocation().getAzimuth(), 1.0e-10); Assert.assertEquals(l.getTolerance(), l.getReverse().getTolerance(), 1.0e-10); Assert.assertTrue(l.sameOrientationAs(l)); Assert.assertFalse(l.sameOrientationAs(l.getReverse())); diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/S1PointTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/S1PointTest.java index eac954d..f1c0f40 100644 --- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/S1PointTest.java +++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/S1PointTest.java @@ -56,7 +56,7 @@ public class S1PointTest { @Test public void testDistance() { S1Point a = S1Point.of(1.0); - S1Point b = S1Point.of(a.getAlpha() + 0.5 * Math.PI); + S1Point b = S1Point.of(a.getAzimuth() + 0.5 * Math.PI); Assert.assertEquals(0.5 * Math.PI, a.distance(b), 1.0e-10); } @@ -92,7 +92,7 @@ public class S1PointTest { } private void checkPoint(S1Point p, double alpha) { - Assert.assertEquals(alpha, p.getAlpha(), EPS); + Assert.assertEquals(alpha, p.getAzimuth(), EPS); } } diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java index 7b9ad53..8e96248 100644 --- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java +++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java @@ -99,10 +99,10 @@ public class CircleTest { @Test public void testSubSpace() { Circle circle = new Circle(S2Point.of(1.2, 2.5), S2Point.of(-4.3, 0), 1.0e-10); - Assert.assertEquals(0.0, circle.toSubSpace(S2Point.of(circle.getXAxis())).getAlpha(), 1.0e-10); - Assert.assertEquals(0.5 * Math.PI, circle.toSubSpace(S2Point.of(circle.getYAxis())).getAlpha(), 1.0e-10); + Assert.assertEquals(0.0, circle.toSubSpace(S2Point.of(circle.getXAxis())).getAzimuth(), 1.0e-10); + Assert.assertEquals(0.5 * Math.PI, circle.toSubSpace(S2Point.of(circle.getYAxis())).getAzimuth(), 1.0e-10); Vector3D p = Vector3D.of(1, 2, -4); - Assert.assertEquals(circle.getPhase(p), circle.toSubSpace(S2Point.of(p)).getAlpha(), 1.0e-10); + Assert.assertEquals(circle.getPhase(p), circle.toSubSpace(S2Point.of(p)).getAzimuth(), 1.0e-10); } @Test @@ -177,9 +177,9 @@ public class CircleTest { SubLimitAngle sub = new LimitAngle(S1Point.of(Geometry.TWO_PI * random.nextDouble()), random.nextBoolean(), 1.0e-10).wholeHyperplane(); - Vector3D psub = c.getPointAt(((LimitAngle) sub.getHyperplane()).getLocation().getAlpha()); + Vector3D psub = c.getPointAt(((LimitAngle) sub.getHyperplane()).getLocation().getAzimuth()); SubLimitAngle tsub = (SubLimitAngle) t.apply(sub, c, tc); - Vector3D ptsub = tc.getPointAt(((LimitAngle) tsub.getHyperplane()).getLocation().getAlpha()); + Vector3D ptsub = tc.getPointAt(((LimitAngle) tsub.getHyperplane()).getLocation().getAzimuth()); Assert.assertEquals(0.0, r.applyTo(psub).distance(ptsub), 1.0e-10); } diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/S2PointTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/S2PointTest.java index 1661921..78f9097 100644 --- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/S2PointTest.java +++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/S2PointTest.java @@ -30,8 +30,8 @@ public class S2PointTest { public void testS2Point() { for (int k = -2; k < 3; ++k) { S2Point p = S2Point.of(1.0 + k * Geometry.TWO_PI, 1.4); - Assert.assertEquals(1.0 + k * Geometry.TWO_PI, p.getTheta(), EPS); - Assert.assertEquals(1.4, p.getPhi(), EPS); + Assert.assertEquals(1.0 + k * Geometry.TWO_PI, p.getAzimuth(), EPS); + Assert.assertEquals(1.4, p.getPolar(), EPS); Assert.assertEquals(Math.cos(1.0) * Math.sin(1.4), p.getVector().getX(), EPS); Assert.assertEquals(Math.sin(1.0) * Math.sin(1.4), p.getVector().getY(), EPS); Assert.assertEquals(Math.cos(1.4), p.getVector().getZ(), EPS); @@ -70,7 +70,7 @@ public class S2PointTest { @Test public void testDistance() { S2Point a = S2Point.of(1.0, 0.5 * Math.PI); - S2Point b = S2Point.of(a.getTheta() + 0.5 * Math.PI, a.getPhi()); + S2Point b = S2Point.of(a.getAzimuth() + 0.5 * Math.PI, a.getPolar()); Assert.assertEquals(0.5 * Math.PI, a.distance(b), 1.0e-10); Assert.assertEquals(Math.PI, a.distance(a.negate()), 1.0e-10); Assert.assertEquals(0.5 * Math.PI, S2Point.MINUS_I.distance(S2Point.MINUS_K), 1.0e-10); @@ -108,7 +108,7 @@ public class S2PointTest { } private void checkPoint(S2Point p, double theta, double phi) { - Assert.assertEquals(theta, p.getTheta(), EPS); - Assert.assertEquals(phi, p.getPhi(), EPS); + Assert.assertEquals(theta, p.getAzimuth(), EPS); + Assert.assertEquals(phi, p.getPolar(), EPS); } } diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java index 0f056e8..ce6b231 100644 --- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java +++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java @@ -375,7 +375,7 @@ public class SphericalPolygonsSetTest { SphericalPolygonsSet hexaWithHole = (SphericalPolygonsSet) new RegionFactory<S2Point>().difference(hexa, hole); - for (double phi = center.getPhi() - alpha + 0.1; phi < center.getPhi() + alpha - 0.1; phi += 0.07) { + for (double phi = center.getPolar() - alpha + 0.1; phi < center.getPolar() + alpha - 0.1; phi += 0.07) { Location l = hexaWithHole.checkPoint(S2Point.of(Math.PI / 4, phi)); if (phi < Math.PI / 6 || phi > Math.PI / 3) { Assert.assertEquals(Location.INSIDE, l); @@ -428,7 +428,7 @@ public class SphericalPolygonsSetTest { concentric.getSize(), 1.0e-10); // we expect lots of sign changes as we traverse all concentric rings - double phi = S2Point.of(center).getPhi(); + double phi = S2Point.of(center).getPolar(); Assert.assertEquals(+0.207, concentric.projectToBoundary(S2Point.of(-0.60, phi)).getOffset(), 0.01); Assert.assertEquals(-0.048, concentric.projectToBoundary(S2Point.of(-0.21, phi)).getOffset(), 0.01); Assert.assertEquals(+0.027, concentric.projectToBoundary(S2Point.of(-0.10, phi)).getOffset(), 0.01);
