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/06 22:41:50 UTC

[commons-geometry] 03/08: GEOMETRY-3: adding euclidean parse methods; converting S1Point and S2Point to valjos; addressing some checkstyle issues

This is an automated email from the ASF dual-hosted git repository.

erans pushed a commit to branch GEOMETRY-3__TBR
in repository https://gitbox.apache.org/repos/asf/commons-geometry.git

commit aaf6960e0d7ac9b9cfd616c3e8e4dedaaf080bd6
Author: Matt Juntunen <ma...@hotmail.com>
AuthorDate: Sat Jun 2 23:12:40 2018 -0400

    GEOMETRY-3: adding euclidean parse methods; converting S1Point and S2Point to valjos; addressing some checkstyle issues
---
 .../core/util/AbstractCoordinateParser.java        |  46 +++++---
 .../commons/geometry/core/util/Coordinates.java    |  30 ++---
 .../geometry/core/util/SimpleCoordinateFormat.java |  61 +++++++---
 .../commons/geometry/core/util/package-info.java   |  23 ++++
 .../core/util/SimpleCoordinateFormatTest.java      | 127 ++++++++++++--------
 .../commons/geometry/euclidean/oned/Point1D.java   |  31 ++++-
 .../commons/geometry/euclidean/oned/Vector1D.java  |  31 ++++-
 .../commons/geometry/euclidean/threed/Point3D.java |  31 ++++-
 .../geometry/euclidean/threed/Vector3D.java        |  33 +++++-
 .../commons/geometry/euclidean/twod/Point2D.java   |  31 ++++-
 .../commons/geometry/euclidean/twod/Vector2D.java  |  31 ++++-
 .../geometry/euclidean/oned/Point1DTest.java       |  32 +++++
 .../geometry/euclidean/oned/Vector1DTest.java      |  32 +++++
 .../geometry/euclidean/threed/Point3DTest.java     |  32 ++++-
 .../geometry/euclidean/threed/Vector3DTest.java    |  33 +++++-
 .../geometry/euclidean/twod/Point2DTest.java       |  32 ++++-
 .../geometry/euclidean/twod/Vector2DTest.java      |  33 +++++-
 .../commons/geometry/spherical/oned/ArcsSet.java   |  33 +++---
 .../commons/geometry/spherical/oned/S1Point.java   |  63 +++++++---
 .../commons/geometry/spherical/package-info.java   |  23 ++++
 .../commons/geometry/spherical/twod/Circle.java    |   7 +-
 .../commons/geometry/spherical/twod/Edge.java      |   6 +-
 .../geometry/spherical/twod/EdgesBuilder.java      |   4 +-
 .../spherical/twod/PropertiesComputer.java         |   2 +-
 .../commons/geometry/spherical/twod/S2Point.java   | 126 ++++++++++++--------
 .../spherical/twod/SphericalPolygonsSet.java       |  16 +--
 .../geometry/spherical/SphericalTestUtils.java     |   2 +-
 .../geometry/spherical/oned/ArcsSetTest.java       | 130 ++++++++++-----------
 .../geometry/spherical/oned/LimitAngleTest.java    |   2 +-
 .../geometry/spherical/oned/S1PointTest.java       |  56 +++++++--
 .../geometry/spherical/twod/CircleTest.java        |  34 +++---
 .../geometry/spherical/twod/S2PointTest.java       |  67 ++++++++---
 .../spherical/twod/SphericalPolygonsSetTest.java   | 106 ++++++++---------
 33 files changed, 984 insertions(+), 362 deletions(-)

diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/AbstractCoordinateParser.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/AbstractCoordinateParser.java
index ec906bc..ceb8527 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/AbstractCoordinateParser.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/AbstractCoordinateParser.java
@@ -114,7 +114,8 @@ public abstract class AbstractCoordinateParser {
             return value;
         }
         catch (NumberFormatException exc) {
-            throw new CoordinateParseException("Failed to parse number from string at index " + startIdx + ": " + substr, exc);
+            fail(String.format("unable to parse number from string \"%s\"", substr), str, pos, exc);
+            return 0.0; // for the compiler
         }
     }
 
@@ -133,8 +134,8 @@ public abstract class AbstractCoordinateParser {
         }
     }
 
