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 9b3f388b8f4f742bde87161438954359dfefb43b Merge: c892bfe 8273d59 Author: Matt Juntunen <[email protected]> AuthorDate: Fri Jul 20 22:51:27 2018 -0400 GEOMETRY-7: merging polar and spherical code with latest from master .../DoubleFunction1N.java} | 20 +- .../DoubleFunction2N.java} | 21 +- .../DoubleFunction3N.java} | 22 +- .../geometry/core/internal/SimpleTupleFormat.java | 426 +++++++++++++++++++++ .../core/{util => internal}/package-info.java | 5 +- .../core/util/AbstractCoordinateParser.java | 257 ------------- .../commons/geometry/core/util/Coordinates.java | 68 ---- .../geometry/core/util/SimpleCoordinateFormat.java | 202 ---------- .../SimpleTupleFormatTest.java} | 85 ++-- .../geometry/euclidean/oned/Cartesian1D.java | 9 +- .../commons/geometry/euclidean/oned/Point1D.java | 27 +- .../commons/geometry/euclidean/oned/Vector1D.java | 27 +- .../geometry/euclidean/threed/Cartesian3D.java | 9 +- .../commons/geometry/euclidean/threed/Point3D.java | 29 +- .../euclidean/threed/SphericalCoordinates.java | 46 +-- .../geometry/euclidean/threed/Vector3D.java | 29 +- .../geometry/euclidean/twod/Cartesian2D.java | 9 +- .../commons/geometry/euclidean/twod/Point2D.java | 31 +- .../geometry/euclidean/twod/PolarCoordinates.java | 44 +-- .../commons/geometry/euclidean/twod/Vector2D.java | 31 +- .../geometry/euclidean/oned/Cartesian1DTest.java | 16 + .../geometry/euclidean/oned/Point1DTest.java | 11 - .../geometry/euclidean/oned/Vector1DTest.java | 23 +- .../geometry/euclidean/threed/Cartesian3DTest.java | 16 + .../geometry/euclidean/threed/Point3DTest.java | 13 +- .../euclidean/threed/SphericalCoordinatesTest.java | 35 +- .../geometry/euclidean/threed/Vector3DTest.java | 23 +- .../geometry/euclidean/twod/Cartesian2DTest.java | 17 +- .../geometry/euclidean/twod/Point2DTest.java | 11 - .../euclidean/twod/PolarCoordinatesTest.java | 36 +- .../geometry/euclidean/twod/Vector2DTest.java | 21 +- .../commons/geometry/spherical/oned/S1Point.java | 21 +- .../commons/geometry/spherical/twod/S2Point.java | 22 +- .../geometry/spherical/oned/S1PointTest.java | 12 - .../geometry/spherical/twod/S2PointTest.java | 11 - 35 files changed, 694 insertions(+), 991 deletions(-) diff --cc commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Point3D.java index 6e4da3e,7c2b3f7..5ddb722 --- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Point3D.java +++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Point3D.java @@@ -44,10 -44,10 +44,10 @@@ public final class Point3D extends Cart new Point3D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); /** Serializable version identifier. */ - private static final long serialVersionUID = 1313493323784566947L; + private static final long serialVersionUID = 20180710L; -- /** Factory for delegating instance creation. */ - private static Coordinates.Factory3D<Point3D> FACTORY = new Coordinates.Factory3D<Point3D>() { - private static DoubleFunction3N<Point3D> FACTORY = new DoubleFunction3N<Point3D>() { ++ /** Package private factory for delegating instance creation. */ ++ static DoubleFunction3N<Point3D> FACTORY = new DoubleFunction3N<Point3D>() { /** {@inheritDoc} */ @Override diff --cc commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinates.java index 5be957d,0000000..02ace69 mode 100644,000000..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 @@@ -1,331 -1,0 +1,313 @@@ +/* + * 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.euclidean.threed; + +import java.io.Serializable; + +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.core.internal.DoubleFunction3N; ++import org.apache.commons.geometry.core.internal.SimpleTupleFormat; +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> + * 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> + * <li><em>Azimuth angle</em> - The angle measured from a fixed reference direction in a plane to + * the orthogonal projection of the point on that plane.</li> + * <li><em>Polar angle</em> - The angle measured from a fixed zenith direction to the point. The zenith + *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 + * 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) + * </pre> + * 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)}, + * {@code azimuth} is in the range {@code [0, 2pi)}, 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 { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20180623L; + + /** Factory object for delegating instance creation. */ - private static final Coordinates.Factory3D<SphericalCoordinates> FACTORY = new Coordinates.Factory3D<SphericalCoordinates>() { ++ private static final DoubleFunction3N<SphericalCoordinates> FACTORY = new DoubleFunction3N<SphericalCoordinates>() { + + /** {@inheritDoc} */ + @Override - public SphericalCoordinates create(double a1, double a2, double a3) { - return new SphericalCoordinates(a1, a2, a3); ++ public SphericalCoordinates apply(double n1, double n2, double n3) { ++ return new SphericalCoordinates(n1, n2, n3); + } + }; + + /** Radius value. */ + private final double radius; + + /** Azimuth angle in radians. */ + private final double azimuth; + + /** Polar angle in radians. */ + private final double polar; + + /** Simple constructor. The given inputs are normalized. + * @param radius Radius value. + * @param azimuth Azimuth angle in radians. + * @param polar Polar angle in radians. + */ + private SphericalCoordinates(double radius, double azimuth, double polar) { + if (radius < 0) { + // negative radius; flip the angles + radius = Math.abs(radius); + azimuth += Geometry.PI; + polar += Geometry.PI; + } + + this.radius = radius; + this.azimuth = normalizeAzimuth(azimuth); + this.polar = normalizePolar(polar); + } + + /** Return the radius value. The value is in the range {@code [0, +Infinity)}. + * @return the radius value + */ + public double getRadius() { + return radius; + } + + /** Return the azimuth angle in radians. This is the angle in the x-y plane measured counter-clockwise from + * the positive x axis. The angle is in the range {@code [0, 2pi)}. + * @return the azimuth angle in radians + */ + public double getAzimuth() { + return azimuth; + } + + /** Return the polar angle in radians. This is the angle the coordinate ray makes with the positive z axis. + * The angle is in the range {@code [0, pi]}. + * @return the polar angle in radians + */ + public double getPolar() { + return polar; + } + + /** {@inheritDoc} */ + @Override + public int getDimension() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isNaN() { + return Double.isNaN(radius) || Double.isNaN(azimuth) || Double.isNaN(polar); + } + + /** {@inheritDoc} */ + @Override + public boolean isInfinite() { + return !isNaN() && (Double.isInfinite(radius) || Double.isInfinite(azimuth) || Double.isInfinite(polar)); + } + - /** Convert this 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. - * @param factory Factory instance that will be passed the computed Cartesian coordinates - * @return the value returned by the factory when passed Cartesian - * coordinates equivalent to this set of spherical coordinates. - */ - public <T> T toCartesian(final Coordinates.Factory3D<T> factory) { - return toCartesian(radius, azimuth, polar, factory); - } - + /** Convert this set of spherical coordinates to a 3 dimensional vector. + * @return A 3-dimensional vector with an equivalent set of + * coordinates. + */ + public Vector3D toVector() { - return toCartesian(Vector3D.getFactory()); ++ return toCartesian(radius, azimuth, polar, Vector3D.FACTORY); + } + + /** Convert this set of spherical coordinates to a 3 dimensional point. + * @return A 3-dimensional point with an equivalent set of + * coordinates. + */ + public Point3D toPoint() { - return toCartesian(Point3D.getFactory()); ++ return toCartesian(radius, azimuth, polar, Point3D.FACTORY); + } + + /** Get a hashCode for this set of spherical coordinates. + * <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 127; + } + return 449 * (79 * Double.hashCode(radius) + Double.hashCode(azimuth) + Double.hashCode(polar)); + } + + /** Test for the equality of two sets of spherical coordinates. + * <p> + * If all values of two sets of coordinates are exactly the same, and none are + * <code>Double.NaN</code>, the two sets are considered to be equal. + * </p> + * <p> + * <code>NaN</code> values are considered to globally affect the coordinates + * and be equal to each other - i.e, if either (or all) values of the + * coordinate set are equal to <code>Double.NaN</code>, the set is equal to + * {@link #NaN}. + * </p> + * + * @param other Object to test for equality to this + * @return true if two SphericalCoordinates objects are equal, false if + * object is null, not an instance of SphericalCoordinates, or + * not equal to this SphericalCoordinates instance + * + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other instanceof SphericalCoordinates) { + final SphericalCoordinates rhs = (SphericalCoordinates) other; + if (rhs.isNaN()) { + return this.isNaN(); + } + + return (radius == rhs.radius) && (azimuth == rhs.azimuth) && (polar == rhs.polar); + } + return false; + } + + /** {@inheritDoc} */ + @Override + public String toString() { - return SimpleCoordinateFormat.getPointFormat().format(radius, azimuth, polar); ++ return SimpleTupleFormat.getDefault().format(radius, azimuth, polar); + } + + /** Return a new instance with the given spherical coordinate values. The values are normalized + * so that {@code radius} lies in the range {@code [0, +Infinity)}, {@code azimuth} lies in the range + * {@code [0, 2pi)}, and {@code polar} lies in the range {@code [0, +pi]}. + * @param radius the length of the line segment from the origin to the coordinate point. + * @param azimuth the angle in the x-y plane, measured in radians counter-clockwise + * from the positive x-axis. + * @param polar the angle in radians between the positive z-axis and the ray from the origin + * to the coordinate point. + * @return a new {@link SphericalCoordinates} instance representing the same point as the given set of + * spherical coordinates. + */ + public static SphericalCoordinates of(final double radius, final double azimuth, final double polar) { + return new SphericalCoordinates(radius, azimuth, polar); + } + + /** Convert the given set of Cartesian coordinates to spherical coordinates. + * @param x X coordinate value + * @param y Y coordinate value + * @param z Z coordinate value + * @return a set of spherical coordinates equivalent to the given Cartesian coordinates + */ + public static SphericalCoordinates ofCartesian(final double x, final double y, final double z) { + final double radius = Math.sqrt((x*x) + (y*y) + (z*z)); + final double azimuth = Math.atan2(y, x); + + // default the polar angle to 0 when the radius is 0 + final double polar = (radius > 0.0) ? Math.acos(z / radius) : 0.0; + + return new SphericalCoordinates(radius, azimuth, polar); + } + + /** Parse the given string and return a new {@link SphericalCoordinates} instance. The parsed + * coordinate values are normalized as in the {@link #of(double, double, double)} method. + * The expected string format is the same as that returned by {@link #toString()}. + * @param input the string to parse + * @return new {@link SphericalCoordinates} instance + * @throws IllegalArgumentException if the string format is invalid. + */ + public static SphericalCoordinates parse(String input) { - return SimpleCoordinateFormat.getPointFormat().parse(input, FACTORY); ++ return SimpleTupleFormat.getDefault().parse(input, FACTORY); + } + + /** Normalize an azimuth value to be within the range {@code [0, 2pi)}. This + * is exactly equivalent to {@link PolarCoordinates#normalizeAzimuth(double)}. + * @param azimuth azimuth value in radians + * @return equivalent azimuth value in the range {@code [0, 2pi)}. + * @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 ++ /** Package private method to 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. + * @param radius The spherical radius value. + * @param azimuth The spherical azimuth angle in radians. + * @param polar The spherical polar angle in radians. + * @param factory Factory instance that will be passed the + * @return the value returned by the factory when passed Cartesian + * coordinates equivalent to the given set of spherical coordinates. + */ - public static <T> T toCartesian(final double radius, final double azimuth, final double polar, - Coordinates.Factory3D<T> factory) { ++ static <T> T toCartesian(final double radius, final double azimuth, final double polar, ++ DoubleFunction3N<T> factory) { + final double xyLength = radius * Math.sin(polar); + + final double x = xyLength * Math.cos(azimuth); + final double y = xyLength * Math.sin(azimuth); + final double z = radius * Math.cos(polar); + - return factory.create(x, y, z); - } - - /** Return a factory object for generating new {@link SphericalCoordinates} instances. - * @return factory object for generating new instances. - */ - public static Coordinates.Factory3D<SphericalCoordinates> getFactory() { - return FACTORY; ++ return factory.apply(x, y, z); + } +} diff --cc commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java index eeeeb72,459b014..c35b2d0 --- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java +++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java @@@ -66,8 -66,8 +66,8 @@@ public final class Vector3D extends Car /** Error message when norms are zero. */ private static final String ZERO_NORM_MSG = "Norm is zero"; -- /** Factory for delegating instance creation. */ - private static Coordinates.Factory3D<Vector3D> FACTORY = new Coordinates.Factory3D<Vector3D>() { - private static DoubleFunction3N<Vector3D> FACTORY = new DoubleFunction3N<Vector3D>() { ++ /** Package private factory for delegating instance creation. */ ++ static DoubleFunction3N<Vector3D> FACTORY = new DoubleFunction3N<Vector3D>() { /** {@inheritDoc} */ @Override diff --cc commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Point2D.java index 6561570,c9fde1f..4009398 --- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Point2D.java +++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Point2D.java @@@ -43,10 -43,10 +43,10 @@@ public final class Point2D extends Cart new Point2D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); /** Serializable UID. */ - private static final long serialVersionUID = 266938651998679754L; + private static final long serialVersionUID = 20180710L; -- /** Factory for delegating instance creation. */ - private static Coordinates.Factory2D<Point2D> FACTORY = new Coordinates.Factory2D<Point2D>() { - private static DoubleFunction2N<Point2D> FACTORY = new DoubleFunction2N<Point2D>() { ++ /** Package private factory for delegating instance creation. */ ++ static DoubleFunction2N<Point2D> FACTORY = new DoubleFunction2N<Point2D>() { /** {@inheritDoc} */ @Override @@@ -178,15 -172,6 +172,15 @@@ return new Point2D(p[0], p[1]); } + /**Return a point with coordinates equivalent to the given set of polar coordinates. + * @param radius The polar coordinate radius value. + * @param azimuth The polar coordinate azimuth angle in radians. + * @return point instance with coordinates equivalent to the given polar coordinates. + */ + public static Point2D ofPolar(final double radius, final double azimuth) { - return PolarCoordinates.toCartesian(radius, azimuth, getFactory()); ++ return PolarCoordinates.toCartesian(radius, azimuth, FACTORY); + } + /** Parses 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 diff --cc commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinates.java index 9dbbfa1,0000000..d1c6aca mode 100644,000000..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 @@@ -1,280 -1,0 +1,262 @@@ +/* + * 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.euclidean.twod; + +import java.io.Serializable; + +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.core.internal.DoubleFunction2N; ++import org.apache.commons.geometry.core.internal.SimpleTupleFormat; +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. + * + * <p>Polar coordinates are defined by a distance from a reference point + * 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 + * positive y-axis. The origin is used as the reference point. Polar coordinate + * are related to Cartesian coordinates as follows: + * <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)} + * and {@code azimuth} is in the range {@code [0, 2pi)}.</p> + * + * @see <a href="https://en.wikipedia.org/wiki/Polar_coordinate_system">Polar Coordinate System</a> + */ +public final class PolarCoordinates implements Spatial, Serializable { + + /** Serializable version UID */ + private static final long serialVersionUID = 20180630L; + + /** Factory object for delegating instance creation. */ - private static final Coordinates.Factory2D<PolarCoordinates> FACTORY = new Coordinates.Factory2D<PolarCoordinates>() { ++ private static final DoubleFunction2N<PolarCoordinates> FACTORY = new DoubleFunction2N<PolarCoordinates>() { + + /** {@inheritDoc} */ + @Override - public PolarCoordinates create(double a1, double a2) { - return new PolarCoordinates(a1, a2); ++ public PolarCoordinates apply(double n1, double n2) { ++ return new PolarCoordinates(n1, n2); + } + }; + + /** Radius value */ + private final double radius; + + /** Azimuth angle in radians */ + private final double azimuth; + + /** Simple constructor. Input values are normalized. + * @param radius Radius value. + * @param azimuth Azimuth angle in radians. + */ + private PolarCoordinates(double radius, double azimuth) { + if (radius < 0) { + // negative radius; flip the angles + radius = Math.abs(radius); + azimuth += Geometry.PI; + } + + this.radius = radius; + this.azimuth = normalizeAzimuth(azimuth);; + } + + /** Return the radius value. The value will be greater than or equal to 0. + * @return radius value + */ + public double getRadius() { + return radius; + } + + /** Return the azimuth angle in radians. The value will be + * in the range {@code [0, 2pi)}. + * @return azimuth value in radians. + */ + public double getAzimuth() { + return azimuth; + } + + /** {@inheritDoc} */ + @Override + public int getDimension() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isNaN() { + return Double.isNaN(radius) || Double.isNaN(azimuth); + } + + /** {@inheritDoc} */ + @Override + public boolean isInfinite() { + return !isNaN() && (Double.isInfinite(radius) || Double.isInfinite(azimuth)); + } + - /** Convert this 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. - * @param factory Factory instance that will be passed the computed Cartesian coordinates - * @return the value returned by the given factory when passed Cartesian - * coordinates equivalent to this set of polar coordinates. - */ - public <T> T toCartesian(final Coordinates.Factory2D<T> factory) { - return toCartesian(radius, azimuth, factory); - } - + /** Convert this set of polar coordinates to a 2-dimensional + * vector. + * @return A 2-dimensional vector with an equivalent set of + * coordinates. + */ + public Vector2D toVector() { - return toCartesian(Vector2D.getFactory()); ++ return toCartesian(radius, azimuth, Vector2D.FACTORY); + } + + /** Convert this set of polar coordinates to a 2-dimensional + * point. + * @return A 2-dimensional point with an equivalent set of + * coordinates. + */ + public Point2D toPoint() { - return toCartesian(Point2D.getFactory()); ++ return toCartesian(radius, azimuth, Point2D.FACTORY); + } + + /** Get a hashCode for this set of polar coordinates. + * <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 191; + } + return 449 * (76 * Double.hashCode(radius) + Double.hashCode(azimuth)); + } + + /** Test for the equality of two sets of polar coordinates. + * <p> + * If all values of two sets of coordinates are exactly the same, and none are + * <code>Double.NaN</code>, the two sets are considered to be equal. + * </p> + * <p> + * <code>NaN</code> values are considered to globally affect the coordinates + * and be equal to each other - i.e, if either (or all) values of the + * coordinate set are equal to <code>Double.NaN</code>, the set is equal to + * {@link #NaN}. + * </p> + * + * @param other Object to test for equality to this + * @return true if two PolarCoordinates objects are equal, false if + * object is null, not an instance of PolarCoordinates, or + * not equal to this PolarCoordinates instance + * + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other instanceof PolarCoordinates) { + final PolarCoordinates rhs = (PolarCoordinates) other; + if (rhs.isNaN()) { + return this.isNaN(); + } + + return (radius == rhs.radius) && (azimuth == rhs.azimuth); + } + return false; + } + + /** {@inheritDoc} */ + @Override + public String toString() { - return SimpleCoordinateFormat.getPointFormat().format(radius, azimuth); ++ return SimpleTupleFormat.getDefault().format(radius, azimuth); + } + + /** Return a new instance with the given polar coordinate values. + * The values are normalized so that {@code radius} lies in the range {@code [0, +Infinity)} + * and {@code azimuth} in the range {@code [0, 2pi)}. + * @param radius Radius value. + * @param azimuth Azimuth angle in radians. + * @return new {@link PolarCoordinates} instance + */ + public static PolarCoordinates of(double radius, double azimuth) { + return new PolarCoordinates(radius, azimuth); + } + + /** Convert the given Cartesian coordinates to polar form. + * @param x X coordinate value + * @param y Y coordinate value + * @return polar coordinates equivalent to the given Cartesian coordinates + */ + public static PolarCoordinates ofCartesian(final double x, final double y) { + final double azimuth = Math.atan2(y, x); + final double radius = Math.hypot(x, y); + + return new PolarCoordinates(radius, azimuth); + } + + /** Parse the given string and return a new polar coordinates instance. The parsed + * coordinates are normalized as in the {@link #of(double, double)} method. The expected string + * format is the same as that returned by {@link #toString()}. + * @param input the string to parse + * @return new {@link PolarCoordinates} instance + * @throws IllegalArgumentException if the string format is invalid. + */ + public static PolarCoordinates parse(String input) { - return SimpleCoordinateFormat.getPointFormat().parse(input, FACTORY); ++ return SimpleTupleFormat.getDefault().parse(input, FACTORY); + } + + /** Normalize an azimuth value to be within the range {@code [0, 2pi)}. + * @param azimuth azimuth value in radians + * @return equivalent azimuth value in the range {@code [0, 2pi)}. + */ + public static double normalizeAzimuth(double azimuth) { + if (Double.isFinite(azimuth) && (azimuth < 0.0 || azimuth >= Geometry.TWO_PI)) { + azimuth = PlaneAngleRadians.normalizeBetweenZeroAndTwoPi(azimuth); + + // azimuth is now in the range [0, 2pi] but we want it to be in the range + // [0, 2pi) in order to have completely unique coordinates + if (azimuth >= Geometry.TWO_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 ++ /** Package private method to 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. + * @param radius Radius value + * @param azimuth Azimuth value in radians + * @param factory Factory instance that will be passed the computed Cartesian coordinates + * @param <T> Type returned by the factory + * @return the value returned by the factory when passed Cartesian + * coordinates equivalent to the given set of polar coordinates. + */ - public static <T> T toCartesian(final double radius, final double azimuth, final Coordinates.Factory2D<T> factory) { ++ static <T> T toCartesian(final double radius, final double azimuth, final DoubleFunction2N<T> factory) { + final double x = radius * Math.cos(azimuth); + final double y = radius * Math.sin(azimuth); + - return factory.create(x, y); - } - - /** Return a factory object for creating new {@link PolarCoordinates} instances. - * @return factory object for creating new instances. - */ - public static Coordinates.Factory2D<PolarCoordinates> getFactory() { - return FACTORY; ++ return factory.apply(x, y); + } +} diff --cc commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java index e2e2e56,4b435cc..6d8f10e --- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java +++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java @@@ -60,8 -60,8 +60,8 @@@ public final class Vector2D extends Car /** Error message when norms are zero. */ private static final String ZERO_NORM_MSG = "Norm is zero"; -- /** Factory for delegating instance creation. */ - private static Coordinates.Factory2D<Vector2D> FACTORY = new Coordinates.Factory2D<Vector2D>() { - private static DoubleFunction2N<Vector2D> FACTORY = new DoubleFunction2N<Vector2D>() { ++ /** Package private factory for delegating instance creation. */ ++ static DoubleFunction2N<Vector2D> FACTORY = new DoubleFunction2N<Vector2D>() { /** {@inheritDoc} */ @Override @@@ -377,15 -371,6 +371,15 @@@ return new Vector2D(v[0], v[1]); } + /** Return a vector with coordinates equivalent to the given set of polar coordinates. + * @param radius The polar coordinate radius value. + * @param azimuth The polar coordinate azimuth angle in radians. + * @return vector instance with coordinates equivalent to the given polar coordinates. + */ + public static Vector2D ofPolar(final double radius, final double azimuth) { - return PolarCoordinates.toCartesian(radius, azimuth, getFactory()); ++ return PolarCoordinates.toCartesian(radius, azimuth, Vector2D.FACTORY); + } + /** Parses the given string and returns a new vector instance. The expected string * format is the same as that returned by {@link #toString()}. * @param str the string to parse diff --cc commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Cartesian3DTest.java index 6326f5e,aadfe73..6e075b9 --- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Cartesian3DTest.java +++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Cartesian3DTest.java @@@ -1,6 -1,7 +1,8 @@@ package org.apache.commons.geometry.euclidean.threed; + import java.util.regex.Pattern; + +import org.apache.commons.geometry.core.Geometry; import org.junit.Assert; import org.junit.Test; @@@ -96,12 -75,20 +98,26 @@@ public class Cartesian3DTest Assert.assertFalse(new StubCartesian3D(0, Double.NaN, Double.POSITIVE_INFINITY).isInfinite()); } + @Test + public void testToString() { + // arrange + StubCartesian3D c = new StubCartesian3D(1, 2, 3); + Pattern pattern = Pattern.compile("\\(1.{0,2}, 2.{0,2}, 3.{0,2}\\)"); + + // act + String str = c.toString(); + + // assert + Assert.assertTrue("Expected string " + str + " to match regex " + pattern, + pattern.matcher(str).matches()); + } + + private void checkSpherical(SphericalCoordinates c, double radius, double azimuth, double polar) { + Assert.assertEquals(radius, c.getRadius(), TEST_TOLERANCE); + Assert.assertEquals(azimuth, c.getAzimuth(), TEST_TOLERANCE); + Assert.assertEquals(polar, c.getPolar(), TEST_TOLERANCE); + } + private static class StubCartesian3D extends Cartesian3D { private static final long serialVersionUID = 1L; diff --cc commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Point3DTest.java index 770cf25,62218a6..b76cbda --- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Point3DTest.java +++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Point3DTest.java @@@ -18,8 -18,6 +18,7 @@@ package org.apache.commons.geometry.euc import java.util.regex.Pattern; +import org.apache.commons.geometry.core.Geometry; - import org.apache.commons.geometry.core.util.Coordinates; import org.apache.commons.numbers.core.Precision; import org.junit.Assert; import org.junit.Test; @@@ -244,37 -242,6 +243,27 @@@ public class Point3DTest } @Test + public void testOfSpherical() { - // arrange ++ // arrange + double sqrt3 = Math.sqrt(3); + + // act/assert + checkPoint(Point3D.ofSpherical(0, 0, 0), 0, 0, 0); + + checkPoint(Point3D.ofSpherical(1, 0, Geometry.HALF_PI), 1, 0, 0); + checkPoint(Point3D.ofSpherical(1, Geometry.PI, Geometry.HALF_PI), -1, 0, 0); + + checkPoint(Point3D.ofSpherical(2, Geometry.HALF_PI, Geometry.HALF_PI), 0, 2, 0); + checkPoint(Point3D.ofSpherical(2, Geometry.MINUS_HALF_PI, Geometry.HALF_PI), 0, -2, 0); + + checkPoint(Point3D.ofSpherical(3, 0, 0), 0, 0, 3); + checkPoint(Point3D.ofSpherical(3, 0, Geometry.PI), 0, 0, -3); + + checkPoint(Point3D.ofSpherical(sqrt3, 0.25 * Geometry.PI, Math.acos(1 / sqrt3)), 1, 1, 1); + checkPoint(Point3D.ofSpherical(sqrt3, -0.75 * Geometry.PI, Math.acos(-1 / sqrt3)), -1, -1, -1); + } + + @Test - public void testGetFactory() { - // act - Coordinates.Factory3D<Point3D> factory = Point3D.getFactory(); - - // assert - checkPoint(factory.create(1, 2, 3), 1, 2, 3); - checkPoint(factory.create(-1, -2, -3), -1, -2, -3); - } - - @Test public void testVectorCombination1() { // arrange Point3D p1 = Point3D.of(1, 2, 3); diff --cc commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinatesTest.java index 11057ed,0000000..ce4f03f mode 100644,000000..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 @@@ -1,430 -1,0 +1,399 @@@ +/* + * 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.euclidean.threed; + +import java.util.regex.Pattern; + +import org.apache.commons.geometry.core.Geometry; - import org.apache.commons.geometry.core.util.Coordinates; ++import org.apache.commons.geometry.core.internal.DoubleFunction3N; +import org.junit.Assert; +import org.junit.Test; + +public class SphericalCoordinatesTest { + + private static final double EPS = 1e-10; + + private static final double QUARTER_PI = 0.25 * Geometry.PI; + private static final double MINUS_QUARTER_PI = -0.25 * Geometry.PI; + private static final double THREE_QUARTER_PI = 0.75 * Geometry.PI; + private static final double MINUS_THREE_QUARTER_PI = -0.75 * Geometry.PI; + + @Test + public void testOf() { + // act/assert + checkSpherical(SphericalCoordinates.of(0, 0, 0), 0, 0, 0); + checkSpherical(SphericalCoordinates.of(0.1, 0.2, 0.3), 0.1, 0.2, 0.3); + + checkSpherical(SphericalCoordinates.of(1, Geometry.HALF_PI, Geometry.PI), + 1, Geometry.HALF_PI, Geometry.PI); + checkSpherical(SphericalCoordinates.of(1, Geometry.MINUS_HALF_PI, Geometry.HALF_PI), + 1, Geometry.THREE_HALVES_PI, Geometry.HALF_PI); + } + + @Test + public void testOf_normalizesAzimuthAngle() { + // act/assert + checkSpherical(SphericalCoordinates.of(2, Geometry.TWO_PI, 0), 2, 0, 0); + checkSpherical(SphericalCoordinates.of(2, Geometry.HALF_PI + Geometry.TWO_PI, 0), 2, Geometry.HALF_PI, 0); + checkSpherical(SphericalCoordinates.of(2, -Geometry.PI, 0), 2, Geometry.PI, 0); + checkSpherical(SphericalCoordinates.of(2, Geometry.THREE_HALVES_PI, 0), 2, Geometry.THREE_HALVES_PI, 0); + } + + @Test + public void testOf_normalizesPolarAngle() { + // act/assert + checkSpherical(SphericalCoordinates.of(1, 0, 0), 1, 0, 0); + + checkSpherical(SphericalCoordinates.of(1, 0, QUARTER_PI), 1, 0, QUARTER_PI); + checkSpherical(SphericalCoordinates.of(1, 0, MINUS_QUARTER_PI), 1, 0, QUARTER_PI); + + checkSpherical(SphericalCoordinates.of(1, 0, Geometry.HALF_PI), 1, 0, Geometry.HALF_PI); + checkSpherical(SphericalCoordinates.of(1, 0, Geometry.MINUS_HALF_PI), 1, 0, Geometry.HALF_PI); + + checkSpherical(SphericalCoordinates.of(1, 0, THREE_QUARTER_PI), 1, 0, THREE_QUARTER_PI); + checkSpherical(SphericalCoordinates.of(1, 0, MINUS_THREE_QUARTER_PI), 1, 0, THREE_QUARTER_PI); + + checkSpherical(SphericalCoordinates.of(1, 0, Geometry.TWO_PI), 1, 0, 0); + checkSpherical(SphericalCoordinates.of(1, 0, Geometry.MINUS_TWO_PI), 1, 0, 0); + } + + @Test + public void testOf_angleWrapAround() { + // act/assert + checkOfWithAngleWrapAround(1, 0, 0); + checkOfWithAngleWrapAround(1, QUARTER_PI, QUARTER_PI); + checkOfWithAngleWrapAround(1, Geometry.HALF_PI, Geometry.HALF_PI); + checkOfWithAngleWrapAround(1, THREE_QUARTER_PI, THREE_QUARTER_PI); + checkOfWithAngleWrapAround(1, Geometry.PI, Geometry.PI); + } + + private void checkOfWithAngleWrapAround(double radius, double azimuth, double polar) { + for (int i=-4; i<=4; ++i) { + checkSpherical( + SphericalCoordinates.of(radius, azimuth + (i * Geometry.TWO_PI), polar + (-i * Geometry.TWO_PI)), + radius, azimuth, polar); + } + } + + @Test + public void testOf_negativeRadius() { + // act/assert + checkSpherical(SphericalCoordinates.of(-2, 0, 0), 2, Geometry.PI, Geometry.PI); + checkSpherical(SphericalCoordinates.of(-2, Geometry.PI, Geometry.PI), 2, 0, 0); + + checkSpherical(SphericalCoordinates.of(-3, Geometry.HALF_PI, QUARTER_PI), 3, Geometry.THREE_HALVES_PI, THREE_QUARTER_PI); + checkSpherical(SphericalCoordinates.of(-3, Geometry.MINUS_HALF_PI, THREE_QUARTER_PI), 3, Geometry.HALF_PI, QUARTER_PI); + + checkSpherical(SphericalCoordinates.of(-4, QUARTER_PI, Geometry.HALF_PI), 4, Geometry.PI + QUARTER_PI, Geometry.HALF_PI); + checkSpherical(SphericalCoordinates.of(-4, MINUS_THREE_QUARTER_PI, Geometry.HALF_PI), 4, QUARTER_PI, Geometry.HALF_PI); + } + + @Test + public void testOf_NaNAndInfinite() { + // act/assert + checkSpherical(SphericalCoordinates.of(Double.NaN, Double.NaN, Double.NaN), + Double.NaN, Double.NaN, Double.NaN); + checkSpherical(SphericalCoordinates.of(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY), + Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + checkSpherical(SphericalCoordinates.of(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY), + Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); + } + + @Test + public void testOfCartesian() { + // arrange + double sqrt3 = Math.sqrt(3); + + // act/assert + checkSpherical(SphericalCoordinates.ofCartesian(0, 0, 0), 0, 0, 0); + + checkSpherical(SphericalCoordinates.ofCartesian(0.1, 0, 0), 0.1, 0, Geometry.HALF_PI); + checkSpherical(SphericalCoordinates.ofCartesian(-0.1, 0, 0), 0.1, Geometry.PI, Geometry.HALF_PI); + + checkSpherical(SphericalCoordinates.ofCartesian(0, 0.1, 0), 0.1, Geometry.HALF_PI, Geometry.HALF_PI); + checkSpherical(SphericalCoordinates.ofCartesian(0, -0.1, 0), 0.1, Geometry.THREE_HALVES_PI, Geometry.HALF_PI); + + checkSpherical(SphericalCoordinates.ofCartesian(0, 0, 0.1), 0.1, 0, 0); + checkSpherical(SphericalCoordinates.ofCartesian(0, 0, -0.1), 0.1, 0, Geometry.PI); + + checkSpherical(SphericalCoordinates.ofCartesian(1, 1, 1), sqrt3, QUARTER_PI, Math.acos(1 / sqrt3)); + checkSpherical(SphericalCoordinates.ofCartesian(-1, -1, -1), sqrt3, 1.25 * Geometry.PI, Math.acos(-1 / sqrt3)); + } + + @Test + public void testToPoint() { + // arrange + double sqrt3 = Math.sqrt(3); + + // act/assert + checkPoint(SphericalCoordinates.of(0, 0, 0).toPoint(), 0, 0, 0); + + checkPoint(SphericalCoordinates.of(1, 0, Geometry.HALF_PI).toPoint(), 1, 0, 0); + checkPoint(SphericalCoordinates.of(1, Geometry.PI, Geometry.HALF_PI).toPoint(), -1, 0, 0); + + checkPoint(SphericalCoordinates.of(2, Geometry.HALF_PI, Geometry.HALF_PI).toPoint(), 0, 2, 0); + checkPoint(SphericalCoordinates.of(2, Geometry.MINUS_HALF_PI, Geometry.HALF_PI).toPoint(), 0, -2, 0); + + checkPoint(SphericalCoordinates.of(3, 0, 0).toPoint(), 0, 0, 3); + checkPoint(SphericalCoordinates.of(3, 0, Geometry.PI).toPoint(), 0, 0, -3); + + checkPoint(SphericalCoordinates.of(Math.sqrt(3), QUARTER_PI, Math.acos(1 / sqrt3)).toPoint(), 1, 1, 1); + checkPoint(SphericalCoordinates.of(Math.sqrt(3), MINUS_THREE_QUARTER_PI, Math.acos(-1 / sqrt3)).toPoint(), -1, -1, -1); + } + + @Test + public void testToVector() { + // arrange + double sqrt3 = Math.sqrt(3); + + // act/assert + checkVector(SphericalCoordinates.of(0, 0, 0).toVector(), 0, 0, 0); + + checkVector(SphericalCoordinates.of(1, 0, Geometry.HALF_PI).toVector(), 1, 0, 0); + checkVector(SphericalCoordinates.of(1, Geometry.PI, Geometry.HALF_PI).toVector(), -1, 0, 0); + + checkVector(SphericalCoordinates.of(2, Geometry.HALF_PI, Geometry.HALF_PI).toVector(), 0, 2, 0); + checkVector(SphericalCoordinates.of(2, Geometry.MINUS_HALF_PI, Geometry.HALF_PI).toVector(), 0, -2, 0); + + checkVector(SphericalCoordinates.of(3, 0, 0).toVector(), 0, 0, 3); + checkVector(SphericalCoordinates.of(3, 0, Geometry.PI).toVector(), 0, 0, -3); + + checkVector(SphericalCoordinates.of(sqrt3, QUARTER_PI, Math.acos(1 / sqrt3)).toVector(), 1, 1, 1); + checkVector(SphericalCoordinates.of(sqrt3, MINUS_THREE_QUARTER_PI, Math.acos(-1 / sqrt3)).toVector(), -1, -1, -1); + } + + @Test - public void testToCartesian_callback() { - // arrange - double sqrt3 = Math.sqrt(3); - Coordinates.Factory3D<Point3D> factory = Point3D.getFactory(); - - // act/assert - checkPoint(SphericalCoordinates.of(0, 0, 0).toCartesian(factory), 0, 0, 0); - - checkPoint(SphericalCoordinates.of(1, 0, Geometry.HALF_PI).toCartesian(factory), 1, 0, 0); - checkPoint(SphericalCoordinates.of(1, Geometry.PI, Geometry.HALF_PI).toCartesian(factory), -1, 0, 0); - - checkPoint(SphericalCoordinates.of(2, Geometry.HALF_PI, Geometry.HALF_PI).toCartesian(factory), 0, 2, 0); - checkPoint(SphericalCoordinates.of(2, Geometry.MINUS_HALF_PI, Geometry.HALF_PI).toCartesian(factory), 0, -2, 0); - - checkPoint(SphericalCoordinates.of(3, 0, 0).toCartesian(factory), 0, 0, 3); - checkPoint(SphericalCoordinates.of(3, 0, Geometry.PI).toCartesian(factory), 0, 0, -3); - - checkPoint(SphericalCoordinates.of(Math.sqrt(3), QUARTER_PI, Math.acos(1 / sqrt3)).toCartesian(factory), 1, 1, 1); - checkPoint(SphericalCoordinates.of(Math.sqrt(3), MINUS_THREE_QUARTER_PI, Math.acos(-1 / sqrt3)).toCartesian(factory), -1, -1, -1); - } - - @Test + public void testToCartesian_static() { + // arrange + double sqrt3 = Math.sqrt(3); - Coordinates.Factory3D<Point3D> factory = Point3D.getFactory(); ++ DoubleFunction3N<Point3D> factory = Point3D.FACTORY; + + // act/assert + checkPoint(SphericalCoordinates.toCartesian(0, 0, 0, factory), 0, 0, 0); + + checkPoint(SphericalCoordinates.toCartesian(1, 0, Geometry.HALF_PI, factory), 1, 0, 0); + checkPoint(SphericalCoordinates.toCartesian(1, Geometry.PI, Geometry.HALF_PI, factory), -1, 0, 0); + + checkPoint(SphericalCoordinates.toCartesian(2, Geometry.HALF_PI, Geometry.HALF_PI, factory), 0, 2, 0); + checkPoint(SphericalCoordinates.toCartesian(2, Geometry.MINUS_HALF_PI, Geometry.HALF_PI, factory), 0, -2, 0); + + checkPoint(SphericalCoordinates.toCartesian(3, 0, 0, factory), 0, 0, 3); + checkPoint(SphericalCoordinates.toCartesian(3, 0, Geometry.PI, factory), 0, 0, -3); + + checkPoint(SphericalCoordinates.toCartesian(Math.sqrt(3), QUARTER_PI, Math.acos(1 / sqrt3), factory), 1, 1, 1); + checkPoint(SphericalCoordinates.toCartesian(Math.sqrt(3), MINUS_THREE_QUARTER_PI, Math.acos(-1 / sqrt3), factory), -1, -1, -1); + } + + @Test + public void testGetDimension() { + // arrange + SphericalCoordinates s = SphericalCoordinates.of(0, 0, 0); + + // act/assert + Assert.assertEquals(3, s.getDimension()); + } + + @Test + public void testNaN() { + // act/assert + Assert.assertTrue(SphericalCoordinates.of(0, 0, Double.NaN).isNaN()); + Assert.assertTrue(SphericalCoordinates.of(0, Double.NaN, 0).isNaN()); + Assert.assertTrue(SphericalCoordinates.of(Double.NaN, 0, 0).isNaN()); + + Assert.assertFalse(SphericalCoordinates.of(1, 1, 1).isNaN()); + Assert.assertFalse(SphericalCoordinates.of(1, 1, Double.NEGATIVE_INFINITY).isNaN()); + Assert.assertFalse(SphericalCoordinates.of(1, Double.POSITIVE_INFINITY, 1).isNaN()); + Assert.assertFalse(SphericalCoordinates.of(Double.NEGATIVE_INFINITY, 1, 1).isNaN()); + } + + @Test + public void testInfinite() { + // act/assert + Assert.assertTrue(SphericalCoordinates.of(0, 0, Double.NEGATIVE_INFINITY).isInfinite()); + Assert.assertTrue(SphericalCoordinates.of(0, Double.NEGATIVE_INFINITY, 0).isInfinite()); + Assert.assertTrue(SphericalCoordinates.of(Double.NEGATIVE_INFINITY, 0, 0).isInfinite()); + Assert.assertTrue(SphericalCoordinates.of(0, 0, Double.POSITIVE_INFINITY).isInfinite()); + Assert.assertTrue(SphericalCoordinates.of(0, Double.POSITIVE_INFINITY, 0).isInfinite()); + Assert.assertTrue(SphericalCoordinates.of(Double.POSITIVE_INFINITY, 0, 0).isInfinite()); + + Assert.assertFalse(SphericalCoordinates.of(1, 1, 1).isInfinite()); + Assert.assertFalse(SphericalCoordinates.of(0, 0, Double.NaN).isInfinite()); + Assert.assertFalse(SphericalCoordinates.of(0, Double.NEGATIVE_INFINITY, Double.NaN).isInfinite()); + Assert.assertFalse(SphericalCoordinates.of(Double.NaN, 0, Double.NEGATIVE_INFINITY).isInfinite()); + Assert.assertFalse(SphericalCoordinates.of(Double.POSITIVE_INFINITY, Double.NaN, 0).isInfinite()); + Assert.assertFalse(SphericalCoordinates.of(0, Double.NaN, Double.POSITIVE_INFINITY).isInfinite()); + } + + @Test + public void testHashCode() { + // arrange + SphericalCoordinates a = SphericalCoordinates.of(1, 2, 3); + SphericalCoordinates b = SphericalCoordinates.of(10, 2, 3); + SphericalCoordinates c = SphericalCoordinates.of(1, 20, 3); + SphericalCoordinates d = SphericalCoordinates.of(1, 2, 30); + + SphericalCoordinates e = SphericalCoordinates.of(1, 2, 3); + + // act/assert + Assert.assertEquals(a.hashCode(), a.hashCode()); + Assert.assertEquals(a.hashCode(), e.hashCode()); + + Assert.assertNotEquals(a.hashCode(), b.hashCode()); + Assert.assertNotEquals(a.hashCode(), c.hashCode()); + Assert.assertNotEquals(a.hashCode(), d.hashCode()); + } + + @Test + public void testHashCode_NaNInstancesHaveSameHashCode() { + // arrange + SphericalCoordinates a = SphericalCoordinates.of(1, 2, Double.NaN); + SphericalCoordinates b = SphericalCoordinates.of(1, Double.NaN, 3); + SphericalCoordinates c = SphericalCoordinates.of(Double.NaN, 2, 3); + + // act/assert + Assert.assertEquals(a.hashCode(), b.hashCode()); + Assert.assertEquals(b.hashCode(), c.hashCode()); + } + + @Test + public void testEquals() { + // arrange + SphericalCoordinates a = SphericalCoordinates.of(1, 2, 3); + SphericalCoordinates b = SphericalCoordinates.of(10, 2, 3); + SphericalCoordinates c = SphericalCoordinates.of(1, 20, 3); + SphericalCoordinates d = SphericalCoordinates.of(1, 2, 30); + + SphericalCoordinates e = SphericalCoordinates.of(1, 2, 3); + + // act/assert + Assert.assertFalse(a.equals(null)); + Assert.assertFalse(a.equals(new Object())); + + Assert.assertTrue(a.equals(a)); + Assert.assertTrue(a.equals(e)); + + Assert.assertFalse(a.equals(b)); + Assert.assertFalse(a.equals(c)); + Assert.assertFalse(a.equals(d)); + } + + @Test + public void testEquals_NaNInstancesEqual() { + // arrange + SphericalCoordinates a = SphericalCoordinates.of(1, 2, Double.NaN); + SphericalCoordinates b = SphericalCoordinates.of(1, Double.NaN, 3); + SphericalCoordinates c = SphericalCoordinates.of(Double.NaN, 2, 3); + + // act/assert + Assert.assertTrue(a.equals(b)); + Assert.assertTrue(b.equals(c)); + } + + @Test + public void testToString() { + // arrange + SphericalCoordinates sph = SphericalCoordinates.of(1, 2, 3); + Pattern pattern = Pattern.compile("\\(1.{0,2}, 2.{0,2}, 3.{0,2}\\)"); + + // act + String str = sph.toString();; + + // assert + Assert.assertTrue("Expected string " + str + " to match regex " + pattern, + pattern.matcher(str).matches()); + } + + @Test + public void testParse() { + // act/assert + checkSpherical(SphericalCoordinates.parse("(1, 2, 3)"), 1, 2, 3); + checkSpherical(SphericalCoordinates.parse("( -2.0 , 1 , -5e-1)"), 2, 1 + Geometry.PI, Geometry.PI - 0.5); + checkSpherical(SphericalCoordinates.parse("(NaN,Infinity,-Infinity)"), Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY); + } + + @Test(expected = IllegalArgumentException.class) + public void testParse_failure() { + // act/assert + SphericalCoordinates.parse("abc"); + } + + @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.THREE_HALVES_PI), Geometry.THREE_HALVES_PI, EPS); + Assert.assertEquals(SphericalCoordinates.normalizeAzimuth(Geometry.TWO_PI), 0.0, EPS); + + Assert.assertEquals(SphericalCoordinates.normalizeAzimuth(Geometry.MINUS_HALF_PI), Geometry.THREE_HALVES_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(); - - // assert - checkSpherical(factory.create(2, 0.5 + Geometry.TWO_PI, 0.1 + Geometry.PI), 2, 0.5, Geometry.PI - 0.1); - } - + private void checkSpherical(SphericalCoordinates c, double radius, double azimuth, double polar) { + Assert.assertEquals(radius, c.getRadius(), EPS); + Assert.assertEquals(azimuth, c.getAzimuth(), EPS); + Assert.assertEquals(polar, c.getPolar(), EPS); + } + + private void checkPoint(Point3D p, double x, double y, double z) { + Assert.assertEquals(x, p.getX(), EPS); + Assert.assertEquals(y, p.getY(), EPS); + Assert.assertEquals(z, p.getZ(), EPS); + } + + private void checkVector(Vector3D v, double x, double y, double z) { + Assert.assertEquals(x, v.getX(), EPS); + Assert.assertEquals(y, v.getY(), EPS); + Assert.assertEquals(z, v.getZ(), EPS); + } +} diff --cc commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java index d52d358,c4017bd..f6a1606 --- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java +++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java @@@ -689,37 -688,6 +688,27 @@@ public class Vector3DTest } @Test + public void testOfSpherical() { - // arrange ++ // arrange + double sqrt3 = Math.sqrt(3); + + // act/assert + checkVector(Vector3D.ofSpherical(0, 0, 0), 0, 0, 0); + + checkVector(Vector3D.ofSpherical(1, 0, Geometry.HALF_PI), 1, 0, 0); + checkVector(Vector3D.ofSpherical(1, Geometry.PI, Geometry.HALF_PI), -1, 0, 0); + + checkVector(Vector3D.ofSpherical(2, Geometry.HALF_PI, Geometry.HALF_PI), 0, 2, 0); + checkVector(Vector3D.ofSpherical(2, Geometry.MINUS_HALF_PI, Geometry.HALF_PI), 0, -2, 0); + + checkVector(Vector3D.ofSpherical(3, 0, 0), 0, 0, 3); + checkVector(Vector3D.ofSpherical(3, 0, Geometry.PI), 0, 0, -3); + + checkVector(Vector3D.ofSpherical(sqrt3, 0.25 * Geometry.PI, Math.acos(1 / sqrt3)), 1, 1, 1); + checkVector(Vector3D.ofSpherical(sqrt3, -0.75 * Geometry.PI, Math.acos(-1 / sqrt3)), -1, -1, -1); + } + + @Test - public void testGetFactory() { - // act - Coordinates.Factory3D<Vector3D> factory = Vector3D.getFactory(); - - // assert - checkVector(factory.create(1, 2, 3), 1, 2, 3); - checkVector(factory.create(-1, -2, -3), -1, -2, -3); - } - - @Test public void testLinearCombination1() { // arrange Vector3D p1 = Vector3D.of(1, 2, 3); diff --cc commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Cartesian2DTest.java index 261ad09,e3d5127..5852616 --- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Cartesian2DTest.java +++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Cartesian2DTest.java @@@ -1,10 -1,11 +1,11 @@@ package org.apache.commons.geometry.euclidean.twod; + import java.util.regex.Pattern; + +import org.apache.commons.geometry.core.Geometry; import org.junit.Assert; import org.junit.Test; -- public class Cartesian2DTest { private static final double TEST_TOLERANCE = 1e-15; @@@ -70,37 -71,19 +71,51 @@@ } @Test + public void testToPolar() { + // arrange + double sqrt2 = Math.sqrt(2.0); + + // act/assert + checkPolar(new StubCartesian2D(0, 0).toPolar(), 0, 0); + + checkPolar(new StubCartesian2D(1, 0).toPolar(), 1, 0); + checkPolar(new StubCartesian2D(-1, 0).toPolar(), 1, Geometry.PI); + + checkPolar(new StubCartesian2D(0, 2).toPolar(), 2, Geometry.HALF_PI); + checkPolar(new StubCartesian2D(0, -2).toPolar(), 2, Geometry.THREE_HALVES_PI); + + checkPolar(new StubCartesian2D(sqrt2, sqrt2).toPolar(), 2, 0.25 * Geometry.PI); + checkPolar(new StubCartesian2D(-sqrt2, sqrt2).toPolar(), 2, 0.75 * Geometry.PI); + checkPolar(new StubCartesian2D(sqrt2, -sqrt2).toPolar(), 2, 1.75 * Geometry.PI); + checkPolar(new StubCartesian2D(-sqrt2, -sqrt2).toPolar(), 2, 1.25 * Geometry.PI); + } + + @Test + public void testToPolar_NaNAndInfinite() { + // act/assert + Assert.assertTrue(new StubCartesian2D(Double.NaN, Double.NaN).toPolar().isNaN()); + Assert.assertTrue(new StubCartesian2D(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY).toPolar().isInfinite()); + } + ++ @Test + public void testToString() { + // arrange + StubCartesian2D c = new StubCartesian2D(1, 2); + Pattern pattern = Pattern.compile("\\(1.{0,2}, 2.{0,2}\\)"); + + // act + String str = c.toString(); + + // assert + Assert.assertTrue("Expected string " + str + " to match regex " + pattern, + pattern.matcher(str).matches()); + } + + private void checkPolar(PolarCoordinates polar, double radius, double azimuth) { + Assert.assertEquals(radius, polar.getRadius(), TEST_TOLERANCE); + Assert.assertEquals(azimuth, polar.getAzimuth(), TEST_TOLERANCE); + } + private static class StubCartesian2D extends Cartesian2D { private static final long serialVersionUID = 1L; diff --cc commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Point2DTest.java index f71ce00,e6cf422..69c3aa3 --- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Point2DTest.java +++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Point2DTest.java @@@ -18,8 -18,6 +18,7 @@@ package org.apache.commons.geometry.euc import java.util.regex.Pattern; +import org.apache.commons.geometry.core.Geometry; - import org.apache.commons.geometry.core.util.Coordinates; import org.apache.commons.numbers.core.Precision; import org.junit.Assert; import org.junit.Test; @@@ -221,38 -219,6 +220,28 @@@ public class Point2DTest } @Test + public void testOfPolar() { + // arrange + double eps = 1e-15; + double sqrt2 = Math.sqrt(2.0); + + // act/assert + checkPoint(Point2D.ofPolar(0, 0), 0, 0, eps); + checkPoint(Point2D.ofPolar(1, 0), 1, 0, eps); + + checkPoint(Point2D.ofPolar(2, Geometry.PI), -2, 0, eps); + checkPoint(Point2D.ofPolar(-2, Geometry.PI), 2, 0, eps); + + checkPoint(Point2D.ofPolar(2, Geometry.HALF_PI), 0, 2, eps); + checkPoint(Point2D.ofPolar(-2, Geometry.HALF_PI), 0, -2, eps); + + checkPoint(Point2D.ofPolar(2, 0.25 * Geometry.PI), sqrt2, sqrt2, eps); + checkPoint(Point2D.ofPolar(2, 0.75 * Geometry.PI), -sqrt2, sqrt2, eps); + checkPoint(Point2D.ofPolar(2, -0.25 * Geometry.PI), sqrt2, - sqrt2, eps); + checkPoint(Point2D.ofPolar(2, -0.75 * Geometry.PI), -sqrt2, - sqrt2, eps); + } + + @Test - public void testGetFactory() { - // act - Coordinates.Factory2D<Point2D> factory = Point2D.getFactory(); - - // assert - checkPoint(factory.create(1, 2), 1, 2); - checkPoint(factory.create(-1, -2), -1, -2); - } - - @Test public void testVectorCombination1() { // arrange Point2D p1 = Point2D.of(1, 2); diff --cc commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinatesTest.java index ce74382,0000000..524e7e0 mode 100644,000000..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 @@@ -1,392 -1,0 +1,362 @@@ +/* + * 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.euclidean.twod; + +import java.util.regex.Pattern; + +import org.apache.commons.geometry.core.Geometry; - import org.apache.commons.geometry.core.util.Coordinates; ++import org.apache.commons.geometry.core.internal.DoubleFunction2N; +import org.junit.Assert; +import org.junit.Test; + +public class PolarCoordinatesTest { + + private static final double EPS = 1e-10; + + @Test + public void testOf() { + // act/assert + checkPolar(PolarCoordinates.of(0, 0), 0, 0); + + checkPolar(PolarCoordinates.of(2, 0), 2, 0); + checkPolar(PolarCoordinates.of(2, Geometry.HALF_PI), 2, Geometry.HALF_PI); + checkPolar(PolarCoordinates.of(2, Geometry.PI), 2, Geometry.PI); + checkPolar(PolarCoordinates.of(2, Geometry.MINUS_HALF_PI), 2, Geometry.THREE_HALVES_PI); + } + + @Test + public void testOf_unnormalizedAngles() { + // act/assert + checkPolar(PolarCoordinates.of(2, Geometry.TWO_PI), 2, 0); + checkPolar(PolarCoordinates.of(2, Geometry.HALF_PI + Geometry.TWO_PI), 2, Geometry.HALF_PI); + checkPolar(PolarCoordinates.of(2, -Geometry.PI), 2, Geometry.PI); + checkPolar(PolarCoordinates.of(2, -Geometry.PI * 1.5), 2, Geometry.HALF_PI); + } + + @Test + public void testOf_azimuthWrapAround() { + // arrange + double delta = 1e-6; + + // act/assert + checkAzimuthWrapAround(2, 0); + checkAzimuthWrapAround(2, delta); + checkAzimuthWrapAround(2, Geometry.PI - delta); + checkAzimuthWrapAround(2, Geometry.PI); + + checkAzimuthWrapAround(2, Geometry.THREE_HALVES_PI); + checkAzimuthWrapAround(2, Geometry.TWO_PI - delta); + } + + private void checkAzimuthWrapAround(double radius, double azimuth) { + checkPolar(PolarCoordinates.of(radius, azimuth), radius, azimuth); + + checkPolar(PolarCoordinates.of(radius, azimuth - Geometry.TWO_PI), radius, azimuth); + checkPolar(PolarCoordinates.of(radius, azimuth - (2 * Geometry.TWO_PI)), radius, azimuth); + checkPolar(PolarCoordinates.of(radius, azimuth - (3 * Geometry.TWO_PI)), radius, azimuth); + + checkPolar(PolarCoordinates.of(radius, azimuth + Geometry.TWO_PI), radius, azimuth); + checkPolar(PolarCoordinates.of(radius, azimuth + (2 * Geometry.TWO_PI)), radius, azimuth); + checkPolar(PolarCoordinates.of(radius, azimuth + (3 * Geometry.TWO_PI)), radius, azimuth); + } + + @Test + public void testOf_negativeRadius() { + // act/assert + checkPolar(PolarCoordinates.of(-1, 0), 1, Geometry.PI); + checkPolar(PolarCoordinates.of(-1e-6, Geometry.HALF_PI), 1e-6, Geometry.THREE_HALVES_PI); + checkPolar(PolarCoordinates.of(-2, Geometry.PI), 2, 0); + checkPolar(PolarCoordinates.of(-3, Geometry.MINUS_HALF_PI), 3, Geometry.HALF_PI); + } + + @Test + public void testOf_NaNAndInfinite() { + // act/assert + checkPolar(PolarCoordinates.of(Double.NaN, 0), Double.NaN, 0); + checkPolar(PolarCoordinates.of(Double.NEGATIVE_INFINITY, 0), Double.POSITIVE_INFINITY, Geometry.PI); + checkPolar(PolarCoordinates.of(Double.POSITIVE_INFINITY, 0), Double.POSITIVE_INFINITY, 0); + + checkPolar(PolarCoordinates.of(0, Double.NaN), 0, Double.NaN); + checkPolar(PolarCoordinates.of(0, Double.NEGATIVE_INFINITY), 0, Double.NEGATIVE_INFINITY); + checkPolar(PolarCoordinates.of(0, Double.POSITIVE_INFINITY), 0, Double.POSITIVE_INFINITY); + } + + @Test + public void testOfCartesian() { + // arrange + double sqrt2 = Math.sqrt(2); + + // act/assert + checkPolar(PolarCoordinates.ofCartesian(0, 0), 0, 0); + + checkPolar(PolarCoordinates.ofCartesian(1, 0), 1, 0); + checkPolar(PolarCoordinates.ofCartesian(1, 1), sqrt2, 0.25 * Geometry.PI); + checkPolar(PolarCoordinates.ofCartesian(0, 1), 1, Geometry.HALF_PI); + + checkPolar(PolarCoordinates.ofCartesian(-1, 1), sqrt2, 0.75 * Geometry.PI); + checkPolar(PolarCoordinates.ofCartesian(-1, 0), 1, Geometry.PI); + checkPolar(PolarCoordinates.ofCartesian(-1, -1), sqrt2, 1.25 * Geometry.PI); + + checkPolar(PolarCoordinates.ofCartesian(0, -1), 1, 1.5 * Geometry.PI); + checkPolar(PolarCoordinates.ofCartesian(1, -1), sqrt2, 1.75 * Geometry.PI); + } + + @Test + public void testDimension() { + // arrange + PolarCoordinates p = PolarCoordinates.of(1, 0); + + // act/assert + Assert.assertEquals(2, p.getDimension()); + } + + @Test + public void testIsNaN() { + // act/assert + Assert.assertFalse(PolarCoordinates.of(1, 0).isNaN()); + Assert.assertFalse(PolarCoordinates.of(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY).isNaN()); + + Assert.assertTrue(PolarCoordinates.of(Double.NaN, 0).isNaN()); + Assert.assertTrue(PolarCoordinates.of(1, Double.NaN).isNaN()); + Assert.assertTrue(PolarCoordinates.of(Double.NaN, Double.NaN).isNaN()); + } + + @Test + public void testIsInfinite() { + // act/assert + Assert.assertFalse(PolarCoordinates.of(1, 0).isInfinite()); + Assert.assertFalse(PolarCoordinates.of(Double.NaN, Double.NaN).isInfinite()); + + Assert.assertTrue(PolarCoordinates.of(Double.POSITIVE_INFINITY, 0).isInfinite()); + Assert.assertTrue(PolarCoordinates.of(Double.NEGATIVE_INFINITY, 0).isInfinite()); + Assert.assertFalse(PolarCoordinates.of(Double.NEGATIVE_INFINITY, Double.NaN).isInfinite()); + + Assert.assertTrue(PolarCoordinates.of(0, Double.POSITIVE_INFINITY).isInfinite()); + Assert.assertTrue(PolarCoordinates.of(0, Double.NEGATIVE_INFINITY).isInfinite()); + Assert.assertFalse(PolarCoordinates.of(Double.NaN, Double.NEGATIVE_INFINITY).isInfinite()); + + Assert.assertTrue(PolarCoordinates.of(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY).isInfinite()); + Assert.assertTrue(PolarCoordinates.of(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY).isInfinite()); + } + + @Test + public void testHashCode() { + // arrange + PolarCoordinates a = PolarCoordinates.of(1, 2); + PolarCoordinates b = PolarCoordinates.of(10, 2); + PolarCoordinates c = PolarCoordinates.of(10, 20); + PolarCoordinates d = PolarCoordinates.of(1, 20); + + PolarCoordinates e = PolarCoordinates.of(1, 2); + + // act/assert + Assert.assertEquals(a.hashCode(), a.hashCode()); + Assert.assertEquals(a.hashCode(), e.hashCode()); + + Assert.assertNotEquals(a.hashCode(), b.hashCode()); + Assert.assertNotEquals(a.hashCode(), c.hashCode()); + Assert.assertNotEquals(a.hashCode(), d.hashCode()); + } + + @Test + public void testHashCode_NaNInstancesHaveSameHashCode() { + // arrange + PolarCoordinates a = PolarCoordinates.of(1, Double.NaN); + PolarCoordinates b = PolarCoordinates.of(Double.NaN, 1); + + // act/assert + Assert.assertEquals(a.hashCode(), b.hashCode()); + } + + @Test + public void testEquals() { + // arrange + PolarCoordinates a = PolarCoordinates.of(1, 2); + PolarCoordinates b = PolarCoordinates.of(10, 2); + PolarCoordinates c = PolarCoordinates.of(10, 20); + PolarCoordinates d = PolarCoordinates.of(1, 20); + + PolarCoordinates e = PolarCoordinates.of(1, 2); + + // act/assert + Assert.assertFalse(a.equals(null)); + Assert.assertFalse(a.equals(new Object())); + + Assert.assertTrue(a.equals(a)); + Assert.assertTrue(a.equals(e)); + + Assert.assertFalse(a.equals(b)); + Assert.assertFalse(a.equals(c)); + Assert.assertFalse(a.equals(d)); + } + + @Test + public void testEquals_NaNInstancesEqual() { + // arrange + PolarCoordinates a = PolarCoordinates.of(1, Double.NaN); + PolarCoordinates b = PolarCoordinates.of(Double.NaN, 1); + + // act/assert + Assert.assertTrue(a.equals(b)); + } + + @Test + public void testToVector() { + // arrange + double sqrt2 = Math.sqrt(2); + + // act/assert + checkVector(PolarCoordinates.of(0, 0).toVector(), 0, 0); + + checkVector(PolarCoordinates.of(1, 0).toVector(), 1, 0); + checkVector(PolarCoordinates.of(sqrt2, 0.25 * Geometry.PI).toVector(), 1, 1); + checkVector(PolarCoordinates.of(1, Geometry.HALF_PI).toVector(), 0, 1); + + checkVector(PolarCoordinates.of(sqrt2, 0.75 * Geometry.PI).toVector(), -1, 1); + checkVector(PolarCoordinates.of(1, Geometry.PI).toVector(), -1, 0); + checkVector(PolarCoordinates.of(sqrt2, -0.75 * Geometry.PI).toVector(), -1, -1); + + checkVector(PolarCoordinates.of(1, Geometry.MINUS_HALF_PI).toVector(), 0, -1); + checkVector(PolarCoordinates.of(sqrt2, -0.25 * Geometry.PI).toVector(), 1, -1); + } + + @Test + public void testToPoint() { + // arrange + double sqrt2 = Math.sqrt(2); + + // act/assert + checkPoint(PolarCoordinates.of(0, 0).toPoint(), 0, 0); + + checkPoint(PolarCoordinates.of(1, 0).toPoint(), 1, 0); + checkPoint(PolarCoordinates.of(sqrt2, 0.25 * Geometry.PI).toPoint(), 1, 1); + checkPoint(PolarCoordinates.of(1, Geometry.HALF_PI).toPoint(), 0, 1); + + checkPoint(PolarCoordinates.of(sqrt2, 0.75 * Geometry.PI).toPoint(), -1, 1); + checkPoint(PolarCoordinates.of(1, Geometry.PI).toPoint(), -1, 0); + checkPoint(PolarCoordinates.of(sqrt2, -0.75 * Geometry.PI).toPoint(), -1, -1); + + checkPoint(PolarCoordinates.of(1, Geometry.MINUS_HALF_PI).toPoint(), 0, -1); + checkPoint(PolarCoordinates.of(sqrt2, -0.25 * Geometry.PI).toPoint(), 1, -1); + } + + @Test - public void testToCartesian() { - // arrange - Coordinates.Factory2D<Point2D> factory = Point2D.getFactory(); - double sqrt2 = Math.sqrt(2); - - // act/assert - checkPoint(PolarCoordinates.of(0, 0).toCartesian(factory), 0, 0); - - checkPoint(PolarCoordinates.of(1, 0).toCartesian(factory), 1, 0); - checkPoint(PolarCoordinates.of(sqrt2, 0.25 * Geometry.PI).toCartesian(factory), 1, 1); - checkPoint(PolarCoordinates.of(1, Geometry.HALF_PI).toCartesian(factory), 0, 1); - - checkPoint(PolarCoordinates.of(sqrt2, 0.75 * Geometry.PI).toCartesian(factory), -1, 1); - checkPoint(PolarCoordinates.of(1, Geometry.PI).toCartesian(factory), -1, 0); - checkPoint(PolarCoordinates.of(sqrt2, -0.75 * Geometry.PI).toCartesian(factory), -1, -1); - - checkPoint(PolarCoordinates.of(1, Geometry.MINUS_HALF_PI).toCartesian(factory), 0, -1); - checkPoint(PolarCoordinates.of(sqrt2, -0.25 * Geometry.PI).toCartesian(factory), 1, -1); - } - - @Test + public void testToCartesian_static() { + // arrange - Coordinates.Factory2D<Point2D> factory = Point2D.getFactory(); ++ DoubleFunction2N<Point2D> factory = Point2D.FACTORY; + double sqrt2 = Math.sqrt(2); + + // act/assert + checkPoint(PolarCoordinates.toCartesian(0, 0, factory), 0, 0); + + checkPoint(PolarCoordinates.toCartesian(1, 0, factory), 1, 0); + checkPoint(PolarCoordinates.toCartesian(sqrt2, 0.25 * Geometry.PI, factory), 1, 1); + checkPoint(PolarCoordinates.toCartesian(1, Geometry.HALF_PI, factory), 0, 1); + + checkPoint(PolarCoordinates.toCartesian(sqrt2, 0.75 * Geometry.PI, factory), -1, 1); + checkPoint(PolarCoordinates.toCartesian(1, Geometry.PI, factory), -1, 0); + checkPoint(PolarCoordinates.toCartesian(sqrt2, -0.75 * Geometry.PI, factory), -1, -1); + + checkPoint(PolarCoordinates.toCartesian(1, Geometry.MINUS_HALF_PI, factory), 0, -1); + checkPoint(PolarCoordinates.toCartesian(sqrt2, -0.25 * Geometry.PI, factory), 1, -1); + } + + @Test + public void testToCartesian_static_NaNAndInfinite() { + // arrange - Coordinates.Factory2D<Point2D> factory = Point2D.getFactory(); ++ DoubleFunction2N<Point2D> factory = Point2D.FACTORY; + + // act/assert + Assert.assertTrue(PolarCoordinates.toCartesian(Double.NaN, 0, factory).isNaN()); + Assert.assertTrue(PolarCoordinates.toCartesian(0, Double.NaN, factory).isNaN()); + + Assert.assertTrue(PolarCoordinates.toCartesian(Double.POSITIVE_INFINITY, 0, factory).isNaN()); + Assert.assertTrue(PolarCoordinates.toCartesian(0, Double.POSITIVE_INFINITY, factory).isNaN()); + Assert.assertTrue(PolarCoordinates.toCartesian(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, factory).isNaN()); + + Assert.assertTrue(PolarCoordinates.toCartesian(Double.NEGATIVE_INFINITY, 0, factory).isNaN()); + Assert.assertTrue(PolarCoordinates.toCartesian(0, Double.NEGATIVE_INFINITY, factory).isNaN()); + Assert.assertTrue(PolarCoordinates.toCartesian(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, factory).isNaN()); + } + + @Test + public void testToString() { + // arrange + PolarCoordinates polar = PolarCoordinates.of(1, 2); + Pattern pattern = Pattern.compile("\\(1.{0,2}, 2.{0,2}\\)"); + + // act + String str = polar.toString();; + + // assert + Assert.assertTrue("Expected string " + str + " to match regex " + pattern, + pattern.matcher(str).matches()); + } + + @Test + public void testParse() { + // act/assert + checkPolar(PolarCoordinates.parse("(1, 2)"), 1, 2); + checkPolar(PolarCoordinates.parse("( -1 , 0.5 )"), 1, 0.5 + Geometry.PI); + checkPolar(PolarCoordinates.parse("(NaN,-Infinity)"), Double.NaN, Double.NEGATIVE_INFINITY); + } + + @Test(expected = IllegalArgumentException.class) + public void testParse_failure() { + // act/assert + PolarCoordinates.parse("abc"); + } + + @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.THREE_HALVES_PI), Geometry.THREE_HALVES_PI, EPS); + Assert.assertEquals(PolarCoordinates.normalizeAzimuth(Geometry.TWO_PI), 0.0, EPS); + + Assert.assertEquals(PolarCoordinates.normalizeAzimuth(Geometry.MINUS_HALF_PI), Geometry.THREE_HALVES_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(); - - // assert - checkPolar(factory.create(-1, Geometry.HALF_PI), 1, Geometry.THREE_HALVES_PI); - } - + private void checkPolar(PolarCoordinates polar, double radius, double azimuth) { + Assert.assertEquals(radius, polar.getRadius(), EPS); + Assert.assertEquals(azimuth, polar.getAzimuth(), EPS); + } + + private void checkVector(Vector2D v, double x, double y) { + Assert.assertEquals(x, v.getX(), EPS); + Assert.assertEquals(y, v.getY(), EPS); + } + + private void checkPoint(Point2D p, double x, double y) { + Assert.assertEquals(x, p.getX(), EPS); + Assert.assertEquals(y, p.getY(), EPS); + } +} diff --cc commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java index dbd2a2a,83d56a1..1ef8d3b --- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java +++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java @@@ -483,40 -482,7 +482,30 @@@ public class Vector2DTest // act/assert Vector2D.of(new double[] {0.0 }); } + + @Test + public void testOfPolar() { + // arrange + double eps = 1e-15; + double sqrt2 = Math.sqrt(2.0); + + // act/assert + checkVector(Vector2D.ofPolar(0, 0), 0, 0, eps); + checkVector(Vector2D.ofPolar(1, 0), 1, 0, eps); + + checkVector(Vector2D.ofPolar(2, Geometry.PI), -2, 0, eps); + checkVector(Vector2D.ofPolar(-2, Geometry.PI), 2, 0, eps); + + checkVector(Vector2D.ofPolar(2, Geometry.HALF_PI), 0, 2, eps); + checkVector(Vector2D.ofPolar(-2, Geometry.HALF_PI), 0, -2, eps); + + checkVector(Vector2D.ofPolar(2, 0.25 * Geometry.PI), sqrt2, sqrt2, eps); + checkVector(Vector2D.ofPolar(2, 0.75 * Geometry.PI), -sqrt2, sqrt2, eps); + checkVector(Vector2D.ofPolar(2, -0.25 * Geometry.PI), sqrt2, - sqrt2, eps); + checkVector(Vector2D.ofPolar(2, -0.75 * Geometry.PI), -sqrt2, - sqrt2, eps); + } + @Test - public void testGetFactory() { - // act - Coordinates.Factory2D<Vector2D> factory = Vector2D.getFactory(); - - // assert - checkVector(factory.create(1, 2), 1, 2); - checkVector(factory.create(-1, -2), -1, -2); - } - - @Test public void testLinearCombination1() { // arrange Vector2D p1 = Vector2D.of(1, 2); diff --cc commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java index 20602e7,21139d2..d18b5a9 --- 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 @@@ -19,10 -19,10 +19,10 @@@ package org.apache.commons.geometry.sph 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.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.PlaneAngleRadians; /** This class represents a point on the 1-sphere. * <p>Instances of this class are guaranteed to be immutable.</p> @@@ -42,8 -42,8 +42,8 @@@ public final class S1Point implements P /** {@inheritDoc} */ @Override - public S1Point create(double a) { - return new S1Point(a); + public S1Point apply(double n) { - return S1Point.of(n); ++ return new S1Point(n); } }; @@@ -165,7 -167,7 +165,7 @@@ /** {@inheritDoc} */ @Override public String toString() { - return SimpleCoordinateFormat.getPointFormat().format(getAzimuth()); - return SimpleTupleFormat.getDefault().format(getAlpha()); ++ return SimpleTupleFormat.getDefault().format(getAzimuth()); } /** Creates a new point instance from the given azimuthal coordinate value. diff --cc commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java index 0b3b191,3fc8795..19cf154 --- 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 @@@ -19,9 -19,8 +19,9 @@@ package org.apache.commons.geometry.sph 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.core.internal.DoubleFunction2N; + import org.apache.commons.geometry.core.internal.SimpleTupleFormat; +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. @@@ -203,7 -209,7 +202,7 @@@ public final class S2Point implements P /** {@inheritDoc} */ @Override public String toString() { - return SimpleCoordinateFormat.getPointFormat().format(getAzimuth(), getPolar()); - return SimpleTupleFormat.getDefault().format(getTheta(), getPhi()); ++ return SimpleTupleFormat.getDefault().format(getAzimuth(), getPolar()); } /** Build a vector from its spherical coordinates diff --cc commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/S1PointTest.java index f1c0f40,ba81963..f67fc50 --- 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 @@@ -80,19 -79,8 +79,8 @@@ public class S1PointTest S1Point.parse("abc"); } - @Test - public void testGetFactory() { - // act - Coordinates.Factory1D<S1Point> factory = S1Point.getFactory(); - - // assert - checkPoint(factory.create(0), 0); - checkPoint(factory.create(1), 1); - checkPoint(factory.create(Geometry.TWO_PI), 0); - } - private void checkPoint(S1Point p, double alpha) { - Assert.assertEquals(alpha, p.getAlpha(), EPS); + Assert.assertEquals(alpha, p.getAzimuth(), EPS); } } diff --cc commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/S2PointTest.java index 462b0dd,2e15d09..827f107 --- 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 @@@ -87,18 -96,8 +86,8 @@@ public class S2PointTest S2Point.parse("abc"); } - @Test - public void testGetFactory() { - // act - Coordinates.Factory2D<S2Point> factory = S2Point.getFactory(); - - // assert - checkPoint(factory.create(0, 0), 0, 0); - checkPoint(factory.create(1, 2), 1, 2); - } - 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); } }
