You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by er...@apache.org on 2018/07/21 10:07:50 UTC

[commons-geometry] 12/15: GEOMETRY-7: merging polar and spherical code with latest from master

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 <ma...@hotmail.com>
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(&theta;) sin(&Phi;)
 + * y = r sin(&theta;) sin(&Phi;)
 + * z = r cos(&Phi;)
 + *
 + * r = &radic;(x<sup>2</sup>+y<sup>2</sup>+z<sup>2</sup>)
 + * &theta; = atan2(y, x)
 + * &Phi; = acos(z/r)
 + * </pre>
 + * where <em>r</em> is the radius, <em>&theta;</em> is the azimuth angle, and <em>&Phi;</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, &theta;, &Phi;)</em> to represent radius, azimuth angle, and
 + * polar angle, whereas the physics convention flips the angle values and uses <em>(r, &Phi;, &theta;)</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(&theta;)
 + * y = r * sin(&theta;)
 + *
 + * r = &radic;(x<sup>2</sup> + y<sup>2</sup>)
 + * &theta; = atan2(y, x)
 + * </pre>
 + * where <em>r</em> is the radius and <em>&theta;</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);
      }
  }