-    /** Ends a parse operation by ensuring that all non-whitespace characters in the string have been parsed. An exception
-     * is thrown if extra content is found.
+    /** Ends a parse operation by ensuring that all non-whitespace characters in the string have been parsed. An
+     * exception is thrown if extra content is found.
      * @param str the string being parsed
      * @param pos the current parsing position
      * @throws IllegalArgumentException if extra non-whitespace content is found past the current parsing position
@@ -142,7 +143,7 @@ public abstract class AbstractCoordinateParser {
     protected void endParse(String str, ParsePosition pos) throws IllegalArgumentException {
         consumeWhitespace(str, pos);
         if (pos.getIndex() != str.length()) {
-            throw new CoordinateParseException("Failed to parse string: unexpected content at index " + pos.getIndex());
+            fail("unexpected content", str, pos);
         }
     }
 
@@ -209,11 +210,35 @@ public abstract class AbstractCoordinateParser {
             final int idx = pos.getIndex();
             final String actualSeq = str.substring(idx, Math.min(str.length(), idx + seq.length()));
 
-            throw new CoordinateParseException("Failed to parse string: expected \"" + seq +
-                    "\" but found \"" + actualSeq + "\" at index " + idx);
+            fail(String.format("expected \"%s\" but found \"%s\"", seq, actualSeq), str, pos);
         }
     }
 
+    /** Aborts the current parsing operation by throwing an {@link IllegalArgumentException} with an informative
+     * error message.
+     * @param msg the error message
+     * @param str the string being parsed
+     * @param pos the current parse position
+     * @throws IllegalArgumentException the exception signaling a parse failure
+     */
+    protected void fail(String msg, String str, ParsePosition pos) throws IllegalArgumentException {
+        fail(msg, str, pos, null);
+    }
+
+    /** Aborts the current parsing operation by throwing an {@link IllegalArgumentException} with an informative
+     * error message.
+     * @param msg the error message
+     * @param str the string being parsed
+     * @param pos the current parse position
+     * @param cause the original cause of the error
+     * @throws IllegalArgumentException the exception signaling a parse failure
+     */
+    protected void fail(String msg, String str, ParsePosition pos, Throwable cause) throws IllegalArgumentException {
+        String fullMsg = String.format("Failed to parse string \"%s\" at index %d: %s", str, pos.getIndex(), msg);
+
+        throw new CoordinateParseException(fullMsg, cause);
+    }
+
     /** Exception class for errors occurring during coordinate parsing.
      */
     private static class CoordinateParseException extends IllegalArgumentException {
@@ -222,17 +247,10 @@ public abstract class AbstractCoordinateParser {
         private static final long serialVersionUID = 1494716029613981959L;
 
         /** Simple constructor.
-         * @param msg the exception message.
-         */
-        public CoordinateParseException(String msg) {
-            super(msg);
-        }
-
-        /** Simple constructor with cause.
          * @param msg the exception message
          * @param cause the exception root cause
          */
-        public CoordinateParseException(String msg, Throwable cause) {
+        CoordinateParseException(String msg, Throwable cause) {
             super(msg, cause);
         }
     }
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/Coordinates.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/Coordinates.java
index fcafd4f..8ea6c77 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/Coordinates.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/Coordinates.java
@@ -20,43 +20,43 @@ package org.apache.commons.geometry.core.util;
  */
 public class Coordinates {
 
-    /** Interface for classes that create new instances of a type from a single coordinate value.
+    /** Interface for classes that create objects from a single coordinate value.
      * @param <T> The type created by this factory.
      */
-    public static interface Factory1D<T> {
+    public interface Factory1D<T> {
 
         /** Creates a new instance of type T from the given coordinate value.
-         * @param v the first coordinate value
+         * @param a the coordinate value
          * @return a new instance of type T
          */
-        T create(double v);
+        T create(double a);
     }
 
-    /** Interface for classes that create new instances of a type from two coordinate values.
+    /** Interface for classes that create objects from two coordinate values.
      * @param <T> The type created by this factory.
      */
-    public static interface Factory2D<T> {
+    public interface Factory2D<T> {
 
         /** Creates a new instance of type T from the given coordinate values.
-         * @param v1 the first coordinate value
-         * @param v2 the second coordinate value
+         * @param a1 the first coordinate value
+         * @param a2 the second coordinate value
          * @return a new instance of type T
          */
-        T create(double v1, double v2);
+        T create(double a1, double a2);
     }
 
-    /** Interface for classes that create new instances of a type from three coordinate values.
+    /** Interface for classes that create objects from three coordinate values.
      * @param <T> The type created by this factory.
      */
-    public static interface Factory3D<T> {
+    public interface Factory3D<T> {
 
         /** Creates a new instance of type T from the given coordinate values.
-         * @param v1 the first coordinate value
-         * @param v2 the second coordinate value
-         * @param v3 the third coordinate value
+         * @param a1 the first coordinate value
+         * @param a2 the second coordinate value
+         * @param a3 the third coordinate value
          * @return a new instance of type T
          */
-        T create(double v1, double v2, double v3);
+        T create(double a1, double a2, double a3);
     }
 
     /** Private constructor. */
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/SimpleCoordinateFormat.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/SimpleCoordinateFormat.java
index fd66e45..6a4eb09 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/SimpleCoordinateFormat.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/SimpleCoordinateFormat.java
@@ -28,6 +28,14 @@ public class SimpleCoordinateFormat extends AbstractCoordinateParser {
     /** Space character */
     private static final String SPACE = " ";
 
+    /** Static instance configured with default values for working with points. */
+    private static final SimpleCoordinateFormat DEFAULT_POINT_FORMAT =
+            new SimpleCoordinateFormat(DEFAULT_SEPARATOR, "(", ")");
+
+    /** Static instance configured with default values for working with vectors. */
+    private static final SimpleCoordinateFormat DEFAULT_VECTOR_FORMAT =
+            new SimpleCoordinateFormat(DEFAULT_SEPARATOR, "{", "}");
+
     /** Creates a new format instance with the default separator value and the given
      * tuple prefix and suffix.
      * @param prefix coordinate tuple prefix; may be null
@@ -47,17 +55,17 @@ public class SimpleCoordinateFormat extends AbstractCoordinateParser {
     }
 
     /** Returns a 1D coordinate tuple string with the given value.
-     * @param v coordinate value
+     * @param a coordinate value
      * @return 1D coordinate tuple string
      */
-    public String format1D(double v) {
+    public String format(double a) {
         StringBuilder sb = new StringBuilder();
 
         if (getPrefix() != null) {
             sb.append(getPrefix());
         }
 
-        sb.append(v);
+        sb.append(a);
 
         if (getSuffix() != null) {
             sb.append(getSuffix());
@@ -67,21 +75,21 @@ public class SimpleCoordinateFormat extends AbstractCoordinateParser {
     }
 
     /** Returns a 2D coordinate tuple string with the given values.
-     * @param v1 first coordinate value
-     * @param v2 second coordinate value
+     * @param a1 first coordinate value
+     * @param a2 second coordinate value
      * @return 2D coordinate tuple string
      */
-    public String format2D(double v1, double v2) {
+    public String format(double a1, double a2) {
         StringBuilder sb = new StringBuilder();
 
         if (getPrefix() != null) {
             sb.append(getPrefix());
         }
 
-        sb.append(v1);
+        sb.append(a1);
         sb.append(getSeparator());
         sb.append(SPACE);
-        sb.append(v2);
+        sb.append(a2);
 
         if (getSuffix() != null) {
             sb.append(getSuffix());
@@ -91,25 +99,25 @@ public class SimpleCoordinateFormat extends AbstractCoordinateParser {
     }
 
     /** Returns a 3D coordinate tuple string with the given values.
-     * @param v1 first coordinate value
-     * @param v2 second coordinate value
-     * @param v3 third coordinate value
+     * @param a1 first coordinate value
+     * @param a2 second coordinate value
+     * @param a3 third coordinate value
      * @return 3D coordinate tuple string
      */
-    public String format3D(double v1, double v2, double v3) {
+    public String format(double a1, double a2, double a3) {
         StringBuilder sb = new StringBuilder();
 
         if (getPrefix() != null) {
             sb.append(getPrefix());
         }
 
-        sb.append(v1);
+        sb.append(a1);
         sb.append(getSeparator());
         sb.append(SPACE);
-        sb.append(v2);
+        sb.append(a2);
         sb.append(getSeparator());
         sb.append(SPACE);
-        sb.append(v3);
+        sb.append(a3);
 
         if (getSuffix() != null) {
             sb.append(getSuffix());
@@ -120,12 +128,13 @@ public class SimpleCoordinateFormat extends AbstractCoordinateParser {
 
     /** Parses the given string as a 1D coordinate tuple and passes the coordinate value to the
      * given factory. The object created by the factory is returned.
+     * @param <T> The type created by {@code factory}
      * @param str the string to be parsed
      * @param factory object that will be passed the parsed coordinate value
      * @return object created by {@code factory}
      * @throws IllegalArgumentException if the input string format is invalid
      */
-    public <T> T parse1D(String str, Coordinates.Factory1D<T> factory) throws IllegalArgumentException {
+    public <T> T parse(String str, Coordinates.Factory1D<T> factory) throws IllegalArgumentException {
         final ParsePosition pos = new ParsePosition(0);
 
         readPrefix(str, pos);
@@ -138,12 +147,13 @@ public class SimpleCoordinateFormat extends AbstractCoordinateParser {
 
     /** Parses the given string as a 2D coordinate tuple and passes the coordinate values to the
      * given factory. The object created by the factory is returned.
+     * @param <T> The type created by {@code factory}
      * @param str the string to be parsed
      * @param factory object that will be passed the parsed coordinate values
      * @return object created by {@code factory}
      * @throws IllegalArgumentException if the input string format is invalid
      */
-    public <T> T parse2D(String str, Coordinates.Factory2D<T> factory) throws IllegalArgumentException {
+    public <T> T parse(String str, Coordinates.Factory2D<T> factory) throws IllegalArgumentException {
         final ParsePosition pos = new ParsePosition(0);
 
         readPrefix(str, pos);
@@ -157,12 +167,13 @@ public class SimpleCoordinateFormat extends AbstractCoordinateParser {
 
     /** Parses the given string as a 3D coordinate tuple and passes the coordinate values to the
      * given factory. The object created by the factory is returned.
+     * @param <T> The type created by {@code factory}
      * @param str the string to be parsed
      * @param factory object that will be passed the parsed coordinate values
      * @return object created by {@code factory}
      * @throws IllegalArgumentException if the input string format is invalid
      */
-    public <T> T parse3D(String str, Coordinates.Factory3D<T> factory) throws IllegalArgumentException {
+    public <T> T parse(String str, Coordinates.Factory3D<T> factory) throws IllegalArgumentException {
         ParsePosition pos = new ParsePosition(0);
 
         readPrefix(str, pos);
@@ -174,4 +185,18 @@ public class SimpleCoordinateFormat extends AbstractCoordinateParser {
 
         return factory.create(v1, v2, v3);
     }
+
+    /** Returns a default instance for working with points.
+     * @return instance configured with default values for points
+     */
+    public static SimpleCoordinateFormat getPointFormat() {
+        return DEFAULT_POINT_FORMAT;
+    }
+
+    /** Returns a default instance for working with vectors.
+     * @return instance configured with default values for vectors.
+     */
+    public static SimpleCoordinateFormat getVectorFormat() {
+        return DEFAULT_VECTOR_FORMAT;
+    }
 }
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/package-info.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/package-info.java
new file mode 100644
index 0000000..526cca0
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ * This package contains common geometry utilities.
+ * </p>
+ */
+package org.apache.commons.geometry.core.util;
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/util/SimpleCoordinateFormatTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/util/SimpleCoordinateFormatTest.java
index 820d3f6..23202d7 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/util/SimpleCoordinateFormatTest.java
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/util/SimpleCoordinateFormatTest.java
@@ -23,7 +23,6 @@ public class SimpleCoordinateFormatTest {
 
     private static final double EPS = 1e-10;
 
-    private static final String COMMA = ",";
     private static final String OPEN_PAREN = "(";
     private static final String CLOSE_PAREN = ")";
 
@@ -80,7 +79,7 @@ public class SimpleCoordinateFormatTest {
         SimpleCoordinateFormat formatter = new SimpleCoordinateFormat("{", "}");
 
         // assert
-        Assert.assertEquals(COMMA, formatter.getSeparator());
+        Assert.assertEquals(",", formatter.getSeparator());
         Assert.assertEquals("{", formatter.getPrefix());
         Assert.assertEquals("}", formatter.getSuffix());
     }
@@ -91,11 +90,11 @@ public class SimpleCoordinateFormatTest {
         SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(OPEN_PAREN, CLOSE_PAREN);
 
         // act/assert
-        Assert.assertEquals("(1.0)", formatter.format1D(1.0));
-        Assert.assertEquals("(-1.0)", formatter.format1D(-1.0));
-        Assert.assertEquals("(NaN)", formatter.format1D(Double.NaN));
-        Assert.assertEquals("(-Infinity)", formatter.format1D(Double.NEGATIVE_INFINITY));
-        Assert.assertEquals("(Infinity)", formatter.format1D(Double.POSITIVE_INFINITY));
+        Assert.assertEquals("(1.0)", formatter.format(1.0));
+        Assert.assertEquals("(-1.0)", formatter.format(-1.0));
+        Assert.assertEquals("(NaN)", formatter.format(Double.NaN));
+        Assert.assertEquals("(-Infinity)", formatter.format(Double.NEGATIVE_INFINITY));
+        Assert.assertEquals("(Infinity)", formatter.format(Double.POSITIVE_INFINITY));
     }
 
     @Test
@@ -104,11 +103,11 @@ public class SimpleCoordinateFormatTest {
         SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(null, null);
 
         // act/assert
-        Assert.assertEquals("1.0", formatter.format1D(1.0));
-        Assert.assertEquals("-1.0", formatter.format1D(-1.0));
-        Assert.assertEquals("NaN", formatter.format1D(Double.NaN));
-        Assert.assertEquals("-Infinity", formatter.format1D(Double.NEGATIVE_INFINITY));
-        Assert.assertEquals("Infinity", formatter.format1D(Double.POSITIVE_INFINITY));
+        Assert.assertEquals("1.0", formatter.format(1.0));
+        Assert.assertEquals("-1.0", formatter.format(-1.0));
+        Assert.assertEquals("NaN", formatter.format(Double.NaN));
+        Assert.assertEquals("-Infinity", formatter.format(Double.NEGATIVE_INFINITY));
+        Assert.assertEquals("Infinity", formatter.format(Double.POSITIVE_INFINITY));
     }
 
     @Test
@@ -117,10 +116,10 @@ public class SimpleCoordinateFormatTest {
         SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(OPEN_PAREN, CLOSE_PAREN);
 
         // act/assert
-        Assert.assertEquals("(1.0, -1.0)", formatter.format2D(1.0, -1.0));
-        Assert.assertEquals("(-1.0, 1.0)", formatter.format2D(-1.0, 1.0));
-        Assert.assertEquals("(NaN, -Infinity)", formatter.format2D(Double.NaN, Double.NEGATIVE_INFINITY));
-        Assert.assertEquals("(-Infinity, Infinity)", formatter.format2D(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
+        Assert.assertEquals("(1.0, -1.0)", formatter.format(1.0, -1.0));
+        Assert.assertEquals("(-1.0, 1.0)", formatter.format(-1.0, 1.0));
+        Assert.assertEquals("(NaN, -Infinity)", formatter.format(Double.NaN, Double.NEGATIVE_INFINITY));
+        Assert.assertEquals("(-Infinity, Infinity)", formatter.format(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
     }
 
     @Test
@@ -129,10 +128,10 @@ public class SimpleCoordinateFormatTest {
         SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(null, null);
 
         // act/assert
-        Assert.assertEquals("1.0, -1.0", formatter.format2D(1.0, -1.0));
-        Assert.assertEquals("-1.0, 1.0", formatter.format2D(-1.0, 1.0));
-        Assert.assertEquals("NaN, -Infinity", formatter.format2D(Double.NaN, Double.NEGATIVE_INFINITY));
-        Assert.assertEquals("-Infinity, Infinity", formatter.format2D(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
+        Assert.assertEquals("1.0, -1.0", formatter.format(1.0, -1.0));
+        Assert.assertEquals("-1.0, 1.0", formatter.format(-1.0, 1.0));
+        Assert.assertEquals("NaN, -Infinity", formatter.format(Double.NaN, Double.NEGATIVE_INFINITY));
+        Assert.assertEquals("-Infinity, Infinity", formatter.format(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
     }
 
     @Test
@@ -141,9 +140,9 @@ public class SimpleCoordinateFormatTest {
         SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(OPEN_PAREN, CLOSE_PAREN);
 
         // act/assert
-        Assert.assertEquals("(1.0, 0.0, -1.0)", formatter.format3D(1.0, 0.0, -1.0));
-        Assert.assertEquals("(-1.0, 1.0, 0.0)", formatter.format3D(-1.0, 1.0, 0.0));
-        Assert.assertEquals("(NaN, -Infinity, Infinity)", formatter.format3D(Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
+        Assert.assertEquals("(1.0, 0.0, -1.0)", formatter.format(1.0, 0.0, -1.0));
+        Assert.assertEquals("(-1.0, 1.0, 0.0)", formatter.format(-1.0, 1.0, 0.0));
+        Assert.assertEquals("(NaN, -Infinity, Infinity)", formatter.format(Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
     }
 
     @Test
@@ -152,9 +151,9 @@ public class SimpleCoordinateFormatTest {
         SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(null, null);
 
         // act/assert
-        Assert.assertEquals("1.0, 0.0, -1.0", formatter.format3D(1.0, 0.0, -1.0));
-        Assert.assertEquals("-1.0, 1.0, 0.0", formatter.format3D(-1.0, 1.0, 0.0));
-        Assert.assertEquals("NaN, -Infinity, Infinity", formatter.format3D(Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
+        Assert.assertEquals("1.0, 0.0, -1.0", formatter.format(1.0, 0.0, -1.0));
+        Assert.assertEquals("-1.0, 1.0, 0.0", formatter.format(-1.0, 1.0, 0.0));
+        Assert.assertEquals("NaN, -Infinity, Infinity", formatter.format(Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
     }
 
     @Test
@@ -163,9 +162,9 @@ public class SimpleCoordinateFormatTest {
         SimpleCoordinateFormat formatter = new SimpleCoordinateFormat("||", "<<", ">>");
 
         // act/assert
-        Assert.assertEquals("<<1.0>>", formatter.format1D(1.0));
-        Assert.assertEquals("<<1.0|| 2.0>>", formatter.format2D(1.0, 2.0));
-        Assert.assertEquals("<<1.0|| 2.0|| 3.0>>", formatter.format3D(1.0, 2.0, 3.0));
+        Assert.assertEquals("<<1.0>>", formatter.format(1.0));
+        Assert.assertEquals("<<1.0|| 2.0>>", formatter.format(1.0, 2.0));
+        Assert.assertEquals("<<1.0|| 2.0|| 3.0>>", formatter.format(1.0, 2.0, 3.0));
     }
 
     @Test
@@ -226,12 +225,12 @@ public class SimpleCoordinateFormatTest {
         SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(OPEN_PAREN, CLOSE_PAREN);
 
         // act/assert
-        checkParse1DFailure(formatter, "", "expected \"(\" but found \"\" at index 0");
-        checkParse1DFailure(formatter, "(1 ", "expected \")\" but found \"\" at index 3");
+        checkParse1DFailure(formatter, "", "index 0: expected \"(\" but found \"\"");
+        checkParse1DFailure(formatter, "(1 ", "index 3: expected \")\" but found \"\"");
 
-        checkParse1DFailure(formatter, "(abc)", "Failed to parse number from string at index 1: abc");
+        checkParse1DFailure(formatter, "(abc)", "unable to parse number from string \"abc\"");
 
-        checkParse1DFailure(formatter, "(1) 1", "unexpected content at index 4");
+        checkParse1DFailure(formatter, "(1) 1", "index 4: unexpected content");
     }
 
     @Test
@@ -288,12 +287,12 @@ public class SimpleCoordinateFormatTest {
         SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(OPEN_PAREN, CLOSE_PAREN);
 
         // act/assert
-        checkParse2DFailure(formatter, "", "expected \"(\" but found \"\" at index 0");
-        checkParse2DFailure(formatter, "(1, 2 ", "expected \")\" but found \"\" at index 6");
+        checkParse2DFailure(formatter, "", "index 0: expected \"(\" but found \"\"");
+        checkParse2DFailure(formatter, "(1, 2 ", "index 6: expected \")\" but found \"\"");
 
-        checkParse2DFailure(formatter, "(0,abc)", "Failed to parse number from string at index 3: abc");
+        checkParse2DFailure(formatter, "(0,abc)", "index 3: unable to parse number from string \"abc\"");
 
-        checkParse2DFailure(formatter, "(1, 2) 1", "unexpected content at index 7");
+        checkParse2DFailure(formatter, "(1, 2) 1", "index 7: unexpected content");
     }
 
     @Test
@@ -348,12 +347,12 @@ public class SimpleCoordinateFormatTest {
         SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(OPEN_PAREN, CLOSE_PAREN);
 
         // act/assert
-        checkParse3DFailure(formatter, "", "expected \"(\" but found \"\" at index 0");
-        checkParse3DFailure(formatter, "(1, 2, 3", "expected \")\" but found \"\" at index 8");
+        checkParse3DFailure(formatter, "", "index 0: expected \"(\" but found \"\"");
+        checkParse3DFailure(formatter, "(1, 2, 3", "index 8: expected \")\" but found \"\"");
 
-        checkParse3DFailure(formatter, "(0,0,abc)", "Failed to parse number from string at index 5: abc");
+        checkParse3DFailure(formatter, "(0,0,abc)", "index 5: unable to parse number from string \"abc\"");
 
-        checkParse3DFailure(formatter, "(1, 2, 3) 1", "unexpected content at index 10");
+        checkParse3DFailure(formatter, "(1, 2, 3) 1", "index 10: unexpected content");
     }
 
     @Test
@@ -373,21 +372,47 @@ public class SimpleCoordinateFormatTest {
         SimpleCoordinateFormat formatter = new SimpleCoordinateFormat("||", "<<", ">>");
 
         // act/assert
-        checkParse1DFailure(formatter, "<", "expected \"<<\" but found \"<\" at index 0");
-        checkParse1DFailure(formatter, "<1.0>>", "expected \"<<\" but found \"<1\" at index 0");
-        checkParse2DFailure(formatter, "<<1.0| 2.0>>", "Failed to parse number from string at index 2: 1.0| 2.0");
-        checkParse3DFailure(formatter, "<<1.0|| 2.0|| 3.0>", "Failed to parse number from string at index 13:  3.0>");
+        checkParse1DFailure(formatter, "<", "index 0: expected \"<<\" but found \"<\"");
+        checkParse1DFailure(formatter, "<1.0>>", "index 0: expected \"<<\" but found \"<1\"");
+        checkParse2DFailure(formatter, "<<1.0| 2.0>>", "index 2: unable to parse number from string \"1.0| 2.0\"");
+        checkParse3DFailure(formatter, "<<1.0|| 2.0|| 3.0>", "index 13: unable to parse number from string \" 3.0>\"");
+    }
+
+    @Test
+    public void testDefaultPointFormat() {
+        // act
+        SimpleCoordinateFormat formatter = SimpleCoordinateFormat.getPointFormat();
+
+        // assert
+        Assert.assertEquals(",", formatter.getSeparator());
+        Assert.assertEquals("(", formatter.getPrefix());
+        Assert.assertEquals(")", formatter.getSuffix());
+
+        Assert.assertEquals("(1.0, 2.0)", formatter.format(1, 2));
+    }
+
+    @Test
+    public void testDefaultVectorFormat() {
+        // act
+        SimpleCoordinateFormat formatter = SimpleCoordinateFormat.getVectorFormat();
+
+        // assert
+        Assert.assertEquals(",", formatter.getSeparator());
+        Assert.assertEquals("{", formatter.getPrefix());
+        Assert.assertEquals("}", formatter.getSuffix());
+
+        Assert.assertEquals("{1.0, 2.0}", formatter.format(1, 2));
     }
 
     private void checkParse1D(SimpleCoordinateFormat formatter, String str, double v) {
-        Stub1D result = formatter.parse1D(str, FACTORY_1D);
+        Stub1D result = formatter.parse(str, FACTORY_1D);
 
         Assert.assertEquals(v, result.v, EPS);
     }
 
     private void checkParse1DFailure(SimpleCoordinateFormat formatter, String str, String msgSubstr) {
         try {
-            formatter.parse1D(str, FACTORY_1D);
+            formatter.parse(str, FACTORY_1D);
             Assert.fail("Operation should have failed");
         }
         catch (IllegalArgumentException exc) {
@@ -398,7 +423,7 @@ public class SimpleCoordinateFormatTest {
     }
 
     private void checkParse2D(SimpleCoordinateFormat formatter, String str, double v1, double v2) {
-        Stub2D result = formatter.parse2D(str, FACTORY_2D);
+        Stub2D result = formatter.parse(str, FACTORY_2D);
 
         Assert.assertEquals(v1, result.v1, EPS);
         Assert.assertEquals(v2, result.v2, EPS);
@@ -406,7 +431,7 @@ public class SimpleCoordinateFormatTest {
 
     private void checkParse2DFailure(SimpleCoordinateFormat formatter, String str, String msgSubstr) {
         try {
-            formatter.parse2D(str, FACTORY_2D);
+            formatter.parse(str, FACTORY_2D);
             Assert.fail("Operation should have failed");
         }
         catch (IllegalArgumentException exc) {
@@ -417,7 +442,7 @@ public class SimpleCoordinateFormatTest {
     }
 
     private void checkParse3D(SimpleCoordinateFormat formatter, String str, double v1, double v2, double v3) {
-        Stub3D result = formatter.parse3D(str, FACTORY_3D);
+        Stub3D result = formatter.parse(str, FACTORY_3D);
 
         Assert.assertEquals(v1, result.v1, EPS);
         Assert.assertEquals(v2, result.v2, EPS);
@@ -426,7 +451,7 @@ public class SimpleCoordinateFormatTest {
 
     private void checkParse3DFailure(SimpleCoordinateFormat formatter, String str, String msgSubstr) {
         try {
-            formatter.parse3D(str, FACTORY_3D);
+            formatter.parse(str, FACTORY_3D);
             Assert.fail("Operation should have failed");
         }
         catch (IllegalArgumentException exc) {
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Point1D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Point1D.java
index 75f4cb8..c6e6910 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Point1D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Point1D.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.geometry.euclidean.oned;
 
+import org.apache.commons.geometry.core.util.Coordinates;
+import org.apache.commons.geometry.core.util.SimpleCoordinateFormat;
 import org.apache.commons.geometry.euclidean.EuclideanPoint;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
@@ -46,6 +48,16 @@ public final class Point1D extends Cartesian1D implements EuclideanPoint<Point1D
     /** Serializable UID. */
     private static final long serialVersionUID = 7556674948671647925L;
 
+    /** Factory for delegating instance creation. */
+    private static Coordinates.Factory1D<Point1D> FACTORY = new Coordinates.Factory1D<Point1D>() {
+
+        /** {@inheritDoc} */
+        @Override
+        public Point1D create(double a) {
+            return new Point1D(a);
+        }
+    };
+
     /** Simple constructor.
      * @param x abscissa (coordinate value)
      */
@@ -136,7 +148,7 @@ public final class Point1D extends Cartesian1D implements EuclideanPoint<Point1D
     /** {@inheritDoc} */
     @Override
     public String toString() {
-        return "(" + getX() + ")";
+        return SimpleCoordinateFormat.getPointFormat().format(getX());
     }
 
     /** Returns a point with the given coordinate value.
@@ -155,6 +167,23 @@ public final class Point1D extends Cartesian1D implements EuclideanPoint<Point1D
         return new Point1D(value.getX());
     }
 
+    /** 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
+     * @return point instance represented by the string
+     * @throws IllegalArgumentException if the given string has an invalid format
+     */
+    public static Point1D parse(String str) throws IllegalArgumentException {
+        return SimpleCoordinateFormat.getPointFormat().parse(str, FACTORY);
+    }
+
+    /** Returns a factory object that can be used to created new point instances.
+     * @return point factory instance
+     */
+    public static Coordinates.Factory1D<Point1D> getFactory() {
+        return FACTORY;
+    }
+
     /** Returns a point with coordinates calculated by multiplying each input coordinate
      * with its corresponding factor and adding the results.
      *
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
index 8455441..5d79379 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.geometry.euclidean.oned;
 
+import org.apache.commons.geometry.core.util.Coordinates;
+import org.apache.commons.geometry.core.util.SimpleCoordinateFormat;
 import org.apache.commons.geometry.euclidean.EuclideanVector;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
@@ -46,6 +48,16 @@ public final class Vector1D extends Cartesian1D implements EuclideanVector<Point
     /** Serializable UID. */
     private static final long serialVersionUID = 1582116020164328846L;
 
+    /** Factory for delegating instance creation. */
+    private static Coordinates.Factory1D<Vector1D> FACTORY = new Coordinates.Factory1D<Vector1D>() {
+
+        /** {@inheritDoc} */
+        @Override
+        public Vector1D create(double a) {
+            return new Vector1D(a);
+        }
+    };
+
     /** Simple constructor.
      * @param x abscissa (coordinate value)
      */
@@ -220,7 +232,7 @@ public final class Vector1D extends Cartesian1D implements EuclideanVector<Point
     /** {@inheritDoc} */
     @Override
     public String toString() {
-        return "{" + getX() + "}";
+        return SimpleCoordinateFormat.getVectorFormat().format(getX());
     }
 
     /** Returns a vector with the given coordinate value.
@@ -239,6 +251,23 @@ public final class Vector1D extends Cartesian1D implements EuclideanVector<Point
         return new Vector1D(value.getX());
     }
 
+    /** 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
+     * @return vector instance represented by the string
+     * @throws IllegalArgumentException if the given string has an invalid format
+     */
+    public static Vector1D parse(String str) throws IllegalArgumentException {
+        return SimpleCoordinateFormat.getVectorFormat().parse(str, FACTORY);
+    }
+
+    /** Returns a factory object that can be used to created new vector instances.
+     * @return vector factory instance
+     */
+    public static Coordinates.Factory1D<Vector1D> getFactory() {
+        return FACTORY;
+    }
+
     /** Returns a vector consisting of the linear combination of the inputs.
      * <p>
      * A linear combination is the sum of all of the inputs multiplied by their
diff --git 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
index 05af242..2093292 100644
--- 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
@@ -17,6 +17,8 @@
 
 package org.apache.commons.geometry.euclidean.threed;
 
+import org.apache.commons.geometry.core.util.Coordinates;
+import org.apache.commons.geometry.core.util.SimpleCoordinateFormat;
 import org.apache.commons.geometry.euclidean.EuclideanPoint;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
@@ -44,6 +46,16 @@ public final class Point3D extends Cartesian3D implements EuclideanPoint<Point3D
     /** Serializable version identifier. */
     private static final long serialVersionUID = 1313493323784566947L;
 
+    /** Factory for delegating instance creation. */
+    private static Coordinates.Factory3D<Point3D> FACTORY = new Coordinates.Factory3D<Point3D>() {
+
+        /** {@inheritDoc} */
+        @Override
+        public Point3D create(double a1, double a2, double a3) {
+            return new Point3D(a1, a2, a3);
+        }
+    };
+
     /** Simple constructor.
      * Build a point from its coordinates
      * @param x abscissa
@@ -145,7 +157,7 @@ public final class Point3D extends Cartesian3D implements EuclideanPoint<Point3D
     /** {@inheritDoc} */
     @Override
     public String toString() {
-        return "(" + getX() + "; " + getY() + "; " + getZ() + ")";
+        return SimpleCoordinateFormat.getPointFormat().format(getX(), getY(), getZ());
     }
 
     /** Returns a point with the given coordinate values
@@ -178,6 +190,23 @@ public final class Point3D extends Cartesian3D implements EuclideanPoint<Point3D
         return new Point3D(p[0], p[1], p[2]);
     }
 
+    /** 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
+     * @return point instance represented by the string
+     * @throws IllegalArgumentException if the given string has an invalid format
+     */
+    public static Point3D parse(String str) throws IllegalArgumentException {
+        return SimpleCoordinateFormat.getPointFormat().parse(str, FACTORY);
+    }
+
+    /** Returns a factory object that can be used to created new point instances.
+     * @return point factory instance
+     */
+    public static Coordinates.Factory3D<Point3D> getFactory() {
+        return FACTORY;
+    }
+
     /** Returns a point with coordinates calculated by multiplying each input coordinate
      * with its corresponding factor and adding the results.
      *
diff --git 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
index f50816a..85246ab 100644
--- 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
@@ -16,13 +16,15 @@
  */
 package org.apache.commons.geometry.euclidean.threed;
 
+import org.apache.commons.geometry.core.util.Coordinates;
+import org.apache.commons.geometry.core.util.SimpleCoordinateFormat;
 import org.apache.commons.geometry.euclidean.EuclideanVector;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
 /** This class represents a vector in three-dimensional Euclidean space.
  * Instances of this class are guaranteed to be immutable.
  */
-public class Vector3D extends Cartesian3D implements EuclideanVector<Point3D, Vector3D> {
+public final class Vector3D extends Cartesian3D implements EuclideanVector<Point3D, Vector3D> {
 
     /** Zero (null) vector (coordinates: 0, 0, 0). */
     public static final Vector3D ZERO   = Vector3D.of(0, 0, 0);
@@ -64,6 +66,16 @@ public class Vector3D extends Cartesian3D implements EuclideanVector<Point3D, Ve
     /** 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>() {
+
+        /** {@inheritDoc} */
+        @Override
+        public Vector3D create(double a1, double a2, double a3) {
+            return new Vector3D(a1, a2, a3);
+        }
+    };
+
     /** Simple constructor.
      * Build a vector from its coordinates
      * @param x abscissa
@@ -373,7 +385,7 @@ public class Vector3D extends Cartesian3D implements EuclideanVector<Point3D, Ve
     /** {@inheritDoc} */
     @Override
     public String toString() {
-        return "{" + getX() + "; " + getY() + "; " + getZ() + "}";
+        return SimpleCoordinateFormat.getVectorFormat().format(getX(), getY(), getZ());
     }
 
     /** Computes the dot product between to vectors. This method simply
@@ -458,6 +470,23 @@ public class Vector3D extends Cartesian3D implements EuclideanVector<Point3D, Ve
         return new Vector3D(x, y, z);
     }
 
+    /** 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
+     * @return vector instance represented by the string
+     * @throws IllegalArgumentException if the given string has an invalid format
+     */
+    public static Vector3D parse(String str) throws IllegalArgumentException {
+        return SimpleCoordinateFormat.getVectorFormat().parse(str, FACTORY);
+    }
+
+    /** Returns a factory object that can be used to created new vector instances.
+     * @return vector factory instance
+     */
+    public static Coordinates.Factory3D<Vector3D> getFactory() {
+        return FACTORY;
+    }
+
     /** Returns a vector consisting of the linear combination of the inputs.
      * <p>
      * A linear combination is the sum of all of the inputs multiplied by their
diff --git 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
index 376d970..4175d11 100644
--- 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
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.geometry.euclidean.twod;
 
+import org.apache.commons.geometry.core.util.Coordinates;
+import org.apache.commons.geometry.core.util.SimpleCoordinateFormat;
 import org.apache.commons.geometry.euclidean.EuclideanPoint;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
@@ -43,6 +45,16 @@ public final class Point2D extends Cartesian2D implements EuclideanPoint<Point2D
     /** Serializable UID. */
     private static final long serialVersionUID = 266938651998679754L;
 
+    /** Factory for delegating instance creation. */
+    private static Coordinates.Factory2D<Point2D> FACTORY = new Coordinates.Factory2D<Point2D>() {
+
+        /** {@inheritDoc} */
+        @Override
+        public Point2D create(double a1, double a2) {
+            return new Point2D(a1, a2);
+        }
+    };
+
     /** Simple constructor.
      * Build a point from its coordinates
      * @param x abscissa
@@ -134,7 +146,7 @@ public final class Point2D extends Cartesian2D implements EuclideanPoint<Point2D
     /** {@inheritDoc} */
     @Override
     public String toString() {
-        return "(" + getX() + "; " + getY() + ")";
+        return SimpleCoordinateFormat.getPointFormat().format(getX(), getY());
     }
 
     /** Returns a point with the given coordinate values
@@ -166,6 +178,23 @@ public final class Point2D extends Cartesian2D implements EuclideanPoint<Point2D
         return new Point2D(p[0], p[1]);
     }
 
+    /** 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
+     * @return point instance represented by the string
+     * @throws IllegalArgumentException if the given string has an invalid format
+     */
+    public static Point2D parse(String str) throws IllegalArgumentException {
+        return SimpleCoordinateFormat.getPointFormat().parse(str, FACTORY);
+    }
+
+    /** Returns a factory object that can be used to created new point instances.
+     * @return point factory instance
+     */
+    public static Coordinates.Factory2D<Point2D> getFactory() {
+        return FACTORY;
+    }
+
     /** Returns a point with coordinates calculated by multiplying each input coordinate
      * with its corresponding factor and adding the results.
      *
diff --git 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
index 98ac4bc..e6285b1 100644
--- 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
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.geometry.euclidean.twod;
 
+import org.apache.commons.geometry.core.util.Coordinates;
+import org.apache.commons.geometry.core.util.SimpleCoordinateFormat;
 import org.apache.commons.geometry.euclidean.EuclideanVector;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
@@ -58,6 +60,16 @@ public final class Vector2D extends Cartesian2D implements EuclideanVector<Point
     /** 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>() {
+
+        /** {@inheritDoc} */
+        @Override
+        public Vector2D create(double a1, double a2) {
+            return new Vector2D(a1, a2);
+        }
+    };
+
     /** Simple constructor.
      * @param x abscissa (first coordinate)
      * @param y ordinate (second coordinate)
@@ -311,7 +323,7 @@ public final class Vector2D extends Cartesian2D implements EuclideanVector<Point
     /** {@inheritDoc} */
     @Override
     public String toString() {
-        return "{" + getX() + "; " + getY() + "}";
+        return SimpleCoordinateFormat.getVectorFormat().format(getX(), getY());
     }
 
     /** Computes the dot product between to vectors. This method simply
@@ -365,6 +377,23 @@ public final class Vector2D extends Cartesian2D implements EuclideanVector<Point
         return new Vector2D(v[0], v[1]);
     }
 
+    /** 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
+     * @return vector instance represented by the string
+     * @throws IllegalArgumentException if the given string has an invalid format
+     */
+    public static Vector2D parse(String str) throws IllegalArgumentException {
+        return SimpleCoordinateFormat.getVectorFormat().parse(str, FACTORY);
+    }
+
+    /** Returns a factory object that can be used to created new vector instances.
+     * @return vector factory instance
+     */
+    public static Coordinates.Factory2D<Vector2D> getFactory() {
+        return FACTORY;
+    }
+
     /** Returns a vector consisting of the linear combination of the inputs.
      * <p>
      * A linear combination is the sum of all of the inputs multiplied by their
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Point1DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Point1DTest.java
index f17af96..bcde081 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Point1DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Point1DTest.java
@@ -19,6 +19,7 @@ package org.apache.commons.geometry.euclidean.oned;
 
 import java.util.regex.Pattern;
 
+import org.apache.commons.geometry.core.util.Coordinates;
 import org.apache.commons.numbers.core.Precision;
 import org.junit.Assert;
 import org.junit.Test;
@@ -183,6 +184,27 @@ public class Point1DTest {
     }
 
     @Test
+    public void testParse() {
+        // act/assert
+        checkPoint(Point1D.parse("(1)"), 1);
+        checkPoint(Point1D.parse("(-1)"), -1);
+
+        checkPoint(Point1D.parse("(0.01)"), 1e-2);
+        checkPoint(Point1D.parse("(-1e-3)"), -1e-3);
+
+        checkPoint(Point1D.parse("(NaN)"), Double.NaN);
+
+        checkPoint(Point1D.parse(Point1D.ZERO.toString()), 0);
+        checkPoint(Point1D.parse(Point1D.ONE.toString()), 1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParse_failure() {
+        // act/assert
+        Point1D.parse("abc");
+    }
+
+    @Test
     public void testOf() {
         // act/assert
         checkPoint(Point1D.of(0), 0.0);
@@ -207,6 +229,16 @@ public class Point1DTest {
     }
 
     @Test
+    public void testGetFactory() {
+        // act
+        Coordinates.Factory1D<Point1D> factory = Point1D.getFactory();
+
+        // assert
+        checkPoint(factory.create(1), 1);
+        checkPoint(factory.create(-1), -1);
+    }
+
+    @Test
     public void testVectorCombination() {
         // act/assert
         checkPoint(Point1D.vectorCombination(2, Point1D.of(3)), 6);
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java
index fa757ab..7184476 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java
@@ -2,6 +2,7 @@ package org.apache.commons.geometry.euclidean.oned;
 
 import java.util.regex.Pattern;
 
+import org.apache.commons.geometry.core.util.Coordinates;
 import org.apache.commons.numbers.core.Precision;
 import org.junit.Assert;
 import org.junit.Test;
@@ -291,6 +292,27 @@ public class Vector1DTest {
     }
 
     @Test
+    public void testParse() {
+        // act/assert
+        checkVector(Vector1D.parse("{1}"), 1);
+        checkVector(Vector1D.parse("{-1}"), -1);
+
+        checkVector(Vector1D.parse("{0.01}"), 1e-2);
+        checkVector(Vector1D.parse("{-1e-3}"), -1e-3);
+
+        checkVector(Vector1D.parse("{NaN}"), Double.NaN);
+
+        checkVector(Vector1D.parse(Vector1D.ZERO.toString()), 0);
+        checkVector(Vector1D.parse(Vector1D.ONE.toString()), 1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParse_failure() {
+        // act/assert
+        Vector1D.parse("abc");
+    }
+
+    @Test
     public void testOf() {
         // act/assert
         checkVector(Vector1D.of(0), 0.0);
@@ -315,6 +337,16 @@ public class Vector1DTest {
     }
 
     @Test
+    public void testGetFactory() {
+        // act
+        Coordinates.Factory1D<Vector1D> factory = Vector1D.getFactory();
+
+        // assert
+        checkVector(factory.create(1), 1);
+        checkVector(factory.create(-1), -1);
+    }
+
+    @Test
     public void testLinearCombination() {
         // act/assert
         checkVector(Vector1D.linearCombination(2, Vector1D.of(3)), 6);
diff --git 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
index fa4cf3f..d20d918 100644
--- 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,6 +18,7 @@ package org.apache.commons.geometry.euclidean.threed;
 
 import java.util.regex.Pattern;
 
+import org.apache.commons.geometry.core.util.Coordinates;
 import org.apache.commons.numbers.core.Precision;
 import org.junit.Assert;
 import org.junit.Test;
@@ -173,7 +174,7 @@ public class Point3DTest {
     public void testToString() {
         // arrange
         Point3D p = Point3D.of(1, 2, 3);
-        Pattern pattern = Pattern.compile("\\(1.{0,2}; 2.{0,2}; 3.{0,2}\\)");
+        Pattern pattern = Pattern.compile("\\(1.{0,2}, 2.{0,2}, 3.{0,2}\\)");
 
         // act
         String str = p.toString();
@@ -184,6 +185,25 @@ public class Point3DTest {
     }
 
     @Test
+    public void testParse() {
+        // act/assert
+        checkPoint(Point3D.parse("(1, 2, 0)"), 1, 2, 0);
+        checkPoint(Point3D.parse("(-1, -2, 0)"), -1, -2, 0);
+
+        checkPoint(Point3D.parse("(0.01, -1e-3, 1e3)"), 1e-2, -1e-3, 1e3);
+
+        checkPoint(Point3D.parse("(NaN, -Infinity, Infinity)"), Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+
+        checkPoint(Point3D.parse(Point3D.ZERO.toString()), 0, 0, 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParse_failure() {
+        // act/assert
+        Point3D.parse("abc");
+    }
+
+    @Test
     public void testOf() {
         // act/assert
         checkPoint(Point3D.of(1, 2, 3), 1, 2, 3);
@@ -223,6 +243,16 @@ public class Point3DTest {
     }
 
     @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 --git 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
index f2b4fcc..c9b3cf9 100644
--- 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
@@ -20,6 +20,7 @@ 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.numbers.core.Precision;
 import org.apache.commons.rng.UniformRandomProvider;
 import org.apache.commons.rng.simple.RandomSource;
@@ -618,7 +619,7 @@ public class Vector3DTest {
     public void testToString() {
         // arrange
         Vector3D v = Vector3D.of(1, 2, 3);
-        Pattern pattern = Pattern.compile("\\{1.{0,2}; 2.{0,2}; 3.{0,2}\\}");
+        Pattern pattern = Pattern.compile("\\{1.{0,2}, 2.{0,2}, 3.{0,2}\\}");
 
         // act
         String str = v.toString();
@@ -629,6 +630,26 @@ public class Vector3DTest {
     }
 
     @Test
+    public void testParse() {
+        // act/assert
+        checkVector(Vector3D.parse("{1, 2, 3}"), 1, 2, 3);
+        checkVector(Vector3D.parse("{-1, -2, -3}"), -1, -2, -3);
+
+        checkVector(Vector3D.parse("{0.01, -1e-3, 0}"), 1e-2, -1e-3, 0);
+
+        checkVector(Vector3D.parse("{NaN, -Infinity, Infinity}"), Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+
+        checkVector(Vector3D.parse(Vector3D.ZERO.toString()), 0, 0, 0);
+        checkVector(Vector3D.parse(Vector3D.MINUS_X.toString()), -1, 0, 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParse_failure() {
+        // act/assert
+        Vector3D.parse("abc");
+    }
+
+    @Test
     public void testOf() {
         // act/assert
         checkVector(Vector3D.of(1, 2, 3), 1, 2, 3);
@@ -668,6 +689,16 @@ public class Vector3DTest {
     }
 
     @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 --git 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
index 6892198..20d95ad 100644
--- 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,6 +18,7 @@ package org.apache.commons.geometry.euclidean.twod;
 
 import java.util.regex.Pattern;
 
+import org.apache.commons.geometry.core.util.Coordinates;
 import org.apache.commons.numbers.core.Precision;
 import org.junit.Assert;
 import org.junit.Test;
@@ -156,7 +157,7 @@ public class Point2DTest {
     public void testToString() {
         // arrange
         Point2D p = Point2D.of(1, 2);
-        Pattern pattern = Pattern.compile("\\(1.{0,2}; 2.{0,2}\\)");
+        Pattern pattern = Pattern.compile("\\(1.{0,2}, 2.{0,2}\\)");
 
         // act
         String str = p.toString();
@@ -167,6 +168,25 @@ public class Point2DTest {
     }
 
     @Test
+    public void testParse() {
+        // act/assert
+        checkPoint(Point2D.parse("(1, 2)"), 1, 2);
+        checkPoint(Point2D.parse("(-1, -2)"), -1, -2);
+
+        checkPoint(Point2D.parse("(0.01, -1e-3)"), 1e-2, -1e-3);
+
+        checkPoint(Point2D.parse("(NaN, -Infinity)"), Double.NaN, Double.NEGATIVE_INFINITY);
+
+        checkPoint(Point2D.parse(Point2D.ZERO.toString()), 0, 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParse_failure() {
+        // act/assert
+        Point2D.parse("abc");
+    }
+
+    @Test
     public void testOf() {
         // act/assert
         checkPoint(Point2D.of(0, 1), 0, 1);
@@ -200,6 +220,16 @@ public class Point2DTest {
     }
 
     @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 --git 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
index 7882e7d..13208c0 100644
--- 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
@@ -3,6 +3,7 @@ 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.numbers.core.Precision;
 import org.junit.Assert;
 import org.junit.Test;
@@ -420,7 +421,7 @@ public class Vector2DTest {
     public void testToString() {
         // arrange
         Vector2D v = Vector2D.of(1, 2);
-        Pattern pattern = Pattern.compile("\\{1.{0,2}; 2.{0,2}\\}");
+        Pattern pattern = Pattern.compile("\\{1.{0,2}, 2.{0,2}\\}");
 
         // act
         String str = v.toString();
@@ -431,6 +432,26 @@ public class Vector2DTest {
     }
 
     @Test
+    public void testParse() {
+        // act/assert
+        checkVector(Vector2D.parse("{1, 2}"), 1, 2);
+        checkVector(Vector2D.parse("{-1, -2}"), -1, -2);
+
+        checkVector(Vector2D.parse("{0.01, -1e-3}"), 1e-2, -1e-3);
+
+        checkVector(Vector2D.parse("{NaN, -Infinity}"), Double.NaN, Double.NEGATIVE_INFINITY);
+
+        checkVector(Vector2D.parse(Vector2D.ZERO.toString()), 0, 0);
+        checkVector(Vector2D.parse(Vector2D.MINUS_X.toString()), -1, 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParse_failure() {
+        // act/assert
+        Vector2D.parse("abc");
+    }
+
+    @Test
     public void testOf() {
         // act/assert
         checkVector(Vector2D.of(0, 1), 0, 1);
@@ -464,6 +485,16 @@ public class Vector2DTest {
     }
 
     @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 --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java
index 2445857..676a88b 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java
@@ -42,6 +42,9 @@ import org.apache.commons.numbers.core.Precision;
  */
 public class ArcsSet extends AbstractRegion<S1Point, S1Point> implements Iterable<double[]> {
 
+    /** Message used for internal errors. */
+    private static final String INTERNAL_ERROR_MESSAGE = "Please file a bug report";
+
     /** Build an arcs set representing the whole circle.
      * @param tolerance tolerance below which close sub-arcs are merged together
      */
@@ -135,12 +138,12 @@ public class ArcsSet extends AbstractRegion<S1Point, S1Point> implements Iterabl
         final double normalizedLower = PlaneAngleRadians.normalizeBetweenZeroAndTwoPi(lower);
         final double normalizedUpper = normalizedLower + (upper - lower);
         final SubHyperplane<S1Point> lowerCut =
-                new LimitAngle(new S1Point(normalizedLower), false, tolerance).wholeHyperplane();
+                new LimitAngle(S1Point.of(normalizedLower), false, tolerance).wholeHyperplane();
 
         if (normalizedUpper <= Geometry.TWO_PI) {
             // simple arc starting after 0 and ending before 2 \pi
             final SubHyperplane<S1Point> upperCut =
-                    new LimitAngle(new S1Point(normalizedUpper), true, tolerance).wholeHyperplane();
+                    new LimitAngle(S1Point.of(normalizedUpper), true, tolerance).wholeHyperplane();
             return new BSPTree<>(lowerCut,
                                          new BSPTree<S1Point>(Boolean.FALSE),
                                          new BSPTree<>(upperCut,
@@ -151,7 +154,7 @@ public class ArcsSet extends AbstractRegion<S1Point, S1Point> implements Iterabl
         } else {
             // arc wrapping around 2 \pi
             final SubHyperplane<S1Point> upperCut =
-                    new LimitAngle(new S1Point(normalizedUpper - Geometry.TWO_PI), true, tolerance).wholeHyperplane();
+                    new LimitAngle(S1Point.of(normalizedUpper - Geometry.TWO_PI), true, tolerance).wholeHyperplane();
             return new BSPTree<>(lowerCut,
                                          new BSPTree<>(upperCut,
                                                                new BSPTree<S1Point>(Boolean.FALSE),
@@ -459,7 +462,7 @@ public class ArcsSet extends AbstractRegion<S1Point, S1Point> implements Iterabl
             if (Precision.equals(size, Geometry.TWO_PI, 0)) {
                 setBarycenter(S1Point.NaN);
             } else if (size >= Precision.SAFE_MIN) {
-                setBarycenter(new S1Point(sum / (2 * size)));
+                setBarycenter(S1Point.of(sum / (2 * size)));
             } else {
                 final LimitAngle limit = (LimitAngle) getTree(false).getCut().getHyperplane();
                 setBarycenter(limit.getLocation());
@@ -495,9 +498,9 @@ public class ArcsSet extends AbstractRegion<S1Point, S1Point> implements Iterabl
                         final double previousOffset = alpha - previous;
                         final double currentOffset  = a[0] - alpha;
                         if (previousOffset < currentOffset) {
-                            return new BoundaryProjection<>(point, new S1Point(previous), previousOffset);
+                            return new BoundaryProjection<>(point, S1Point.of(previous), previousOffset);
                         } else {
-                            return new BoundaryProjection<>(point, new S1Point(a[0]), currentOffset);
+                            return new BoundaryProjection<>(point, S1Point.of(a[0]), currentOffset);
                         }
                     }
                 } else if (alpha <= a[1]) {
@@ -506,9 +509,9 @@ public class ArcsSet extends AbstractRegion<S1Point, S1Point> implements Iterabl
                     final double offset0 = a[0] - alpha;
                     final double offset1 = alpha - a[1];
                     if (offset0 < offset1) {
-                        return new BoundaryProjection<>(point, new S1Point(a[1]), offset1);
+                        return new BoundaryProjection<>(point, S1Point.of(a[1]), offset1);
                     } else {
-                        return new BoundaryProjection<>(point, new S1Point(a[0]), offset0);
+                        return new BoundaryProjection<>(point, S1Point.of(a[0]), offset0);
                     }
                 }
             }
@@ -529,18 +532,18 @@ public class ArcsSet extends AbstractRegion<S1Point, S1Point> implements Iterabl
                 final double previousOffset = alpha - (previous - Geometry.TWO_PI);
                 final double currentOffset  = first - alpha;
                 if (previousOffset < currentOffset) {
-                    return new BoundaryProjection<>(point, new S1Point(previous), previousOffset);
+                    return new BoundaryProjection<>(point, S1Point.of(previous), previousOffset);
                 } else {
-                    return new BoundaryProjection<>(point, new S1Point(first), currentOffset);
+                    return new BoundaryProjection<>(point, S1Point.of(first), currentOffset);
                 }
             } else {
                 // the test point is between last and 2\pi
                 final double previousOffset = alpha - previous;
                 final double currentOffset  = first + Geometry.TWO_PI - alpha;
                 if (previousOffset < currentOffset) {
-                    return new BoundaryProjection<>(point, new S1Point(previous), previousOffset);
+                    return new BoundaryProjection<>(point, S1Point.of(previous), previousOffset);
                 } else {
-                    return new BoundaryProjection<>(point, new S1Point(first), currentOffset);
+                    return new BoundaryProjection<>(point, S1Point.of(first), currentOffset);
                 }
             }
 
@@ -652,7 +655,7 @@ public class ArcsSet extends AbstractRegion<S1Point, S1Point> implements Iterabl
                 }
                 if (end == null) {
                     // this should never happen
-                    throw new IllegalStateException("Please file a bug report");
+                    throw new IllegalStateException(INTERNAL_ERROR_MESSAGE);
                 }
 
                 // we have identified the last arc
@@ -787,11 +790,11 @@ public class ArcsSet extends AbstractRegion<S1Point, S1Point> implements Iterabl
      */
     private void addArcLimit(final BSPTree<S1Point> tree, final double alpha, final boolean isStart) {
 
-        final LimitAngle limit = new LimitAngle(new S1Point(alpha), !isStart, getTolerance());
+        final LimitAngle limit = new LimitAngle(S1Point.of(alpha), !isStart, getTolerance());
         final BSPTree<S1Point> node = tree.getCell(limit.getLocation(), getTolerance());
         if (node.getCut() != null) {
             // this should never happen
-            throw new IllegalStateException("Please file a bug report");
+            throw new IllegalStateException(INTERNAL_ERROR_MESSAGE);
         }
 
         node.insertCut(limit);
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java
index 8f7b19c..c2f6d86 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java
@@ -17,13 +17,15 @@
 package org.apache.commons.geometry.spherical.oned;
 
 import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.util.Coordinates;
+import org.apache.commons.geometry.core.util.SimpleCoordinateFormat;
 import org.apache.commons.geometry.euclidean.twod.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>
  */
-public class S1Point implements Point<S1Point> {
+public final class S1Point implements Point<S1Point> {
 
    // CHECKSTYLE: stop ConstantName
     /** A vector with all coordinates set to NaN. */
@@ -33,22 +35,22 @@ public class S1Point implements Point<S1Point> {
     /** Serializable UID. */
     private static final long serialVersionUID = 20131218L;
 
-    /** Azimuthal angle \( \alpha \). */
+    /** Factory for delegating instance creation. */
+    private static Coordinates.Factory1D<S1Point> FACTORY = new Coordinates.Factory1D<S1Point>() {
+
+        /** {@inheritDoc} */
+        @Override
+        public S1Point create(double a) {
+            return S1Point.of(a);
+        }
+    };
+
+    /** Azimuthal angle in radians \( \alpha \). */
     private final double alpha;
 
     /** Corresponding 2D normalized vector. */
     private final Vector2D vector;
 
-    /** Simple constructor.
-     * Build a vector from its coordinates
-     * @param alpha azimuthal angle \( \alpha \)
-     * @see #getAlpha()
-     */
-    public S1Point(final double alpha) {
-        this(PlaneAngleRadians.normalizeBetweenZeroAndTwoPi(alpha),
-             Vector2D.of(Math.cos(alpha), Math.sin(alpha)));
-    }
-
     /** Build a point from its internal components.
      * @param alpha azimuthal angle \( \alpha \)
      * @param vector corresponding vector
@@ -58,7 +60,7 @@ public class S1Point implements Point<S1Point> {
         this.vector = vector;
     }
 
-    /** Get the azimuthal angle \( \alpha \).
+    /** Get the azimuthal angle in radians \( \alpha \).
      * @return azimuthal angle \( \alpha \)
      * @see #S1Point(double)
      */
@@ -159,4 +161,39 @@ public class S1Point implements Point<S1Point> {
         }
         return 1759 * Double.hashCode(alpha);
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return SimpleCoordinateFormat.getPointFormat().format(getAlpha());
+    }
+
+    /** Creates a new point instance from the given azimuthal coordinate value.
+     * @param alpha azimuthal angle in radians \( \alpha \)
+     * @return point instance with the given azimuth coordinate value
+     * @see #getAlpha()
+     */
+    public static S1Point of(double alpha) {
+        double normalizedAlpha = PlaneAngleRadians.normalizeBetweenZeroAndTwoPi(alpha);
+        Vector2D vector = Vector2D.of(Math.cos(normalizedAlpha), Math.sin(normalizedAlpha));
+
+        return new S1Point(normalizedAlpha, vector);
+    }
+
+    /** 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
+     * @return point instance represented by the string
+     * @throws IllegalArgumentException if the given string has an invalid format
+     */
+    public static S1Point parse(String str) throws IllegalArgumentException {
+        return SimpleCoordinateFormat.getPointFormat().parse(str, FACTORY);
+    }
+
+    /** Returns a factory object that can be used to created new point instances.
+     * @return point factory instance
+     */
+    public static Coordinates.Factory1D<S1Point> getFactory() {
+        return FACTORY;
+    }
 }
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/package-info.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/package-info.java
new file mode 100644
index 0000000..020a968
--- /dev/null
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ * Base package for spherical geometry components.
+ * </p>
+ */
+package org.apache.commons.geometry.spherical;
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java
index 28d4e02..3b017ca 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java
@@ -16,7 +16,6 @@
  */
 package org.apache.commons.geometry.spherical.twod;
 
-import org.apache.commons.geometry.core.Point;
 import org.apache.commons.geometry.core.partitioning.Embedding;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
 import org.apache.commons.geometry.core.partitioning.SubHyperplane;
@@ -147,7 +146,7 @@ public class Circle implements Hyperplane<S2Point>, Embedding<S2Point, S1Point>
      */
     @Override
     public S1Point toSubSpace(final S2Point point) {
-        return new S1Point(getPhase(point.getVector()));
+        return S1Point.of(getPhase(point.getVector()));
     }
 
     /** Get the phase angle of a direction.
@@ -169,7 +168,7 @@ public class Circle implements Hyperplane<S2Point>, Embedding<S2Point, S1Point>
      */
     @Override
     public S2Point toSpace(final S1Point point) {
-        return new S2Point(getPointAt(point.getAlpha()));
+        return S2Point.of(getPointAt(point.getAlpha()));
     }
 
     /** Get a circle point from its phase around the circle.
@@ -307,7 +306,7 @@ public class Circle implements Hyperplane<S2Point>, Embedding<S2Point, S1Point>
         /** {@inheritDoc} */
         @Override
         public S2Point apply(final S2Point point) {
-            return new S2Point(rotation.applyTo(point.getVector()));
+            return S2Point.of(rotation.applyTo(point.getVector()));
         }
 
         /** {@inheritDoc} */
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Edge.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Edge.java
index 997d051..609408a 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Edge.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Edge.java
@@ -148,7 +148,7 @@ public class Edge {
             if (unwrappedEnd >= 0) {
                 // the start of the edge is inside the circle
                 previousVertex = addSubEdge(previousVertex,
-                                            new Vertex(new S2Point(circle.getPointAt(edgeStart + unwrappedEnd))),
+                                            new Vertex(S2Point.of(circle.getPointAt(edgeStart + unwrappedEnd))),
                                             unwrappedEnd, insideList, splitCircle);
                 alreadyManagedLength = unwrappedEnd;
             }
@@ -166,7 +166,7 @@ public class Edge {
             } else {
                 // the edge is long enough to enter inside the circle
                 previousVertex = addSubEdge(previousVertex,
-                                            new Vertex(new S2Point(circle.getPointAt(edgeStart + arcRelativeStart))),
+                                            new Vertex(S2Point.of(circle.getPointAt(edgeStart + arcRelativeStart))),
                                             arcRelativeStart - alreadyManagedLength, outsideList, splitCircle);
                 alreadyManagedLength = arcRelativeStart;
 
@@ -177,7 +177,7 @@ public class Edge {
                 } else {
                     // the edge is long enough to exit outside of the circle
                     previousVertex = addSubEdge(previousVertex,
-                                                new Vertex(new S2Point(circle.getPointAt(edgeStart + arcRelativeStart))),
+                                                new Vertex(S2Point.of(circle.getPointAt(edgeStart + arcRelativeStart))),
                                                 arcRelativeStart - alreadyManagedLength, insideList, splitCircle);
                     alreadyManagedLength = arcRelativeStart;
                     previousVertex = addSubEdge(previousVertex, end,
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EdgesBuilder.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EdgesBuilder.java
index 47b6d39..085f9c5 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EdgesBuilder.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EdgesBuilder.java
@@ -91,8 +91,8 @@ class EdgesBuilder implements BSPTreeVisitor<S2Point> {
         final Circle circle  = (Circle) sub.getHyperplane();
         final List<Arc> arcs = ((ArcsSet) sub.getRemainingRegion()).asList();
         for (final Arc a : arcs) {
-            final Vertex start = new Vertex(circle.toSpace(new S1Point(a.getInf())));
-            final Vertex end   = new Vertex(circle.toSpace(new S1Point(a.getSup())));
+            final Vertex start = new Vertex(circle.toSpace(S1Point.of(a.getInf())));
+            final Vertex end   = new Vertex(circle.toSpace(S1Point.of(a.getSup())));
             start.bindWith(circle);
             end.bindWith(circle);
             final Edge edge;
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java
index 2cbcb03..258b17e 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java
@@ -160,7 +160,7 @@ class PropertiesComputer implements BSPTreeVisitor<S2Point> {
         if (summedBarycenter.getNormSq() == 0) {
             return S2Point.NaN;
         } else {
-            return new S2Point(summedBarycenter);
+            return S2Point.of(summedBarycenter);
         }
     }
 
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java
index 567e666..a0c7499 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java
@@ -17,6 +17,8 @@
 package org.apache.commons.geometry.spherical.twod;
 
 import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.util.Coordinates;
+import org.apache.commons.geometry.core.util.SimpleCoordinateFormat;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
 
 /** This class represents a point on the 2-sphere.
@@ -29,7 +31,7 @@ import org.apache.commons.geometry.euclidean.threed.Vector3D;
  * </p>
  * <p>Instances of this class are guaranteed to be immutable.</p>
  */
-public class S2Point implements Point<S2Point> {
+public final class S2Point implements Point<S2Point> {
 
     /** +I (coordinates: \( \theta = 0, \varphi = \pi/2 \)). */
     public static final S2Point PLUS_I = new S2Point(0, 0.5 * Math.PI, Vector3D.PLUS_X);
@@ -57,6 +59,16 @@ public class S2Point implements Point<S2Point> {
     /** Serializable UID. */
     private static final long serialVersionUID = 20131218L;
 
+    /** Factory for delegating instance creation. */
+    private static Coordinates.Factory2D<S2Point> FACTORY = new Coordinates.Factory2D<S2Point>() {
+
+        /** {@inheritDoc} */
+        @Override
+        public S2Point create(double a1, double a2) {
+            return S2Point.of(a1, a2);
+        }
+    };
+
     /** Azimuthal angle \( \theta \) in the x-y plane. */
     private final double theta;
 
@@ -66,29 +78,6 @@ public class S2Point implements Point<S2Point> {
     /** Corresponding 3D normalized vector. */
     private final Vector3D vector;
 
-    /** Simple constructor.
-     * Build a vector from its spherical coordinates
-     * @param theta azimuthal angle \( \theta \) in the x-y plane
-     * @param phi polar angle \( \varphi \)
-     * @see #getTheta()
-     * @see #getPhi()
-     * @exception IllegalArgumentException if \( \varphi \) is not in the [\( 0; \pi \)] range
-     */
-    public S2Point(final double theta, final double phi)
-        throws IllegalArgumentException {
-        this(theta, phi, vector(theta, phi));
-    }
-
-    /** Simple constructor.
-     * Build a vector from its underlying 3D vector
-     * @param vector 3D vector
-     * @exception IllegalArgumentException if vector norm is zero
-     */
-    public S2Point(final Vector3D vector) throws IllegalArgumentException {
-        this(Math.atan2(vector.getY(), vector.getX()), Vector3D.PLUS_Z.angle(vector),
-             vector.normalize());
-    }
-
     /** Build a point from its internal components.
      * @param theta azimuthal angle \( \theta \) in the x-y plane
      * @param phi polar angle \( \varphi \)
@@ -100,28 +89,6 @@ public class S2Point implements Point<S2Point> {
         this.vector = vector;
     }
 
-    /** Build the normalized vector corresponding to spherical coordinates.
-     * @param theta azimuthal angle \( \theta \) in the x-y plane
-     * @param phi polar angle \( \varphi \)
-     * @return normalized vector
-     * @exception IllegalArgumentException if \( \varphi \) is not in the [\( 0; \pi \)] range
-     */
-    private static Vector3D vector(final double theta, final double phi)
-       throws IllegalArgumentException {
-
-        if (phi < 0 || phi > Math.PI) {
-            throw new IllegalArgumentException(phi + " is out of [" + 0 + ", " + Math.PI + "] range");
-        }
-
-        final double cosTheta = Math.cos(theta);
-        final double sinTheta = Math.sin(theta);
-        final double cosPhi   = Math.cos(phi);
-        final double sinPhi   = Math.sin(phi);
-
-        return Vector3D.of(cosTheta * sinPhi, sinTheta * sinPhi, cosPhi);
-
-    }
-
     /** Get the azimuthal angle \( \theta \) in the x-y plane.
      * @return azimuthal angle \( \theta \) in the x-y plane
      * @see #S2Point(double, double)
@@ -236,4 +203,71 @@ public class S2Point implements Point<S2Point> {
         }
         return 134 * (37 * Double.hashCode(theta) +  Double.hashCode(phi));
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return SimpleCoordinateFormat.getPointFormat().format(getTheta(), getPhi());
+    }
+
+    /** Build a vector from its spherical coordinates
+     * @param theta azimuthal angle \( \theta \) in the x-y plane
+     * @param phi polar angle \( \varphi \)
+     * @return point instance with the given coordinates
+     * @see #getTheta()
+     * @see #getPhi()
+     * @exception IllegalArgumentException if \( \varphi \) is not in the [\( 0; \pi \)] range
+     */
+    public static S2Point of(final double theta, final double phi) {
+        return new S2Point(theta, phi, vector(theta, phi));
+    }
+
+    /** Build a point from its underlying 3D vector
+     * @param vector 3D vector
+     * @return point instance with the coordinates determined by the given 3D vector
+     * @exception IllegalArgumentException if vector norm is zero
+     */
+    public static S2Point of(final Vector3D vector) {
+        return new S2Point(Math.atan2(vector.getY(), vector.getX()),
+                Vector3D.PLUS_Z.angle(vector),
+                vector.normalize());
+    }
+
+    /** Build the normalized vector corresponding to spherical coordinates.
+     * @param theta azimuthal angle \( \theta \) in the x-y plane
+     * @param phi polar angle \( \varphi \)
+     * @return normalized vector
+     * @exception IllegalArgumentException if \( \varphi \) is not in the [\( 0; \pi \)] range
+     */
+    private static Vector3D vector(final double theta, final double phi)
+       throws IllegalArgumentException {
+
+        if (phi < 0 || phi > Math.PI) {
+            throw new IllegalArgumentException(phi + " is out of [" + 0 + ", " + Math.PI + "] range");
+        }
+
+        final double cosTheta = Math.cos(theta);
+        final double sinTheta = Math.sin(theta);
+        final double cosPhi   = Math.cos(phi);
+        final double sinPhi   = Math.sin(phi);
+
+        return Vector3D.of(cosTheta * sinPhi, sinTheta * sinPhi, cosPhi);
+    }
+
+    /** 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
+     * @return point instance represented by the string
+     * @throws IllegalArgumentException if the given string has an invalid format
+     */
+    public static S2Point parse(String str) throws IllegalArgumentException {
+        return SimpleCoordinateFormat.getPointFormat().parse(str, FACTORY);
+    }
+
+    /** Returns a factory object that can be used to created new point instances.
+     * @return point factory instance
+     */
+    public static Coordinates.Factory2D<S2Point> getFactory() {
+        return FACTORY;
+    }
 }
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSet.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSet.java
index fd22d6b..d9e3eeb 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSet.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSet.java
@@ -161,11 +161,11 @@ public class SphericalPolygonsSet extends AbstractRegion<S2Point, S1Point> {
         final S2Point[] array = new S2Point[n];
         final Rotation r0 = new Rotation(Vector3D.crossProduct(center, meridian),
                                          outsideRadius, RotationConvention.VECTOR_OPERATOR);
-        array[0] = new S2Point(r0.applyTo(center));
+        array[0] = S2Point.of(r0.applyTo(center));
 
         final Rotation r = new Rotation(center, Geometry.TWO_PI / n, RotationConvention.VECTOR_OPERATOR);
         for (int i = 1; i < n; ++i) {
-            array[i] = new S2Point(r.applyTo(array[i - 1].getVector()));
+            array[i] = S2Point.of(r.applyTo(array[i - 1].getVector()));
         }
 
         return array;
@@ -325,7 +325,7 @@ public class SphericalPolygonsSet extends AbstractRegion<S2Point, S1Point> {
             if (tree.getCut() == null && (Boolean) tree.getAttribute()) {
                 // the instance covers the whole space
                 setSize(4 * Math.PI);
-                setBarycenter(new S2Point(0, 0));
+                setBarycenter(S2Point.of(0, 0));
             } else {
                 setSize(0);
                 setBarycenter(S2Point.NaN);
@@ -478,13 +478,13 @@ public class SphericalPolygonsSet extends AbstractRegion<S2Point, S1Point> {
         if (isEmpty(root.getMinus()) && isFull(root.getPlus())) {
             // the polygon covers an hemisphere, and its boundary is one 2π long edge
             final Circle circle = (Circle) root.getCut().getHyperplane();
-            return new EnclosingBall<>(new S2Point(circle.getPole()).negate(),
+            return new EnclosingBall<>(S2Point.of(circle.getPole()).negate(),
                                                         0.5 * Math.PI);
         }
         if (isFull(root.getMinus()) && isEmpty(root.getPlus())) {
             // the polygon covers an hemisphere, and its boundary is one 2π long edge
             final Circle circle = (Circle) root.getCut().getHyperplane();
-            return new EnclosingBall<>(new S2Point(circle.getPole()),
+            return new EnclosingBall<>(S2Point.of(circle.getPole()),
                                                         0.5 * Math.PI);
         }
 
@@ -517,7 +517,7 @@ public class SphericalPolygonsSet extends AbstractRegion<S2Point, S1Point> {
             EnclosingBall<S2Point> enclosingS2 =
                     new EnclosingBall<>(S2Point.PLUS_K, Double.POSITIVE_INFINITY);
             for (Point3D outsidePoint : getOutsidePoints()) {
-                final S2Point outsideS2 = new S2Point(outsidePoint.asVector());
+                final S2Point outsideS2 = S2Point.of(outsidePoint.asVector());
                 final BoundaryProjection<S2Point> projection = projectToBoundary(outsideS2);
                 if (Math.PI - projection.getOffset() < enclosingS2.getRadius()) {
                     enclosingS2 = new EnclosingBall<>(outsideS2.negate(),
@@ -529,11 +529,11 @@ public class SphericalPolygonsSet extends AbstractRegion<S2Point, S1Point> {
         }
         final S2Point[] support = new S2Point[support3D.length];
         for (int i = 0; i < support3D.length; ++i) {
-            support[i] = new S2Point(support3D[i].asVector());
+            support[i] = S2Point.of(support3D[i].asVector());
         }
 
         final EnclosingBall<S2Point> enclosingS2 =
-                new EnclosingBall<>(new S2Point(enclosing3D.getCenter().asVector()),
+                new EnclosingBall<>(S2Point.of(enclosing3D.getCenter().asVector()),
                                                      Math.acos((1 + h * h - r * r) / (2 * h)),
                                                      support);
 
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/SphericalTestUtils.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/SphericalTestUtils.java
index b5a1211..a43bc3d 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/SphericalTestUtils.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/SphericalTestUtils.java
@@ -89,7 +89,7 @@ public class SphericalTestUtils {
             @Override
             protected LimitAngle parseHyperplane()
                 throws IOException, ParseException {
-                return new LimitAngle(new S1Point(getNumber()), getBoolean(), getNumber());
+                return new LimitAngle(S1Point.of(getNumber()), getBoolean(), getNumber());
             }
 
         };
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/ArcsSetTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/ArcsSetTest.java
index 3b59551..a6bab1a 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/ArcsSetTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/ArcsSetTest.java
@@ -39,12 +39,12 @@ public class ArcsSetTest {
         ArcsSet set = new ArcsSet(2.3, 5.7, 1.0e-10);
         Assert.assertEquals(3.4, set.getSize(), 1.0e-10);
         Assert.assertEquals(1.0e-10, set.getTolerance(), 1.0e-20);
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new S1Point(2.3)));
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new S1Point(5.7)));
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new S1Point(1.2)));
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new S1Point(8.5)));
-        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(new S1Point(8.7)));
-        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(new S1Point(3.0)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(S1Point.of(2.3)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(S1Point.of(5.7)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(S1Point.of(1.2)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(S1Point.of(8.5)));
+        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(S1Point.of(8.7)));
+        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(S1Point.of(3.0)));
         Assert.assertEquals(1, set.asList().size());
         Assert.assertEquals(2.3, set.asList().get(0).getInf(), 1.0e-10);
         Assert.assertEquals(5.7, set.asList().get(0).getSup(), 1.0e-10);
@@ -55,12 +55,12 @@ public class ArcsSetTest {
         ArcsSet set = new ArcsSet(5.7 - Geometry.TWO_PI, 2.3, 1.0e-10);
         Assert.assertEquals(Geometry.TWO_PI - 3.4, set.getSize(), 1.0e-10);
         Assert.assertEquals(1.0e-10, set.getTolerance(), 1.0e-20);
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new S1Point(2.3)));
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new S1Point(5.7)));
-        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(new S1Point(1.2)));
-        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(new S1Point(8.5)));
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new S1Point(8.7)));
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new S1Point(3.0)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(S1Point.of(2.3)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(S1Point.of(5.7)));
+        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(S1Point.of(1.2)));
+        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(S1Point.of(8.5)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(S1Point.of(8.7)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(S1Point.of(3.0)));
         Assert.assertEquals(1, set.asList().size());
         Assert.assertEquals(5.7, set.asList().get(0).getInf(), 1.0e-10);
         Assert.assertEquals(2.3 + Geometry.TWO_PI, set.asList().get(0).getSup(), 1.0e-10);
@@ -72,7 +72,7 @@ public class ArcsSetTest {
         Arc     arc = new Arc(1.5 * Math.PI, 2.5 * Math.PI, 1.0e-10);
         ArcsSet.Split split = set.split(arc);
         for (double alpha = 0.0; alpha <= Geometry.TWO_PI; alpha += 0.01) {
-            S1Point p = new S1Point(alpha);
+            S1Point p = S1Point.of(alpha);
             if (alpha < 0.5 * Math.PI || alpha > 1.5 * Math.PI) {
                 Assert.assertEquals(Location.OUTSIDE, split.getPlus().checkPoint(p));
                 Assert.assertEquals(Location.INSIDE,  split.getMinus().checkPoint(p));
@@ -89,7 +89,7 @@ public class ArcsSetTest {
         Arc     arc = new Arc(Math.PI, Geometry.TWO_PI, 1.0e-10);
         ArcsSet.Split split = set.split(arc);
         for (double alpha = 0.01; alpha < Geometry.TWO_PI; alpha += 0.01) {
-            S1Point p = new S1Point(alpha);
+            S1Point p = S1Point.of(alpha);
             if (alpha > Math.PI) {
                 Assert.assertEquals(Location.OUTSIDE, split.getPlus().checkPoint(p));
                 Assert.assertEquals(Location.INSIDE,  split.getMinus().checkPoint(p));
@@ -99,11 +99,11 @@ public class ArcsSetTest {
             }
         }
 
-        S1Point zero = new S1Point(0.0);
+        S1Point zero = S1Point.of(0.0);
         Assert.assertEquals(Location.BOUNDARY,  split.getPlus().checkPoint(zero));
         Assert.assertEquals(Location.BOUNDARY,  split.getMinus().checkPoint(zero));
 
-        S1Point pi = new S1Point(Math.PI);
+        S1Point pi = S1Point.of(Math.PI);
         Assert.assertEquals(Location.BOUNDARY,  split.getPlus().checkPoint(pi));
         Assert.assertEquals(Location.BOUNDARY,  split.getMinus().checkPoint(pi));
 
@@ -118,9 +118,9 @@ public class ArcsSetTest {
     public void testFullEqualEndPoints() {
         ArcsSet set = new ArcsSet(1.0, 1.0, 1.0e-10);
         Assert.assertEquals(1.0e-10, set.getTolerance(), 1.0e-20);
-        Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(new S1Point(9.0)));
+        Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(S1Point.of(9.0)));
         for (double alpha = -20.0; alpha <= 20.0; alpha += 0.1) {
-            Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(new S1Point(alpha)));
+            Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(S1Point.of(alpha)));
         }
         Assert.assertEquals(1, set.asList().size());
         Assert.assertEquals(0.0, set.asList().get(0).getInf(), 1.0e-10);
@@ -132,9 +132,9 @@ public class ArcsSetTest {
     public void testFullCircle() {
         ArcsSet set = new ArcsSet(1.0e-10);
         Assert.assertEquals(1.0e-10, set.getTolerance(), 1.0e-20);
-        Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(new S1Point(9.0)));
+        Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(S1Point.of(9.0)));
         for (double alpha = -20.0; alpha <= 20.0; alpha += 0.1) {
-            Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(new S1Point(alpha)));
+            Assert.assertEquals(Region.Location.INSIDE, set.checkPoint(S1Point.of(alpha)));
         }
         Assert.assertEquals(1, set.asList().size());
         Assert.assertEquals(0.0, set.asList().get(0).getInf(), 1.0e-10);
@@ -163,8 +163,8 @@ public class ArcsSetTest {
     @Test
     public void testSpecialConstruction() {
         List<SubHyperplane<S1Point>> boundary = new ArrayList<>();
-        boundary.add(new LimitAngle(new S1Point(0.0), false, 1.0e-10).wholeHyperplane());
-        boundary.add(new LimitAngle(new S1Point(Geometry.TWO_PI - 1.0e-11), true, 1.0e-10).wholeHyperplane());
+        boundary.add(new LimitAngle(S1Point.of(0.0), false, 1.0e-10).wholeHyperplane());
+        boundary.add(new LimitAngle(S1Point.of(Geometry.TWO_PI - 1.0e-11), true, 1.0e-10).wholeHyperplane());
         ArcsSet set = new ArcsSet(boundary, 1.0e-10);
         Assert.assertEquals(Geometry.TWO_PI, set.getSize(), 1.0e-10);
         Assert.assertEquals(1.0e-10, set.getTolerance(), 1.0e-20);
@@ -190,20 +190,20 @@ public class ArcsSetTest {
 
         ArcsSet aMb = (ArcsSet) new RegionFactory<S1Point>().difference(a, b);
         for (int k = -2; k < 3; ++k) {
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(0.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(0.9 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(new S1Point(1.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(new S1Point(1.1 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(new S1Point(2.9 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(new S1Point(3.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(3.1 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(4.9 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(new S1Point(5.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(new S1Point(5.1 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(new S1Point(5.9 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(new S1Point(6.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(6.1 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(6.2 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(0.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(0.9 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(S1Point.of(1.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(S1Point.of(1.1 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(S1Point.of(2.9 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(S1Point.of(3.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(3.1 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(4.9 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(S1Point.of(5.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(S1Point.of(5.1 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(S1Point.of(5.9 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(S1Point.of(6.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(6.1 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(6.2 + k * Geometry.TWO_PI)));
         }
 
         List<Arc> aMbList = aMb.asList();
@@ -236,19 +236,19 @@ public class ArcsSetTest {
 
         ArcsSet aMb = (ArcsSet) new RegionFactory<S1Point>().intersection(a, b);
         for (int k = -2; k < 3; ++k) {
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(0.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(new S1Point(1.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(new S1Point(1.1 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(new S1Point(2.9 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(new S1Point(3.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(3.1 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(4.9 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(new S1Point(5.0 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(new S1Point(5.1 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(new S1Point(5.4 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(new S1Point(5.5 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(5.6 + k * Geometry.TWO_PI)));
-            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(new S1Point(6.2 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(0.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(S1Point.of(1.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(S1Point.of(1.1 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(S1Point.of(2.9 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(S1Point.of(3.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(3.1 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(4.9 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(S1Point.of(5.0 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(S1Point.of(5.1 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.INSIDE,   aMb.checkPoint(S1Point.of(5.4 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.BOUNDARY, aMb.checkPoint(S1Point.of(5.5 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(5.6 + k * Geometry.TWO_PI)));
+            Assert.assertEquals(Location.OUTSIDE,  aMb.checkPoint(S1Point.of(6.2 + k * Geometry.TWO_PI)));
         }
 
         List<Arc> aMbList = aMb.asList();
@@ -270,15 +270,15 @@ public class ArcsSetTest {
                                                               new ArcsSet(0.5, 2.0, 1.0e-10)),
                                                               new ArcsSet(0.0, 5.5, 1.0e-10));
         Assert.assertEquals(3.0, set.getSize(), 1.0e-10);
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new S1Point(0.0)));
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new S1Point(4.0)));
-        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(new S1Point(6.0)));
-        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(new S1Point(1.2)));
-        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(new S1Point(5.25)));
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new S1Point(0.5)));
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new S1Point(3.0)));
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new S1Point(5.0)));
-        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(new S1Point(5.5)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(S1Point.of(0.0)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(S1Point.of(4.0)));
+        Assert.assertEquals(Region.Location.OUTSIDE,  set.checkPoint(S1Point.of(6.0)));
+        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(S1Point.of(1.2)));
+        Assert.assertEquals(Region.Location.INSIDE,   set.checkPoint(S1Point.of(5.25)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(S1Point.of(0.5)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(S1Point.of(3.0)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(S1Point.of(5.0)));
+        Assert.assertEquals(Region.Location.BOUNDARY, set.checkPoint(S1Point.of(5.5)));
 
         List<Arc> list = set.asList();
         Assert.assertEquals(2, list.size());
@@ -337,8 +337,8 @@ public class ArcsSetTest {
     @Test
     public void testShiftedAngles() {
         for (int k = -2; k < 3; ++k) {
-            SubLimitAngle l1  = new LimitAngle(new S1Point(1.0 + k * Geometry.TWO_PI), false, 1.0e-10).wholeHyperplane();
-            SubLimitAngle l2  = new LimitAngle(new S1Point(1.5 + k * Geometry.TWO_PI), true,  1.0e-10).wholeHyperplane();
+            SubLimitAngle l1  = new LimitAngle(S1Point.of(1.0 + k * Geometry.TWO_PI), false, 1.0e-10).wholeHyperplane();
+            SubLimitAngle l2  = new LimitAngle(S1Point.of(1.5 + k * Geometry.TWO_PI), true,  1.0e-10).wholeHyperplane();
             ArcsSet set = new ArcsSet(new BSPTree<>(l1,
                                                             new BSPTree<S1Point>(Boolean.FALSE),
                                                             new BSPTree<>(l2,
@@ -349,9 +349,9 @@ public class ArcsSetTest {
                                       1.0e-10);
             for (double alpha = 1.0e-6; alpha < Geometry.TWO_PI; alpha += 0.001) {
                 if (alpha < 1 || alpha > 1.5) {
-                    Assert.assertEquals(Location.OUTSIDE, set.checkPoint(new S1Point(alpha)));
+                    Assert.assertEquals(Location.OUTSIDE, set.checkPoint(S1Point.of(alpha)));
                 } else {
-                    Assert.assertEquals(Location.INSIDE,  set.checkPoint(new S1Point(alpha)));
+                    Assert.assertEquals(Location.INSIDE,  set.checkPoint(S1Point.of(alpha)));
                 }
             }
         }
@@ -360,9 +360,9 @@ public class ArcsSetTest {
 
     @Test(expected=ArcsSet.InconsistentStateAt2PiWrapping.class)
     public void testInconsistentState() {
-        SubLimitAngle l1 = new LimitAngle(new S1Point(1.0), false, 1.0e-10).wholeHyperplane();
-        SubLimitAngle l2 = new LimitAngle(new S1Point(2.0), true,  1.0e-10).wholeHyperplane();
-        SubLimitAngle l3 = new LimitAngle(new S1Point(3.0), false, 1.0e-10).wholeHyperplane();
+        SubLimitAngle l1 = new LimitAngle(S1Point.of(1.0), false, 1.0e-10).wholeHyperplane();
+        SubLimitAngle l2 = new LimitAngle(S1Point.of(2.0), true,  1.0e-10).wholeHyperplane();
+        SubLimitAngle l3 = new LimitAngle(S1Point.of(3.0), false, 1.0e-10).wholeHyperplane();
         new ArcsSet(new BSPTree<>(l1,
                                           new BSPTree<S1Point>(Boolean.FALSE),
                                           new BSPTree<>(l2,
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/LimitAngleTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/LimitAngleTest.java
index 454979a..f9a9b3b 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/LimitAngleTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/LimitAngleTest.java
@@ -25,7 +25,7 @@ public class LimitAngleTest {
     @Test
     public void testReversedLimit() {
         for (int k = -2; k < 3; ++k) {
-            LimitAngle l  = new LimitAngle(new S1Point(1.0 + k * Geometry.TWO_PI), false, 1.0e-10);
+            LimitAngle l  = new LimitAngle(S1Point.of(1.0 + k * Geometry.TWO_PI), false, 1.0e-10);
             Assert.assertEquals(l.getLocation().getAlpha(), l.getReverse().getLocation().getAlpha(), 1.0e-10);
             Assert.assertEquals(l.getTolerance(), l.getReverse().getTolerance(), 1.0e-10);
             Assert.assertTrue(l.sameOrientationAs(l));
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/S1PointTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/S1PointTest.java
index 8b0b989..eac954d 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/S1PointTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/S1PointTest.java
@@ -17,17 +17,20 @@
 package org.apache.commons.geometry.spherical.oned;
 
 import org.apache.commons.geometry.core.Geometry;
+import org.apache.commons.geometry.core.util.Coordinates;
 import org.junit.Assert;
 import org.junit.Test;
 
 public class S1PointTest {
 
+    private static final double EPS = 1e-10;
+
     @Test
     public void testS1Point() {
         for (int k = -2; k < 3; ++k) {
-            S1Point p = new S1Point(1.0 + k * Geometry.TWO_PI);
-            Assert.assertEquals(Math.cos(1.0), p.getVector().getX(), 1.0e-10);
-            Assert.assertEquals(Math.sin(1.0), p.getVector().getY(), 1.0e-10);
+            S1Point p = S1Point.of(1.0 + k * Geometry.TWO_PI);
+            Assert.assertEquals(Math.cos(1.0), p.getVector().getX(), EPS);
+            Assert.assertEquals(Math.sin(1.0), p.getVector().getY(), EPS);
             Assert.assertFalse(p.isNaN());
         }
     }
@@ -35,14 +38,14 @@ public class S1PointTest {
     @Test
     public void testNaN() {
         Assert.assertTrue(S1Point.NaN.isNaN());
-        Assert.assertTrue(S1Point.NaN.equals(new S1Point(Double.NaN)));
-        Assert.assertFalse(new S1Point(1.0).equals(S1Point.NaN));
+        Assert.assertTrue(S1Point.NaN.equals(S1Point.of(Double.NaN)));
+        Assert.assertFalse(S1Point.of(1.0).equals(S1Point.NaN));
     }
 
     @Test
     public void testEquals() {
-        S1Point a = new S1Point(1.0);
-        S1Point b = new S1Point(1.0);
+        S1Point a = S1Point.of(1.0);
+        S1Point b = S1Point.of(1.0);
         Assert.assertEquals(a.hashCode(), b.hashCode());
         Assert.assertFalse(a == b);
         Assert.assertTrue(a.equals(b));
@@ -52,9 +55,44 @@ public class S1PointTest {
 
     @Test
     public void testDistance() {
-        S1Point a = new S1Point(1.0);
-        S1Point b = new S1Point(a.getAlpha() + 0.5 * Math.PI);
+        S1Point a = S1Point.of(1.0);
+        S1Point b = S1Point.of(a.getAlpha() + 0.5 * Math.PI);
         Assert.assertEquals(0.5 * Math.PI, a.distance(b), 1.0e-10);
     }
 
+    @Test
+    public void testToString() {
+        // act/assert
+        Assert.assertEquals("(0.0)", S1Point.of(0.0).toString());
+        Assert.assertEquals("(1.0)", S1Point.of(1.0).toString());
+    }
+
+    @Test
+    public void testParse() {
+        // act/assert
+        checkPoint(S1Point.parse("(0)"), 0.0);
+        checkPoint(S1Point.parse("(1)"), 1.0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testParse_failure() {
+        // act/assert
+        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);
+    }
+
 }
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java
index a958dcf..7b9ad53 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java
@@ -47,7 +47,7 @@ public class CircleTest {
 
     @Test
     public void testXY() {
-        Circle circle = new Circle(new S2Point(1.2, 2.5), new S2Point(-4.3, 0), 1.0e-10);
+        Circle circle = new Circle(S2Point.of(1.2, 2.5), S2Point.of(-4.3, 0), 1.0e-10);
         Assert.assertEquals(0.0, circle.getPointAt(0).distance(circle.getXAxis()), 1.0e-10);
         Assert.assertEquals(0.0, circle.getPointAt(0.5 * Math.PI).distance(circle.getYAxis()), 1.0e-10);
         Assert.assertEquals(0.5 * Math.PI, Vector3D.angle(circle.getXAxis(), circle.getYAxis()), 1.0e-10);
@@ -60,7 +60,7 @@ public class CircleTest {
 
     @Test
     public void testReverse() {
-        Circle circle = new Circle(new S2Point(1.2, 2.5), new S2Point(-4.3, 0), 1.0e-10);
+        Circle circle = new Circle(S2Point.of(1.2, 2.5), S2Point.of(-4.3, 0), 1.0e-10);
         Circle reversed = circle.getReverse();
         Assert.assertEquals(0.0, reversed.getPointAt(0).distance(reversed.getXAxis()), 1.0e-10);
         Assert.assertEquals(0.0, reversed.getPointAt(0.5 * Math.PI).distance(reversed.getYAxis()), 1.0e-10);
@@ -82,7 +82,7 @@ public class CircleTest {
 
     @Test
     public void testPhase() {
-        Circle circle = new Circle(new S2Point(1.2, 2.5), new S2Point(-4.3, 0), 1.0e-10);
+        Circle circle = new Circle(S2Point.of(1.2, 2.5), S2Point.of(-4.3, 0), 1.0e-10);
         Vector3D p = Vector3D.of(1, 2, -4);
         Vector3D samePhase = circle.getPointAt(circle.getPhase(p));
         Assert.assertEquals(0.0,
@@ -98,20 +98,20 @@ public class CircleTest {
 
     @Test
     public void testSubSpace() {
-        Circle circle = new Circle(new S2Point(1.2, 2.5), new S2Point(-4.3, 0), 1.0e-10);
-        Assert.assertEquals(0.0, circle.toSubSpace(new S2Point(circle.getXAxis())).getAlpha(), 1.0e-10);
-        Assert.assertEquals(0.5 * Math.PI, circle.toSubSpace(new S2Point(circle.getYAxis())).getAlpha(), 1.0e-10);
+        Circle circle = new Circle(S2Point.of(1.2, 2.5), S2Point.of(-4.3, 0), 1.0e-10);
+        Assert.assertEquals(0.0, circle.toSubSpace(S2Point.of(circle.getXAxis())).getAlpha(), 1.0e-10);
+        Assert.assertEquals(0.5 * Math.PI, circle.toSubSpace(S2Point.of(circle.getYAxis())).getAlpha(), 1.0e-10);
         Vector3D p = Vector3D.of(1, 2, -4);
-        Assert.assertEquals(circle.getPhase(p), circle.toSubSpace(new S2Point(p)).getAlpha(), 1.0e-10);
+        Assert.assertEquals(circle.getPhase(p), circle.toSubSpace(S2Point.of(p)).getAlpha(), 1.0e-10);
     }
 
     @Test
     public void testSpace() {
-        Circle circle = new Circle(new S2Point(1.2, 2.5), new S2Point(-4.3, 0), 1.0e-10);
+        Circle circle = new Circle(S2Point.of(1.2, 2.5), S2Point.of(-4.3, 0), 1.0e-10);
         for (double alpha = 0; alpha < Geometry.TWO_PI; alpha += 0.1) {
             Vector3D p = Vector3D.linearCombination(Math.cos(alpha), circle.getXAxis(),
                                       Math.sin(alpha), circle.getYAxis());
-            Vector3D q = circle.toSpace(new S1Point(alpha)).getVector();
+            Vector3D q = circle.toSpace(S1Point.of(alpha)).getVector();
             Assert.assertEquals(0.0, p.distance(q), 1.0e-10);
             Assert.assertEquals(0.5 * Math.PI, Vector3D.angle(circle.getPole(), q), 1.0e-10);
         }
@@ -120,12 +120,12 @@ public class CircleTest {
     @Test
     public void testOffset() {
         Circle circle = new Circle(Vector3D.PLUS_Z, 1.0e-10);
-        Assert.assertEquals(0.0,                circle.getOffset(new S2Point(Vector3D.PLUS_X)),  1.0e-10);
-        Assert.assertEquals(0.0,                circle.getOffset(new S2Point(Vector3D.MINUS_X)), 1.0e-10);
-        Assert.assertEquals(0.0,                circle.getOffset(new S2Point(Vector3D.PLUS_Y)),  1.0e-10);
-        Assert.assertEquals(0.0,                circle.getOffset(new S2Point(Vector3D.MINUS_Y)), 1.0e-10);
-        Assert.assertEquals(-0.5 * Math.PI, circle.getOffset(new S2Point(Vector3D.PLUS_Z)),  1.0e-10);
-        Assert.assertEquals(0.5 * Math.PI, circle.getOffset(new S2Point(Vector3D.MINUS_Z)), 1.0e-10);
+        Assert.assertEquals(0.0,                circle.getOffset(S2Point.of(Vector3D.PLUS_X)),  1.0e-10);
+        Assert.assertEquals(0.0,                circle.getOffset(S2Point.of(Vector3D.MINUS_X)), 1.0e-10);
+        Assert.assertEquals(0.0,                circle.getOffset(S2Point.of(Vector3D.PLUS_Y)),  1.0e-10);
+        Assert.assertEquals(0.0,                circle.getOffset(S2Point.of(Vector3D.MINUS_Y)), 1.0e-10);
+        Assert.assertEquals(-0.5 * Math.PI, circle.getOffset(S2Point.of(Vector3D.PLUS_Z)),  1.0e-10);
+        Assert.assertEquals(0.5 * Math.PI, circle.getOffset(S2Point.of(Vector3D.MINUS_Z)), 1.0e-10);
 
     }
 
@@ -164,7 +164,7 @@ public class CircleTest {
                                       RotationConvention.VECTOR_OPERATOR);
             Transform<S2Point, S1Point> t = Circle.getTransform(r);
 
-            S2Point  p = new S2Point(Vector3D.of(sphRandom.nextVector()));
+            S2Point  p = S2Point.of(Vector3D.of(sphRandom.nextVector()));
             S2Point tp = t.apply(p);
             Assert.assertEquals(0.0, r.applyTo(p.getVector()).distance(tp.getVector()), 1.0e-10);
 
@@ -175,7 +175,7 @@ public class CircleTest {
             Assert.assertEquals(0.0, r.applyTo(c.getYAxis()).distance(tc.getYAxis()), 1.0e-10);
             Assert.assertEquals(c.getTolerance(), ((Circle) t.apply(c)).getTolerance(), 1.0e-10);
 
-            SubLimitAngle  sub = new LimitAngle(new S1Point(Geometry.TWO_PI * random.nextDouble()),
+            SubLimitAngle  sub = new LimitAngle(S1Point.of(Geometry.TWO_PI * random.nextDouble()),
                                                 random.nextBoolean(), 1.0e-10).wholeHyperplane();
             Vector3D psub = c.getPointAt(((LimitAngle) sub.getHyperplane()).getLocation().getAlpha());
             SubLimitAngle tsub = (SubLimitAngle) t.apply(sub, c, tc);
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/S2PointTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/S2PointTest.java
index bbf5891..1661921 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/S2PointTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/S2PointTest.java
@@ -18,45 +18,48 @@ package org.apache.commons.geometry.spherical.twod;
 
 
 import org.apache.commons.geometry.core.Geometry;
+import org.apache.commons.geometry.core.util.Coordinates;
 import org.junit.Assert;
 import org.junit.Test;
 
 public class S2PointTest {
 
+    private static final double EPS = 1e-10;
+
     @Test
     public void testS2Point() {
         for (int k = -2; k < 3; ++k) {
-            S2Point p = new S2Point(1.0 + k * Geometry.TWO_PI, 1.4);
-            Assert.assertEquals(1.0 + k * Geometry.TWO_PI, p.getTheta(), 1.0e-10);
-            Assert.assertEquals(1.4, p.getPhi(), 1.0e-10);
-            Assert.assertEquals(Math.cos(1.0) * Math.sin(1.4), p.getVector().getX(), 1.0e-10);
-            Assert.assertEquals(Math.sin(1.0) * Math.sin(1.4), p.getVector().getY(), 1.0e-10);
-            Assert.assertEquals(Math.cos(1.4), p.getVector().getZ(), 1.0e-10);
+            S2Point p = S2Point.of(1.0 + k * Geometry.TWO_PI, 1.4);
+            Assert.assertEquals(1.0 + k * Geometry.TWO_PI, p.getTheta(), EPS);
+            Assert.assertEquals(1.4, p.getPhi(), EPS);
+            Assert.assertEquals(Math.cos(1.0) * Math.sin(1.4), p.getVector().getX(), EPS);
+            Assert.assertEquals(Math.sin(1.0) * Math.sin(1.4), p.getVector().getY(), EPS);
+            Assert.assertEquals(Math.cos(1.4), p.getVector().getZ(), EPS);
             Assert.assertFalse(p.isNaN());
         }
     }
 
     @Test(expected=IllegalArgumentException.class)
     public void testNegativePolarAngle() {
-        new S2Point(1.0, -1.0);
+        S2Point.of(1.0, -1.0);
     }
 
     @Test(expected=IllegalArgumentException.class)
     public void testTooLargePolarAngle() {
-        new S2Point(1.0, 3.5);
+        S2Point.of(1.0, 3.5);
     }
 
     @Test
     public void testNaN() {
         Assert.assertTrue(S2Point.NaN.isNaN());
-        Assert.assertTrue(S2Point.NaN.equals(new S2Point(Double.NaN, 1.0)));
-        Assert.assertFalse(new S2Point(1.0, 1.3).equals(S2Point.NaN));
+        Assert.assertTrue(S2Point.NaN.equals(S2Point.of(Double.NaN, 1.0)));
+        Assert.assertFalse(S2Point.of(1.0, 1.3).equals(S2Point.NaN));
     }
 
     @Test
     public void testEquals() {
-        S2Point a = new S2Point(1.0, 1.0);
-        S2Point b = new S2Point(1.0, 1.0);
+        S2Point a = S2Point.of(1.0, 1.0);
+        S2Point b = S2Point.of(1.0, 1.0);
         Assert.assertEquals(a.hashCode(), b.hashCode());
         Assert.assertFalse(a == b);
         Assert.assertTrue(a.equals(b));
@@ -66,12 +69,46 @@ public class S2PointTest {
 
     @Test
     public void testDistance() {
-        S2Point a = new S2Point(1.0, 0.5 * Math.PI);
-        S2Point b = new S2Point(a.getTheta() + 0.5 * Math.PI, a.getPhi());
+        S2Point a = S2Point.of(1.0, 0.5 * Math.PI);
+        S2Point b = S2Point.of(a.getTheta() + 0.5 * Math.PI, a.getPhi());
         Assert.assertEquals(0.5 * Math.PI, a.distance(b), 1.0e-10);
         Assert.assertEquals(Math.PI, a.distance(a.negate()), 1.0e-10);
         Assert.assertEquals(0.5 * Math.PI, S2Point.MINUS_I.distance(S2Point.MINUS_K), 1.0e-10);
-        Assert.assertEquals(0.0, new S2Point(1.0, 0).distance(new S2Point(2.0, 0)), 1.0e-10);
+        Assert.assertEquals(0.0, S2Point.of(1.0, 0).distance(S2Point.of(2.0, 0)), 1.0e-10);
+    }
+
+    @Test
+    public void testToString() {
+        // act/assert
+        Assert.assertEquals("(0.0, 0.0)", S2Point.of(0.0, 0.0).toString());
+        Assert.assertEquals("(1.0, 2.0)", S2Point.of(1.0, 2.0).toString());
+    }
+
+    @Test
+    public void testParse() {
+        // act/assert
+        checkPoint(S2Point.parse("(0,0)"), 0.0, 0.0);
+        checkPoint(S2Point.parse("(1,2)"), 1.0, 2.0);
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testParse_failure() {
+        // act/assert
+        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);
+    }
 }
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java
index 0f52671..0f056e8 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java
@@ -43,7 +43,7 @@ public class SphericalPolygonsSetTest {
                                                              0x852fd2a0ed8d2f6dl));
         for (int i = 0; i < 1000; ++i) {
             Vector3D v = Vector3D.of(random.nextVector());
-            Assert.assertEquals(Location.INSIDE, full.checkPoint(new S2Point(v)));
+            Assert.assertEquals(Location.INSIDE, full.checkPoint(S2Point.of(v)));
         }
         Assert.assertEquals(4 * Math.PI, new SphericalPolygonsSet(0.01, new S2Point[0]).getSize(), 1.0e-10);
         Assert.assertEquals(0, new SphericalPolygonsSet(0.01, new S2Point[0]).getBoundarySize(), 1.0e-10);
@@ -61,7 +61,7 @@ public class SphericalPolygonsSetTest {
                                                              0x76d9205d6167b6ddl));
         for (int i = 0; i < 1000; ++i) {
             Vector3D v = Vector3D.of(random.nextVector());
-            Assert.assertEquals(Location.OUTSIDE, empty.checkPoint(new S2Point(v)));
+            Assert.assertEquals(Location.OUTSIDE, empty.checkPoint(S2Point.of(v)));
         }
         Assert.assertEquals(0, empty.getSize(), 1.0e-10);
         Assert.assertEquals(0, empty.getBoundarySize(), 1.0e-10);
@@ -81,11 +81,11 @@ public class SphericalPolygonsSetTest {
         for (int i = 0; i < 1000; ++i) {
             Vector3D v = Vector3D.of(random.nextVector());
             if (v.getZ() < -sinTol) {
-                Assert.assertEquals(Location.INSIDE, south.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.INSIDE, south.checkPoint(S2Point.of(v)));
             } else if (v.getZ() > sinTol) {
-                Assert.assertEquals(Location.OUTSIDE, south.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.OUTSIDE, south.checkPoint(S2Point.of(v)));
             } else {
-                Assert.assertEquals(Location.BOUNDARY, south.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.BOUNDARY, south.checkPoint(S2Point.of(v)));
             }
         }
         Assert.assertEquals(1, south.getBoundaryLoops().size());
@@ -117,11 +117,11 @@ public class SphericalPolygonsSetTest {
         for (int i = 0; i < 1000; ++i) {
             Vector3D v = Vector3D.of(random.nextVector());
             if ((v.getX() > sinTol) && (v.getY() > sinTol) && (v.getZ() > sinTol)) {
-                Assert.assertEquals(Location.INSIDE, octant.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.INSIDE, octant.checkPoint(S2Point.of(v)));
             } else if ((v.getX() < -sinTol) || (v.getY() < -sinTol) || (v.getZ() < -sinTol)) {
-                Assert.assertEquals(Location.OUTSIDE, octant.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.OUTSIDE, octant.checkPoint(S2Point.of(v)));
             } else {
-                Assert.assertEquals(Location.BOUNDARY, octant.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.BOUNDARY, octant.checkPoint(S2Point.of(v)));
             }
         }
 
@@ -156,7 +156,7 @@ public class SphericalPolygonsSetTest {
         Assert.assertEquals(3, count);
 
         Assert.assertEquals(0.0,
-                            octant.getBarycenter().distance(new S2Point(Vector3D.of(1, 1, 1))),
+                            octant.getBarycenter().distance(S2Point.of(Vector3D.of(1, 1, 1))),
                             1.0e-10);
         Assert.assertEquals(0.5 * Math.PI, octant.getSize(), 1.0e-10);
 
@@ -166,7 +166,7 @@ public class SphericalPolygonsSetTest {
 
         EnclosingBall<S2Point> reversedCap =
                 ((SphericalPolygonsSet) factory.getComplement(octant)).getEnclosingCap();
-        Assert.assertEquals(0, reversedCap.getCenter().distance(new S2Point(Vector3D.of(-1, -1, -1))), 1.0e-10);
+        Assert.assertEquals(0, reversedCap.getCenter().distance(S2Point.of(Vector3D.of(-1, -1, -1))), 1.0e-10);
         Assert.assertEquals(Math.PI - Math.asin(1.0 / Math.sqrt(3)), reversedCap.getRadius(), 1.0e-10);
 
     }
@@ -182,11 +182,11 @@ public class SphericalPolygonsSetTest {
         for (int i = 0; i < 1000; ++i) {
             Vector3D v = Vector3D.of(random.nextVector());
             if ((v.getX() > sinTol) && (v.getY() > sinTol) && (v.getZ() > sinTol)) {
-                Assert.assertEquals(Location.INSIDE, octant.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.INSIDE, octant.checkPoint(S2Point.of(v)));
             } else if ((v.getX() < -sinTol) || (v.getY() < -sinTol) || (v.getZ() < -sinTol)) {
-                Assert.assertEquals(Location.OUTSIDE, octant.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.OUTSIDE, octant.checkPoint(S2Point.of(v)));
             } else {
-                Assert.assertEquals(Location.BOUNDARY, octant.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.BOUNDARY, octant.checkPoint(S2Point.of(v)));
             }
         }
     }
@@ -208,11 +208,11 @@ public class SphericalPolygonsSetTest {
         for (int i = 0; i < 1000; ++i) {
             Vector3D v = Vector3D.of(random.nextVector());
             if (((v.getX() < -sinTol) || (v.getY() < -sinTol)) && (v.getZ() > sinTol)) {
-                Assert.assertEquals(Location.INSIDE, threeOctants.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.INSIDE, threeOctants.checkPoint(S2Point.of(v)));
             } else if (((v.getX() > sinTol) && (v.getY() > sinTol)) || (v.getZ() < -sinTol)) {
-                Assert.assertEquals(Location.OUTSIDE, threeOctants.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.OUTSIDE, threeOctants.checkPoint(S2Point.of(v)));
             } else {
-                Assert.assertEquals(Location.BOUNDARY, threeOctants.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.BOUNDARY, threeOctants.checkPoint(S2Point.of(v)));
             }
         }
 
@@ -274,14 +274,14 @@ public class SphericalPolygonsSetTest {
         boundary.add(create(Vector3D.PLUS_Z,  Vector3D.MINUS_Y, Vector3D.PLUS_X,  tol, 0.0, 0.5 * Math.PI));
         SphericalPolygonsSet polygon = new SphericalPolygonsSet(boundary, tol);
 
-        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(Vector3D.of( 1,  1,  1).normalize())));
-        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(new S2Point(Vector3D.of(-1,  1,  1).normalize())));
-        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(new S2Point(Vector3D.of(-1, -1,  1).normalize())));
-        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(new S2Point(Vector3D.of( 1, -1,  1).normalize())));
-        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(Vector3D.of( 1,  1, -1).normalize())));
-        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(Vector3D.of(-1,  1, -1).normalize())));
-        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(new S2Point(Vector3D.of(-1, -1, -1).normalize())));
-        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(Vector3D.of( 1, -1, -1).normalize())));
+        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.of(Vector3D.of( 1,  1,  1).normalize())));
+        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(S2Point.of(Vector3D.of(-1,  1,  1).normalize())));
+        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(S2Point.of(Vector3D.of(-1, -1,  1).normalize())));
+        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(S2Point.of(Vector3D.of( 1, -1,  1).normalize())));
+        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.of(Vector3D.of( 1,  1, -1).normalize())));
+        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.of(Vector3D.of(-1,  1, -1).normalize())));
+        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(S2Point.of(Vector3D.of(-1, -1, -1).normalize())));
+        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.of(Vector3D.of( 1, -1, -1).normalize())));
 
         Assert.assertEquals(Geometry.TWO_PI, polygon.getSize(), 1.0e-10);
         Assert.assertEquals(3 * Math.PI, polygon.getBoundarySize(), 1.0e-10);
@@ -342,15 +342,15 @@ public class SphericalPolygonsSetTest {
         for (int i = 0; i < 1000; ++i) {
             Vector3D v = Vector3D.of(random.nextVector());
             if ((v.getX() < -sinTol) && (v.getY() < -sinTol) && (v.getZ() < -sinTol)) {
-                Assert.assertEquals(Location.INSIDE, polygon.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.INSIDE, polygon.checkPoint(S2Point.of(v)));
             } else if ((v.getX() < sinTol) && (v.getY() < sinTol) && (v.getZ() < sinTol)) {
-                Assert.assertEquals(Location.BOUNDARY, polygon.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.BOUNDARY, polygon.checkPoint(S2Point.of(v)));
             } else if ((v.getX() > sinTol) && (v.getY() > sinTol) && (v.getZ() > sinTol)) {
-                Assert.assertEquals(Location.INSIDE, polygon.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.INSIDE, polygon.checkPoint(S2Point.of(v)));
             } else if ((v.getX() > -sinTol) && (v.getY() > -sinTol) && (v.getZ() > -sinTol)) {
-                Assert.assertEquals(Location.BOUNDARY, polygon.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.BOUNDARY, polygon.checkPoint(S2Point.of(v)));
             } else {
-                Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(v)));
+                Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.of(v)));
             }
         }
 
@@ -366,17 +366,17 @@ public class SphericalPolygonsSetTest {
     public void testPartWithHole() {
         double tol = 0.01;
         double alpha = 0.7;
-        S2Point center = new S2Point(Vector3D.of(1, 1, 1));
+        S2Point center = S2Point.of(Vector3D.of(1, 1, 1));
         SphericalPolygonsSet hexa = new SphericalPolygonsSet(center.getVector(), Vector3D.PLUS_Z, alpha, 6, tol);
         SphericalPolygonsSet hole  = new SphericalPolygonsSet(tol,
-                                                              new S2Point(Math.PI / 6, Math.PI / 3),
-                                                              new S2Point(Math.PI / 3, Math.PI / 3),
-                                                              new S2Point(Math.PI / 4, Math.PI / 6));
+                                                              S2Point.of(Math.PI / 6, Math.PI / 3),
+                                                              S2Point.of(Math.PI / 3, Math.PI / 3),
+                                                              S2Point.of(Math.PI / 4, Math.PI / 6));
         SphericalPolygonsSet hexaWithHole =
                 (SphericalPolygonsSet) new RegionFactory<S2Point>().difference(hexa, hole);
 
         for (double phi = center.getPhi() - alpha + 0.1; phi < center.getPhi() + alpha - 0.1; phi += 0.07) {
-            Location l = hexaWithHole.checkPoint(new S2Point(Math.PI / 4, phi));
+            Location l = hexaWithHole.checkPoint(S2Point.of(Math.PI / 4, phi));
             if (phi < Math.PI / 6 || phi > Math.PI / 3) {
                 Assert.assertEquals(Location.INSIDE,  l);
             } else {
@@ -428,24 +428,24 @@ public class SphericalPolygonsSetTest {
                             concentric.getSize(), 1.0e-10);
 
         // we expect lots of sign changes as we traverse all concentric rings
-        double phi = new S2Point(center).getPhi();
-        Assert.assertEquals(+0.207, concentric.projectToBoundary(new S2Point(-0.60,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(-0.048, concentric.projectToBoundary(new S2Point(-0.21,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(+0.027, concentric.projectToBoundary(new S2Point(-0.10,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(-0.041, concentric.projectToBoundary(new S2Point( 0.01,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(+0.049, concentric.projectToBoundary(new S2Point( 0.16,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(-0.038, concentric.projectToBoundary(new S2Point( 0.29,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(+0.097, concentric.projectToBoundary(new S2Point( 0.48,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(-0.022, concentric.projectToBoundary(new S2Point( 0.64,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(+0.072, concentric.projectToBoundary(new S2Point( 0.79,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(-0.022, concentric.projectToBoundary(new S2Point( 0.93,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(+0.091, concentric.projectToBoundary(new S2Point( 1.08,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(-0.037, concentric.projectToBoundary(new S2Point( 1.28,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(+0.051, concentric.projectToBoundary(new S2Point( 1.40,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(-0.041, concentric.projectToBoundary(new S2Point( 1.55,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(+0.027, concentric.projectToBoundary(new S2Point( 1.67,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(-0.044, concentric.projectToBoundary(new S2Point( 1.79,  phi)).getOffset(), 0.01);
-        Assert.assertEquals(+0.201, concentric.projectToBoundary(new S2Point( 2.16,  phi)).getOffset(), 0.01);
+        double phi = S2Point.of(center).getPhi();
+        Assert.assertEquals(+0.207, concentric.projectToBoundary(S2Point.of(-0.60,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(-0.048, concentric.projectToBoundary(S2Point.of(-0.21,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(+0.027, concentric.projectToBoundary(S2Point.of(-0.10,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(-0.041, concentric.projectToBoundary(S2Point.of( 0.01,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(+0.049, concentric.projectToBoundary(S2Point.of( 0.16,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(-0.038, concentric.projectToBoundary(S2Point.of( 0.29,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(+0.097, concentric.projectToBoundary(S2Point.of( 0.48,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(-0.022, concentric.projectToBoundary(S2Point.of( 0.64,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(+0.072, concentric.projectToBoundary(S2Point.of( 0.79,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(-0.022, concentric.projectToBoundary(S2Point.of( 0.93,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(+0.091, concentric.projectToBoundary(S2Point.of( 1.08,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(-0.037, concentric.projectToBoundary(S2Point.of( 1.28,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(+0.051, concentric.projectToBoundary(S2Point.of( 1.40,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(-0.041, concentric.projectToBoundary(S2Point.of( 1.55,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(+0.027, concentric.projectToBoundary(S2Point.of( 1.67,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(-0.044, concentric.projectToBoundary(S2Point.of( 1.79,  phi)).getOffset(), 0.01);
+        Assert.assertEquals(+0.201, concentric.projectToBoundary(S2Point.of( 2.16,  phi)).getOffset(), 0.01);
 
     }
 
@@ -544,7 +544,7 @@ public class SphericalPolygonsSetTest {
     }
 
     private S2Point s2Point(double latitude, double longitude) {
-        return new S2Point(Math.toRadians(longitude), Math.toRadians(90.0 - latitude));
+        return S2Point.of(Math.toRadians(longitude), Math.toRadians(90.0 - latitude));
     }
 
 }