You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ma...@apache.org on 2020/05/06 21:25:19 UTC
[commons-geometry] branch master updated: GEOMETRY-96: optimizing
HyperplaneSubset.Builder implementations to only create internal BSP trees
when needed
This is an automated email from the ASF dual-hosted git repository.
mattjuntunen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-geometry.git
The following commit(s) were added to refs/heads/master by this push:
new 7ccde75 GEOMETRY-96: optimizing HyperplaneSubset.Builder implementations to only create internal BSP trees when needed
7ccde75 is described below
commit 7ccde75624c83b9d0e41e7a2c81ef0d96639dcae
Author: Matt Juntunen <ma...@apache.org>
AuthorDate: Wed May 6 10:20:20 2020 -0400
GEOMETRY-96: optimizing HyperplaneSubset.Builder implementations to only create internal BSP trees when needed
---
.../euclidean/threed/EmbeddedTreePlaneSubset.java | 68 +----
.../geometry/euclidean/threed/PlaneSubset.java | 111 +++++++-
.../commons/geometry/euclidean/threed/Planes.java | 12 +
.../geometry/euclidean/threed/RegionBSPTree3D.java | 6 +-
.../euclidean/twod/EmbeddedTreeLineSubset.java | 68 +----
.../geometry/euclidean/twod/LineSubset.java | 112 +++++++-
.../commons/geometry/euclidean/twod/Lines.java | 12 +
.../threed/EmbeddedTreePlaneSubsetTest.java | 82 +-----
.../geometry/euclidean/threed/PlaneSubsetTest.java | 281 +++++++++++++++++++
.../euclidean/twod/EmbeddedTreeLineSubsetTest.java | 207 --------------
...neSpanTest.java => LineSpanningSubsetTest.java} | 2 +-
.../geometry/euclidean/twod/LineSubsetTest.java | 312 +++++++++++++++++++++
.../commons/geometry/spherical/oned/CutAngle.java | 4 +-
.../twod/EmbeddedTreeGreatCircleSubset.java | 68 +----
.../geometry/spherical/twod/GreatCircleSubset.java | 112 +++++++-
.../geometry/spherical/twod/GreatCircles.java | 14 +
.../twod/EmbeddedTreeSubGreatCircleTest.java | 117 --------
.../spherical/twod/GreatCircleSubsetTest.java | 284 +++++++++++++++++++
18 files changed, 1258 insertions(+), 614 deletions(-)
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubset.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubset.java
index 5c49f6f..7b15ef6 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubset.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubset.java
@@ -21,8 +21,6 @@ import java.util.List;
import org.apache.commons.geometry.core.Transform;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
-import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
-import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.euclidean.twod.ConvexArea;
import org.apache.commons.geometry.euclidean.twod.RegionBSPTree2D;
@@ -118,7 +116,7 @@ public final class EmbeddedTreePlaneSubset extends PlaneSubset {
* a plane equivalent to this instance
*/
public void add(final PlaneConvexSubset subset) {
- validatePlane(subset.getPlane());
+ Planes.validatePlanesEquivalent(getPlane(), subset.getPlane());
region.add(subset.getSubspaceRegion());
}
@@ -129,70 +127,8 @@ public final class EmbeddedTreePlaneSubset extends PlaneSubset {
* a plane equivalent to this instance
*/
public void add(final EmbeddedTreePlaneSubset subset) {
- validatePlane(subset.getPlane());
+ Planes.validatePlanesEquivalent(getPlane(), subset.getPlane());
region.union(subset.getSubspaceRegion());
}
-
- /** Validate that the given plane is equivalent to the plane
- * defining this instance.
- * @param inputPlane plane to validate
- * @throws IllegalArgumentException if the given plane is not equivalent
- * to the plane for this instance
- */
- private void validatePlane(final Plane inputPlane) {
- final Plane plane = getPlane();
-
- if (!plane.eq(inputPlane, plane.getPrecision())) {
- throw new IllegalArgumentException("Argument is not on the same " +
- "plane. Expected " + plane + " but was " +
- inputPlane);
- }
- }
-
- /** {@link HyperplaneSubset.Builder} implementation for plane subsets.
- */
- public static class Builder implements HyperplaneSubset.Builder<Vector3D> {
-
- /** Plane subset instance created by this builder. */
- private final EmbeddedTreePlaneSubset subset;
-
- /** Construct a new instance for building a subset region for the given plane.
- * @param plane the underlying plane for the subset
- */
- public Builder(final Plane plane) {
- this.subset = new EmbeddedTreePlaneSubset(plane);
- }
-
- /** {@inheritDoc} */
- @Override
- public void add(final HyperplaneSubset<Vector3D> sub) {
- addInternal(sub);
- }
-
- /** {@inheritDoc} */
- @Override
- public void add(final HyperplaneConvexSubset<Vector3D> sub) {
- addInternal(sub);
- }
-
- /** {@inheritDoc} */
- @Override
- public EmbeddedTreePlaneSubset build() {
- return subset;
- }
-
- /** Internal method for adding hyperplane subsets to this builder.
- * @param sub the hyperplane subset to add; either convex or non-convex
- */
- private void addInternal(final HyperplaneSubset<Vector3D> sub) {
- if (sub instanceof PlaneConvexSubset) {
- subset.add((PlaneConvexSubset) sub);
- } else if (sub instanceof EmbeddedTreePlaneSubset) {
- subset.add((EmbeddedTreePlaneSubset) sub);
- } else {
- throw new IllegalArgumentException("Unsupported plane subset type: " + sub.getClass().getName());
- }
- }
- }
}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PlaneSubset.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PlaneSubset.java
index a3a3598..945f87d 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PlaneSubset.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PlaneSubset.java
@@ -16,11 +16,15 @@
*/
package org.apache.commons.geometry.euclidean.threed;
+import java.util.List;
+import java.util.Objects;
import java.util.function.BiFunction;
import org.apache.commons.geometry.core.partitioning.AbstractRegionEmbeddingHyperplaneSubset;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
import org.apache.commons.geometry.core.partitioning.HyperplaneBoundedRegion;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
+import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.partitioning.SplitLocation;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
@@ -61,8 +65,12 @@ public abstract class PlaneSubset
/** {@inheritDoc} */
@Override
- public EmbeddedTreePlaneSubset.Builder builder() {
- return new EmbeddedTreePlaneSubset.Builder(plane);
+ public abstract List<PlaneConvexSubset> toConvex();
+
+ /** {@inheritDoc} */
+ @Override
+ public HyperplaneSubset.Builder<Vector3D> builder() {
+ return new Builder(plane);
}
/** Return the object used to perform floating point comparisons, which is the
@@ -142,4 +150,103 @@ public abstract class PlaneSubset
return new Split<>(minus, plus);
}
}
+
+ /** Internal implementation of the {@link HyperplaneSubset.Builder} interface. In cases where only a single
+ * convex subset is given to the builder, this class returns the convex subset instance directly. In all other
+ * cases, an {@link EmbeddedTreePlaneSubset} is used to construct the final subset.
+ */
+ private static final class Builder implements HyperplaneSubset.Builder<Vector3D> {
+ /** Plane that a subset is being constructed for. */
+ private final Plane plane;
+
+ /** Embedded tree subset. */
+ private EmbeddedTreePlaneSubset treeSubset;
+
+ /** Convex subset added as the first subset to the builder. This is returned directly if
+ * no other subsets are added.
+ */
+ private PlaneConvexSubset convexSubset;
+
+ /** Create a new subset builder for the given plane.
+ * @param plane plane to build a subset for
+ */
+ Builder(final Plane plane) {
+ this.plane = plane;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void add(final HyperplaneSubset<Vector3D> sub) {
+ addInternal(sub);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void add(final HyperplaneConvexSubset<Vector3D> sub) {
+ addInternal(sub);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public PlaneSubset build() {
+ // return the convex subset directly if that was all we were given
+ if (convexSubset != null) {
+ return convexSubset;
+ }
+ return getTreeSubset();
+ }
+
+ /** Internal method for adding hyperplane subsets to this builder.
+ * @param sub the hyperplane subset to add; may be either convex or non-convex
+ */
+ private void addInternal(final HyperplaneSubset<Vector3D> sub) {
+ Objects.requireNonNull(sub, "Hyperplane subset must not be null");
+
+ if (sub instanceof PlaneConvexSubset) {
+ addConvexSubset((PlaneConvexSubset) sub);
+ } else if (sub instanceof EmbeddedTreePlaneSubset) {
+ addTreeSubset((EmbeddedTreePlaneSubset) sub);
+ } else {
+ throw new IllegalArgumentException("Unsupported hyperplane subset type: " + sub.getClass().getName());
+ }
+ }
+
+ /** Add a convex subset to the builder.
+ * @param convex convex subset to add
+ */
+ private void addConvexSubset(final PlaneConvexSubset convex) {
+ Planes.validatePlanesEquivalent(plane, convex.getPlane());
+
+ if (treeSubset == null && convexSubset == null) {
+ convexSubset = convex;
+ } else {
+ getTreeSubset().add(convex);
+ }
+ }
+
+ /** Add an embedded tree subset to the builder.
+ * @param tree embedded tree subset to add
+ */
+ private void addTreeSubset(final EmbeddedTreePlaneSubset tree) {
+ // no need to validate the line here since the add() method does that for us
+ getTreeSubset().add(tree);
+ }
+
+ /** Get the tree subset for the builder, creating it if needed.
+ * @return the tree subset for the builder
+ */
+ private EmbeddedTreePlaneSubset getTreeSubset() {
+ if (treeSubset == null) {
+ treeSubset = new EmbeddedTreePlaneSubset(plane);
+
+ if (convexSubset != null) {
+ treeSubset.add(convexSubset);
+
+ convexSubset = null;
+ }
+ }
+
+ return treeSubset;
+ }
+ }
}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Planes.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Planes.java
index 8a4f275..fcba291 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Planes.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Planes.java
@@ -261,4 +261,16 @@ public final class Planes {
return new PlaneConvexSubset(plane, area);
}
+
+ /** Validate that the actual plane is equivalent to the expected plane, throwing an exception if not.
+ * @param expected the expected plane
+ * @param actual the actual plane
+ * @throws IllegalArgumentException if the actual plane is not equivalent to the expected plane
+ */
+ static void validatePlanesEquivalent(final Plane expected, final Plane actual) {
+ if (!expected.eq(actual, expected.getPrecision())) {
+ throw new IllegalArgumentException("Arguments do not represent the same plane. Expected " +
+ expected + " but was " + actual + ".");
+ }
+ }
}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3D.java
index 177ded8..0739d67 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3D.java
@@ -22,6 +22,7 @@ import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneBoundedRegion;
import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.partitioning.bsp.AbstractBSPTree;
@@ -31,7 +32,6 @@ import org.apache.commons.geometry.core.partitioning.bsp.RegionCutBoundary;
import org.apache.commons.geometry.euclidean.threed.line.Line3D;
import org.apache.commons.geometry.euclidean.threed.line.LineConvexSubset3D;
import org.apache.commons.geometry.euclidean.threed.line.LinecastPoint3D;
-import org.apache.commons.geometry.euclidean.twod.RegionBSPTree2D;
import org.apache.commons.geometry.euclidean.twod.Vector2D;
/** Binary space partitioning (BSP) tree representing a region in three dimensional
@@ -353,8 +353,8 @@ public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, Regio
* @param reverse if true, the boundary contribution is reversed before being added to the total.
*/
private void addBoundaryContribution(final HyperplaneSubset<Vector3D> boundary, boolean reverse) {
- final EmbeddedTreePlaneSubset boundarySubset = (EmbeddedTreePlaneSubset) boundary;
- final RegionBSPTree2D base = boundarySubset.getSubspaceRegion();
+ final PlaneSubset boundarySubset = (PlaneSubset) boundary;
+ final HyperplaneBoundedRegion<Vector2D> base = boundarySubset.getSubspaceRegion();
final double area = base.getSize();
final Vector2D baseBarycenter = base.getBarycenter();
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/EmbeddedTreeLineSubset.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/EmbeddedTreeLineSubset.java
index 9bfb2f8..ab557dd 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/EmbeddedTreeLineSubset.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/EmbeddedTreeLineSubset.java
@@ -22,8 +22,6 @@ import java.util.List;
import org.apache.commons.geometry.core.RegionLocation;
import org.apache.commons.geometry.core.Transform;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
-import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
-import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.partitioning.SplitLocation;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
@@ -156,7 +154,7 @@ public final class EmbeddedTreeLineSubset extends LineSubset {
* a line equivalent to this instance
*/
public void add(final LineConvexSubset subset) {
- validateLine(subset.getLine());
+ Lines.validateLinesEquivalent(getLine(), subset.getLine());
region.add(subset.getInterval());
}
@@ -168,7 +166,7 @@ public final class EmbeddedTreeLineSubset extends LineSubset {
* a line equivalent to this instance
*/
public void add(final EmbeddedTreeLineSubset subset) {
- validateLine(subset.getLine());
+ Lines.validateLinesEquivalent(getLine(), subset.getLine());
region.union(subset.getSubspaceRegion());
}
@@ -197,66 +195,4 @@ public final class EmbeddedTreeLineSubset extends LineSubset {
RegionLocation classifyAbscissa(final double abscissa) {
return region.classify(abscissa);
}
-
- /** Validate that the given line is equivalent to the line
- * defining this instance.
- * @param inputLine the line to validate
- * @throws IllegalArgumentException if the given line is not equivalent
- * to the line for this instance
- */
- private void validateLine(final Line inputLine) {
- final Line line = getLine();
-
- if (!line.eq(inputLine, line.getPrecision())) {
- throw new IllegalArgumentException("Argument is not on the same " +
- "line. Expected " + line + " but was " +
- inputLine);
- }
- }
-
- /** {@link HyperplaneSubset.Builder} implementation for line subsets.
- */
- public static final class Builder implements HyperplaneSubset.Builder<Vector2D> {
-
- /** Line subset instance created by this builder. */
- private final EmbeddedTreeLineSubset lineSubset;
-
- /** Construct a new instance for building a line subset for the given line.
- * @param line the underlying line for the subset
- */
- public Builder(final Line line) {
- this.lineSubset = new EmbeddedTreeLineSubset(line);
- }
-
- /** {@inheritDoc} */
- @Override
- public void add(final HyperplaneSubset<Vector2D> sub) {
- addInternal(sub);
- }
-
- /** {@inheritDoc} */
- @Override
- public void add(final HyperplaneConvexSubset<Vector2D> sub) {
- addInternal(sub);
- }
-
- /** {@inheritDoc} */
- @Override
- public EmbeddedTreeLineSubset build() {
- return lineSubset;
- }
-
- /** Internal method for adding hyperplane subsets to this builder.
- * @param sub the hyperplane subset to add; either convex or non-convex
- */
- private void addInternal(final HyperplaneSubset<Vector2D> sub) {
- if (sub instanceof LineConvexSubset) {
- lineSubset.add((LineConvexSubset) sub);
- } else if (sub instanceof EmbeddedTreeLineSubset) {
- lineSubset.add((EmbeddedTreeLineSubset) sub);
- } else {
- throw new IllegalArgumentException("Unsupported hyperplane subset type: " + sub.getClass().getName());
- }
- }
- }
}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/LineSubset.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/LineSubset.java
index e8c28aa..ba05313 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/LineSubset.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/LineSubset.java
@@ -16,8 +16,13 @@
*/
package org.apache.commons.geometry.euclidean.twod;
+import java.util.List;
+import java.util.Objects;
+
import org.apache.commons.geometry.core.RegionLocation;
import org.apache.commons.geometry.core.partitioning.AbstractRegionEmbeddingHyperplaneSubset;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
+import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
import org.apache.commons.geometry.euclidean.oned.Vector1D;
@@ -53,8 +58,12 @@ public abstract class LineSubset extends AbstractRegionEmbeddingHyperplaneSubset
/** {@inheritDoc} */
@Override
- public EmbeddedTreeLineSubset.Builder builder() {
- return new EmbeddedTreeLineSubset.Builder(line);
+ public abstract List<LineConvexSubset> toConvex();
+
+ /** {@inheritDoc} */
+ @Override
+ public HyperplaneSubset.Builder<Vector2D> builder() {
+ return new Builder(line);
}
/** {@inheritDoc} */
@@ -153,4 +162,103 @@ public abstract class LineSubset extends AbstractRegionEmbeddingHyperplaneSubset
new Split<>(low, high) :
new Split<>(high, low);
}
+
+ /** Internal implementation of the {@link HyperplaneSubset.Builder} interface. In cases where only a single
+ * convex subset is given to the builder, this class returns the convex subset instance directly. In all other
+ * cases, an {@link EmbeddedTreeLineSubset} is used to construct the final subset.
+ */
+ private static final class Builder implements HyperplaneSubset.Builder<Vector2D> {
+ /** Line that a subset is being constructed for. */
+ private final Line line;
+
+ /** Embedded tree subset. */
+ private EmbeddedTreeLineSubset treeSubset;
+
+ /** Convex subset added as the first subset to the builder. This is returned directly if
+ * no other subsets are added.
+ */
+ private LineConvexSubset convexSubset;
+
+ /** Create a new subset builder for the given line.
+ * @param line line to build a subset for
+ */
+ Builder(final Line line) {
+ this.line = line;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void add(final HyperplaneSubset<Vector2D> sub) {
+ addInternal(sub);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void add(final HyperplaneConvexSubset<Vector2D> sub) {
+ addInternal(sub);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public LineSubset build() {
+ // return the convex subset directly if that was all we were given
+ if (convexSubset != null) {
+ return convexSubset;
+ }
+ return getTreeSubset();
+ }
+
+ /** Internal method for adding hyperplane subsets to this builder.
+ * @param sub the hyperplane subset to add; may be either convex or non-convex
+ */
+ private void addInternal(final HyperplaneSubset<Vector2D> sub) {
+ Objects.requireNonNull(sub, "Hyperplane subset must not be null");
+
+ if (sub instanceof LineConvexSubset) {
+ addConvexSubset((LineConvexSubset) sub);
+ } else if (sub instanceof EmbeddedTreeLineSubset) {
+ addTreeSubset((EmbeddedTreeLineSubset) sub);
+ } else {
+ throw new IllegalArgumentException("Unsupported hyperplane subset type: " + sub.getClass().getName());
+ }
+ }
+
+ /** Add a convex subset to the builder.
+ * @param convex convex subset to add
+ */
+ private void addConvexSubset(final LineConvexSubset convex) {
+ Lines.validateLinesEquivalent(line, convex.getLine());
+
+ if (treeSubset == null && convexSubset == null) {
+ convexSubset = convex;
+ } else {
+ getTreeSubset().add(convex);
+ }
+ }
+
+ /** Add an embedded tree subset to the builder.
+ * @param tree embedded tree subset to add
+ */
+ private void addTreeSubset(final EmbeddedTreeLineSubset tree) {
+ // no need to validate the line here since the add() method does that for us
+ getTreeSubset().add(tree);
+ }
+
+ /** Get the tree subset for the builder, creating it if needed.
+ * @return the tree subset for the builder
+ */
+ private EmbeddedTreeLineSubset getTreeSubset() {
+ if (treeSubset == null) {
+ treeSubset = new EmbeddedTreeLineSubset(line);
+
+ if (convexSubset != null) {
+ treeSubset.add(convexSubset);
+
+ convexSubset = null;
+ }
+ }
+
+ return treeSubset;
+ }
+ }
}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Lines.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Lines.java
index 5915fe0..1ac2ebe 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Lines.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Lines.java
@@ -274,4 +274,16 @@ public final class Lines {
throw new IllegalArgumentException(MessageFormat.format(
"Invalid line subset interval: {0}, {1}", Double.toString(a), Double.toString(b)));
}
+
+ /** Validate that the actual line is equivalent to the expected line, throwing an exception if not.
+ * @param expected the expected line
+ * @param actual the actual line
+ * @throws IllegalArgumentException if the actual line is not equivalent to the expected line
+ */
+ static void validateLinesEquivalent(final Line expected, final Line actual) {
+ if (!expected.eq(actual, expected.getPrecision())) {
+ throw new IllegalArgumentException("Arguments do not represent the same line. Expected " +
+ expected + " but was " + actual + ".");
+ }
+ }
}
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubsetTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubsetTest.java
index 3a323bb..5860ac3 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubsetTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubsetTest.java
@@ -22,10 +22,6 @@ import java.util.List;
import org.apache.commons.geometry.core.GeometryTestUtils;
import org.apache.commons.geometry.core.RegionLocation;
import org.apache.commons.geometry.core.Transform;
-import org.apache.commons.geometry.core.partitioning.Hyperplane;
-import org.apache.commons.geometry.core.partitioning.HyperplaneBoundedRegion;
-import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
-import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.partitioning.SplitLocation;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
@@ -412,42 +408,6 @@ public class EmbeddedTreePlaneSubsetTest {
}
@Test
- public void testBuilder() {
- // arrange
- Plane mainPlane = Planes.fromPointAndPlaneVectors(
- Vector3D.of(0, 0, 1), Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
- EmbeddedTreePlaneSubset.Builder builder = new EmbeddedTreePlaneSubset.Builder(mainPlane);
-
- ConvexArea a = ConvexArea.fromVertexLoop(
- Arrays.asList(Vector2D.ZERO, Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_Y), TEST_PRECISION);
- ConvexArea b = ConvexArea.fromVertexLoop(
- Arrays.asList(Vector2D.Unit.PLUS_X, Vector2D.of(1, 1), Vector2D.Unit.PLUS_Y), TEST_PRECISION);
-
- Plane closePlane = Planes.fromPointAndPlaneVectors(
- Vector3D.of(1e-16, 0, 1), Vector3D.of(1, 1e-16, 0), Vector3D.Unit.PLUS_Y, TEST_PRECISION);
-
- // act
- builder.add(Planes.subsetFromConvexArea(closePlane, a));
- builder.add(new EmbeddedTreePlaneSubset(closePlane, b.toTree()));
-
- EmbeddedTreePlaneSubset result = builder.build();
-
- // assert
- Assert.assertFalse(result.isFull());
- Assert.assertFalse(result.isEmpty());
- Assert.assertTrue(result.isFinite());
- Assert.assertFalse(result.isInfinite());
-
- checkPoints(result, RegionLocation.INSIDE, Vector3D.of(0.5, 0.5, 1));
- checkPoints(result, RegionLocation.OUTSIDE,
- Vector3D.of(-1, 0.5, 1), Vector3D.of(2, 0.5, 1),
- Vector3D.of(0.5, -1, 1), Vector3D.of(0.5, 2, 1));
- checkPoints(result, RegionLocation.BOUNDARY,
- Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1),
- Vector3D.of(1, 1, 1), Vector3D.of(0, 1, 1));
- }
-
- @Test
public void testSubPlaneAddMethods_validatesPlane() {
// arrange
EmbeddedTreePlaneSubset sp = new EmbeddedTreePlaneSubset(XY_PLANE, false);
@@ -466,47 +426,9 @@ public class EmbeddedTreePlaneSubsetTest {
}, IllegalArgumentException.class);
}
- @Test
- public void testBuilder_addUnknownType() {
- // arrange
- EmbeddedTreePlaneSubset.Builder sp = new EmbeddedTreePlaneSubset.Builder(XY_PLANE);
-
- // act/assert
- GeometryTestUtils.assertThrows(() -> {
- sp.add(new StubSubPlane(XY_PLANE));
- }, IllegalArgumentException.class);
- }
-
- private static void checkPoints(EmbeddedTreePlaneSubset sp, RegionLocation loc, Vector3D... pts) {
+ private static void checkPoints(EmbeddedTreePlaneSubset ps, RegionLocation loc, Vector3D... pts) {
for (Vector3D pt : pts) {
- Assert.assertEquals("Unexpected subplane location for point " + pt, loc, sp.classify(pt));
- }
- }
-
- private static class StubSubPlane extends PlaneSubset implements HyperplaneSubset<Vector3D> {
-
- StubSubPlane(Plane plane) {
- super(plane);
- }
-
- @Override
- public Split<? extends HyperplaneSubset<Vector3D>> split(Hyperplane<Vector3D> splitter) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public HyperplaneSubset<Vector3D> transform(Transform<Vector3D> transform) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public List<? extends HyperplaneConvexSubset<Vector3D>> toConvex() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public HyperplaneBoundedRegion<Vector2D> getSubspaceRegion() {
- throw new UnsupportedOperationException();
+ Assert.assertEquals("Unexpected location for point " + pt, loc, ps.classify(pt));
}
}
}
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneSubsetTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneSubsetTest.java
new file mode 100644
index 0000000..12308fe
--- /dev/null
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneSubsetTest.java
@@ -0,0 +1,281 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.threed;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.geometry.core.GeometryTestUtils;
+import org.apache.commons.geometry.core.RegionLocation;
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneBoundedRegion;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
+import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
+import org.apache.commons.geometry.core.partitioning.Split;
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.twod.ConvexArea;
+import org.apache.commons.geometry.euclidean.twod.Vector2D;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PlaneSubsetTest {
+
+ private static final double TEST_EPS = 1e-10;
+
+ private static final DoublePrecisionContext TEST_PRECISION =
+ new EpsilonDoublePrecisionContext(TEST_EPS);
+
+ private static final Plane XY_PLANE = Planes.fromPointAndPlaneVectors(Vector3D.ZERO,
+ Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
+
+ @Test
+ public void testBuilder_empty() {
+ // act
+ HyperplaneSubset.Builder<Vector3D> builder = XY_PLANE.span().builder();
+
+ PlaneSubset result = (PlaneSubset) builder.build();
+
+ // assert
+ Assert.assertSame(XY_PLANE, result.getPlane());
+
+ Assert.assertFalse(result.isFull());
+ Assert.assertTrue(result.isEmpty());
+ Assert.assertTrue(result.isFinite());
+ Assert.assertFalse(result.isInfinite());
+
+ Assert.assertEquals(0, result.getSize(), TEST_EPS);
+
+ checkPoints(result, RegionLocation.OUTSIDE, Vector3D.ZERO);
+ }
+
+ @Test
+ public void testBuilder_addSingleConvex_returnsSameInstance() {
+ // arrange
+ PlaneConvexSubset convex = Planes.subsetFromVertexLoop(Arrays.asList(
+ Vector3D.ZERO,
+ Vector3D.of(1, 0, 0),
+ Vector3D.of(0, 1, 0)
+ ), TEST_PRECISION);
+
+ // act
+ HyperplaneSubset.Builder<Vector3D> builder = XY_PLANE.span().builder();
+
+ builder.add(convex);
+
+ PlaneSubset result = (PlaneSubset) builder.build();
+
+ // assert
+ Assert.assertSame(convex, result);
+ }
+
+ @Test
+ public void testBuilder_addSingleTreeSubset() {
+ // arrange
+ ConvexArea area = ConvexArea.fromVertexLoop(Arrays.asList(
+ Vector2D.ZERO,
+ Vector2D.of(1, 0),
+ Vector2D.of(0, 1)
+ ), TEST_PRECISION);
+ EmbeddedTreePlaneSubset treeSubset = new EmbeddedTreePlaneSubset(XY_PLANE, area.toTree());
+
+ // act
+ HyperplaneSubset.Builder<Vector3D> builder = XY_PLANE.span().builder();
+
+ builder.add(treeSubset);
+
+ PlaneSubset result = (PlaneSubset) builder.build();
+
+ // assert
+ Assert.assertNotSame(treeSubset, result);
+
+ Assert.assertFalse(result.isFull());
+ Assert.assertFalse(result.isEmpty());
+ Assert.assertTrue(result.isFinite());
+ Assert.assertFalse(result.isInfinite());
+
+ Assert.assertEquals(0.5, result.getSize(), TEST_EPS);
+
+ checkPoints(result, RegionLocation.INSIDE, Vector3D.of(0.25, 0.25, 0));
+ checkPoints(result, RegionLocation.OUTSIDE,
+ Vector3D.of(0.25, 0.25, 1), Vector3D.of(0.25, 0.25, -1),
+ Vector3D.of(1, 0.25, 0), Vector3D.of(-1, 0.25, 0),
+ Vector3D.of(0.25, 1, 0), Vector3D.of(0.25, -1, 0));
+ checkPoints(result, RegionLocation.BOUNDARY, Vector3D.of(0, 0, 0));
+ }
+
+ @Test
+ public void testBuilder_addMixed_convexFirst() {
+ // arrange
+ Plane mainPlane = Planes.fromPointAndPlaneVectors(
+ Vector3D.of(0, 0, 1), Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
+
+ ConvexArea a = ConvexArea.fromVertexLoop(
+ Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(1, 1).normalize()), TEST_PRECISION);
+ ConvexArea b = ConvexArea.fromVertexLoop(
+ Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 1).normalize(), Vector2D.of(0, 1)), TEST_PRECISION);
+ ConvexArea c = ConvexArea.fromVertexLoop(
+ Arrays.asList(Vector2D.Unit.PLUS_X, Vector2D.of(1, 1), Vector2D.Unit.PLUS_Y), TEST_PRECISION);
+
+ Plane closePlane = Planes.fromPointAndPlaneVectors(
+ Vector3D.of(1e-16, 0, 1), Vector3D.of(1, 1e-16, 0), Vector3D.Unit.PLUS_Y, TEST_PRECISION);
+
+ // act
+ HyperplaneSubset.Builder<Vector3D> builder = mainPlane.span().builder();
+
+ builder.add(Planes.subsetFromConvexArea(closePlane, a));
+ builder.add(Planes.subsetFromConvexArea(closePlane, b));
+ builder.add(new EmbeddedTreePlaneSubset(closePlane, c.toTree()));
+
+ PlaneSubset result = (PlaneSubset) builder.build();
+
+ // assert
+ Assert.assertFalse(result.isFull());
+ Assert.assertFalse(result.isEmpty());
+ Assert.assertTrue(result.isFinite());
+ Assert.assertFalse(result.isInfinite());
+
+ checkPoints(result, RegionLocation.INSIDE, Vector3D.of(0.5, 0.5, 1));
+ checkPoints(result, RegionLocation.OUTSIDE,
+ Vector3D.of(-1, 0.5, 1), Vector3D.of(2, 0.5, 1),
+ Vector3D.of(0.5, -1, 1), Vector3D.of(0.5, 2, 1));
+ checkPoints(result, RegionLocation.BOUNDARY,
+ Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1),
+ Vector3D.of(1, 1, 1), Vector3D.of(0, 1, 1));
+ }
+
+ @Test
+ public void testBuilder_addMixed_treeSubsetFirst() {
+ // arrange
+ Plane mainPlane = Planes.fromPointAndPlaneVectors(
+ Vector3D.of(0, 0, 1), Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
+
+ ConvexArea a = ConvexArea.fromVertexLoop(
+ Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(1, 1).normalize()), TEST_PRECISION);
+ ConvexArea b = ConvexArea.fromVertexLoop(
+ Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 1).normalize(), Vector2D.of(0, 1)), TEST_PRECISION);
+ ConvexArea c = ConvexArea.fromVertexLoop(
+ Arrays.asList(Vector2D.Unit.PLUS_X, Vector2D.of(1, 1), Vector2D.Unit.PLUS_Y), TEST_PRECISION);
+
+ Plane closePlane = Planes.fromPointAndPlaneVectors(
+ Vector3D.of(1e-16, 0, 1), Vector3D.of(1, 1e-16, 0), Vector3D.Unit.PLUS_Y, TEST_PRECISION);
+
+ // act
+ HyperplaneSubset.Builder<Vector3D> builder = mainPlane.span().builder();
+
+ builder.add(new EmbeddedTreePlaneSubset(closePlane, c.toTree()));
+ builder.add(Planes.subsetFromConvexArea(closePlane, a));
+ builder.add(Planes.subsetFromConvexArea(closePlane, b));
+
+ PlaneSubset result = (PlaneSubset) builder.build();
+
+ // assert
+ Assert.assertFalse(result.isFull());
+ Assert.assertFalse(result.isEmpty());
+ Assert.assertTrue(result.isFinite());
+ Assert.assertFalse(result.isInfinite());
+
+ checkPoints(result, RegionLocation.INSIDE, Vector3D.of(0.5, 0.5, 1));
+ checkPoints(result, RegionLocation.OUTSIDE,
+ Vector3D.of(-1, 0.5, 1), Vector3D.of(2, 0.5, 1),
+ Vector3D.of(0.5, -1, 1), Vector3D.of(0.5, 2, 1));
+ checkPoints(result, RegionLocation.BOUNDARY,
+ Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1),
+ Vector3D.of(1, 1, 1), Vector3D.of(0, 1, 1));
+ }
+
+ @Test
+ public void testBuilder_nullArguments() {
+ // arrange
+ HyperplaneSubset.Builder<Vector3D> builder = XY_PLANE.span().builder();
+
+ // act/assert
+ GeometryTestUtils.assertThrows(() -> {
+ builder.add((HyperplaneSubset<Vector3D>) null);
+ }, NullPointerException.class, "Hyperplane subset must not be null");
+
+ GeometryTestUtils.assertThrows(() -> {
+ builder.add((HyperplaneConvexSubset<Vector3D>) null);
+ }, NullPointerException.class, "Hyperplane subset must not be null");
+ }
+
+ @Test
+ public void testBuilder_argumentsFromDifferentPlanes() {
+ // arrange
+ PlaneConvexSubset convex = Planes.subsetFromVertexLoop(Arrays.asList(
+ Vector3D.ZERO,
+ Vector3D.of(1, 0, 1),
+ Vector3D.of(0, 1, 1)
+ ), TEST_PRECISION);
+
+ HyperplaneSubset.Builder<Vector3D> builder = XY_PLANE.span().builder();
+
+ // act/assert
+ GeometryTestUtils.assertThrows(() -> {
+ builder.add(convex);
+ }, IllegalArgumentException.class);
+
+ GeometryTestUtils.assertThrows(() -> {
+ builder.add(new EmbeddedTreePlaneSubset(convex.getPlane(), convex.getSubspaceRegion().toTree()));
+ }, IllegalArgumentException.class);
+ }
+
+ @Test
+ public void testBuilder_addUnknownType() {
+ // arrange
+ HyperplaneSubset.Builder<Vector3D> builder = XY_PLANE.span().builder();
+
+ // act/assert
+ GeometryTestUtils.assertThrows(() -> {
+ builder.add(new StubSubPlane(Planes.fromNormal(Vector3D.Unit.PLUS_Y, TEST_PRECISION)));
+ }, IllegalArgumentException.class);
+ }
+
+ private static void checkPoints(PlaneSubset ps, RegionLocation loc, Vector3D... pts) {
+ for (Vector3D pt : pts) {
+ Assert.assertEquals("Unexpected location for point " + pt, loc, ps.classify(pt));
+ }
+ }
+
+ private static class StubSubPlane extends PlaneSubset implements HyperplaneSubset<Vector3D> {
+
+ StubSubPlane(Plane plane) {
+ super(plane);
+ }
+
+ @Override
+ public Split<? extends HyperplaneSubset<Vector3D>> split(Hyperplane<Vector3D> splitter) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public HyperplaneSubset<Vector3D> transform(Transform<Vector3D> transform) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<PlaneConvexSubset> toConvex() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public HyperplaneBoundedRegion<Vector2D> getSubspaceRegion() {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/EmbeddedTreeLineSubsetTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/EmbeddedTreeLineSubsetTest.java
index 2143274..d01a0e3 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/EmbeddedTreeLineSubsetTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/EmbeddedTreeLineSubsetTest.java
@@ -19,12 +19,6 @@ package org.apache.commons.geometry.euclidean.twod;
import java.util.List;
import org.apache.commons.geometry.core.GeometryTestUtils;
-import org.apache.commons.geometry.core.RegionLocation;
-import org.apache.commons.geometry.core.Transform;
-import org.apache.commons.geometry.core.partitioning.Hyperplane;
-import org.apache.commons.geometry.core.partitioning.HyperplaneBoundedRegion;
-import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
-import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.partitioning.SplitLocation;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
@@ -32,8 +26,6 @@ import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
import org.apache.commons.geometry.euclidean.oned.Interval;
import org.apache.commons.geometry.euclidean.oned.RegionBSPTree1D;
-import org.apache.commons.geometry.euclidean.oned.Vector1D;
-import org.apache.commons.geometry.euclidean.twod.EmbeddedTreeLineSubset.Builder;
import org.apache.commons.numbers.angle.PlaneAngleRadians;
import org.junit.Assert;
import org.junit.Test;
@@ -474,205 +466,6 @@ public class EmbeddedTreeLineSubsetTest {
}
@Test
- public void testBuilder_instanceMethod() {
- // arrange
- Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
- Builder builder = new EmbeddedTreeLineSubset(line).builder();
-
- // act
- EmbeddedTreeLineSubset subset = builder.build();
-
- // assert
- Assert.assertFalse(subset.isFull());
- Assert.assertTrue(subset.isEmpty());
-
- List<LineConvexSubset> segments = subset.toConvex();
- Assert.assertEquals(0, segments.size());
-
- Assert.assertSame(line, subset.getLine());
- Assert.assertSame(line, subset.getHyperplane());
- Assert.assertSame(TEST_PRECISION, subset.getPrecision());
- }
-
- @Test
- public void testBuilder_createEmpty() {
- // arrange
- Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
-
- Builder builder = new Builder(line);
-
- // act
- EmbeddedTreeLineSubset subset = builder.build();
-
- // assert
- Assert.assertFalse(subset.isFull());
- Assert.assertTrue(subset.isEmpty());
-
- List<LineConvexSubset> segments = subset.toConvex();
- Assert.assertEquals(0, segments.size());
- }
-
- @Test
- public void testBuilder_addConvex() {
- // arrange
- Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
- Line otherLine = Lines.fromPointAndAngle(Vector2D.of(0, 1), 1e-11, TEST_PRECISION);
-
- Builder builder = new Builder(line);
-
- // act
- builder.add(Lines.subsetFromInterval(line, 2, 4));
- builder.add(Lines.subsetFromInterval(otherLine, 1, 3));
- builder.add(Lines.segmentFromPoints(Vector2D.of(-4, 1), Vector2D.of(-1, 1), TEST_PRECISION));
-
- EmbeddedTreeLineSubset subset = builder.build();
-
- // assert
- Assert.assertFalse(subset.isFull());
- Assert.assertFalse(subset.isEmpty());
-
- List<LineConvexSubset> segments = subset.toConvex();
-
- EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-4, 1), segments.get(0).getStartPoint(), TEST_EPS);
- EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 1), segments.get(0).getEndPoint(), TEST_EPS);
-
- EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), segments.get(1).getStartPoint(), TEST_EPS);
- EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(4, 1), segments.get(1).getEndPoint(), TEST_EPS);
- }
-
- @Test
- public void testBuilder_addNonConvex() {
- // arrange
- Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
-
- EmbeddedTreeLineSubset a = new EmbeddedTreeLineSubset(line);
- RegionBSPTree1D aTree = a.getSubspaceRegion();
- aTree.add(Interval.max(-3, TEST_PRECISION));
- aTree.add(Interval.of(1, 2, TEST_PRECISION));
-
- EmbeddedTreeLineSubset b = new EmbeddedTreeLineSubset(line);
- RegionBSPTree1D bTree = b.getSubspaceRegion();
- bTree.add(Interval.of(2, 4, TEST_PRECISION));
- bTree.add(Interval.of(-4, -2, TEST_PRECISION));
-
- Builder builder = new Builder(line);
-
- int aTreeCount = aTree.count();
- int bTreeCount = bTree.count();
-
- // act
- builder.add(a);
- builder.add(b);
-
- EmbeddedTreeLineSubset subset = builder.build();
-
- // assert
- Assert.assertFalse(subset.isFull());
- Assert.assertFalse(subset.isEmpty());
-
- List<LineConvexSubset> segments = subset.toConvex();
-
- Assert.assertEquals(2, segments.size());
-
- Assert.assertNull(segments.get(0).getStartPoint());
- EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 1), segments.get(0).getEndPoint(), TEST_EPS);
-
- EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), segments.get(1).getStartPoint(), TEST_EPS);
- EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(4, 1), segments.get(1).getEndPoint(), TEST_EPS);
-
- Assert.assertEquals(aTreeCount, aTree.count());
- Assert.assertEquals(bTreeCount, bTree.count());
- }
-
- @Test
- public void testBuilder_argumentsFromDifferentLine() {
- // arrange
- Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
- Line otherLine = Lines.fromPointAndAngle(Vector2D.of(0, 1), 1e-2, TEST_PRECISION);
-
- Builder builder = new Builder(line);
-
- // act/assert
- GeometryTestUtils.assertThrows(() -> {
- builder.add(Lines.subsetFromInterval(otherLine, 0, 1));
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- builder.add(new EmbeddedTreeLineSubset(otherLine));
- }, IllegalArgumentException.class);
- }
-
- @Test
- public void testBuilder_unknownSubsetType() {
- // arrange
- Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
-
- LineSubset unknownType = new LineSubset(line) {
- @Override
- public boolean isInfinite() {
- return false;
- }
-
- @Override
- public boolean isFinite() {
- return true;
- }
-
- @Override
- public List<? extends HyperplaneConvexSubset<Vector2D>> toConvex() {
- return null;
- }
-
- @Override
- public HyperplaneBoundedRegion<Vector1D> getSubspaceRegion() {
- return null;
- }
-
- @Override
- public Split<? extends HyperplaneSubset<Vector2D>> split(Hyperplane<Vector2D> splitter) {
- return null;
- }
-
- @Override
- public HyperplaneSubset<Vector2D> transform(Transform<Vector2D> transform) {
- return null;
- }
-
- @Override
- public Vector2D closest(Vector2D point) {
- return null;
- }
-
- @Override
- public boolean isFull() {
- return false;
- }
-
- @Override
- public boolean isEmpty() {
- return false;
- }
-
- @Override
- public double getSize() {
- return 0;
- }
-
- @Override
- RegionLocation classifyAbscissa(double abscissa) {
- return null;
- }
- };
-
- Builder builder = new Builder(line);
-
- // act/assert
- GeometryTestUtils.assertThrows(() -> {
- builder.add(unknownType);
- }, IllegalArgumentException.class);
- }
-
- @Test
public void testToString() {
// arrange
EmbeddedTreeLineSubset sub = new EmbeddedTreeLineSubset(DEFAULT_TEST_LINE);
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSpanTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSpanningSubsetTest.java
similarity index 99%
rename from commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSpanTest.java
rename to commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSpanningSubsetTest.java
index 4c1dd4a..b24a330 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSpanTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSpanningSubsetTest.java
@@ -26,7 +26,7 @@ import org.apache.commons.geometry.euclidean.oned.Interval;
import org.junit.Assert;
import org.junit.Test;
-public class LineSpanTest {
+public class LineSpanningSubsetTest {
private static final double TEST_EPS = 1e-10;
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSubsetTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSubsetTest.java
new file mode 100644
index 0000000..1b313a6
--- /dev/null
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSubsetTest.java
@@ -0,0 +1,312 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.twod;
+
+import java.util.List;
+
+import org.apache.commons.geometry.core.GeometryTestUtils;
+import org.apache.commons.geometry.core.RegionLocation;
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneBoundedRegion;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
+import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
+import org.apache.commons.geometry.core.partitioning.Split;
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
+import org.apache.commons.geometry.euclidean.oned.Interval;
+import org.apache.commons.geometry.euclidean.oned.RegionBSPTree1D;
+import org.apache.commons.geometry.euclidean.oned.Vector1D;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class LineSubsetTest {
+
+ private static final double TEST_EPS = 1e-10;
+
+ private static final DoublePrecisionContext TEST_PRECISION =
+ new EpsilonDoublePrecisionContext(TEST_EPS);
+
+ @Test
+ public void testBuilder_empty() {
+ // arrange
+ Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+
+ HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+
+ // act
+ LineSubset subset = (LineSubset) builder.build();
+
+ // assert
+ Assert.assertFalse(subset.isFull());
+ Assert.assertTrue(subset.isEmpty());
+
+ List<LineConvexSubset> segments = subset.toConvex();
+ Assert.assertEquals(0, segments.size());
+ }
+
+ @Test
+ public void testBuilder_addSingleConvex_usesSameInstance() {
+ // arrange
+ Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+
+ HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+ LineConvexSubset convex = Lines.subsetFromInterval(line, 2, 4);
+
+ // act
+ builder.add(convex);
+
+ LineSubset subset = (LineSubset) builder.build();
+
+ // assert
+ Assert.assertSame(convex, subset);
+ }
+
+ @Test
+ public void testBuilder_addConvex() {
+ // arrange
+ Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+ Line otherLine = Lines.fromPointAndAngle(Vector2D.of(0, 1), 1e-11, TEST_PRECISION);
+
+ HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+
+ // act
+ builder.add(Lines.subsetFromInterval(line, 2, 4));
+ builder.add(Lines.subsetFromInterval(otherLine, 1, 3));
+ builder.add(Lines.segmentFromPoints(Vector2D.of(-4, 1), Vector2D.of(-1, 1), TEST_PRECISION));
+
+ LineSubset subset = (LineSubset) builder.build();
+
+ // assert
+ Assert.assertFalse(subset.isFull());
+ Assert.assertFalse(subset.isEmpty());
+
+ List<LineConvexSubset> segments = subset.toConvex();
+ Assert.assertEquals(2, segments.size());
+
+ EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-4, 1), segments.get(0).getStartPoint(), TEST_EPS);
+ EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 1), segments.get(0).getEndPoint(), TEST_EPS);
+
+ EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), segments.get(1).getStartPoint(), TEST_EPS);
+ EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(4, 1), segments.get(1).getEndPoint(), TEST_EPS);
+ }
+
+ @Test
+ public void testBuilder_addTreeSubset() {
+ // arrange
+ Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+
+ EmbeddedTreeLineSubset a = new EmbeddedTreeLineSubset(line);
+ RegionBSPTree1D aTree = a.getSubspaceRegion();
+ aTree.add(Interval.max(-3, TEST_PRECISION));
+ aTree.add(Interval.of(1, 2, TEST_PRECISION));
+
+ EmbeddedTreeLineSubset b = new EmbeddedTreeLineSubset(line);
+ RegionBSPTree1D bTree = b.getSubspaceRegion();
+ bTree.add(Interval.of(2, 4, TEST_PRECISION));
+ bTree.add(Interval.of(-4, -2, TEST_PRECISION));
+
+ HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+
+ int aTreeCount = aTree.count();
+ int bTreeCount = bTree.count();
+
+ // act
+ builder.add(a);
+ builder.add(b);
+
+ LineSubset subset = (LineSubset) builder.build();
+
+ // assert
+ Assert.assertFalse(subset.isFull());
+ Assert.assertFalse(subset.isEmpty());
+
+ List<LineConvexSubset> segments = subset.toConvex();
+
+ Assert.assertEquals(2, segments.size());
+
+ Assert.assertNull(segments.get(0).getStartPoint());
+ EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 1), segments.get(0).getEndPoint(), TEST_EPS);
+
+ EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), segments.get(1).getStartPoint(), TEST_EPS);
+ EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(4, 1), segments.get(1).getEndPoint(), TEST_EPS);
+
+ Assert.assertEquals(aTreeCount, aTree.count());
+ Assert.assertEquals(bTreeCount, bTree.count());
+ }
+
+ @Test
+ public void testBuilder_addMixed_convexFirst() {
+ // arrange
+ Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+ Line otherLine = Lines.fromPointAndAngle(Vector2D.of(0, 1), 1e-11, TEST_PRECISION);
+
+ HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+
+ // act
+ builder.add(Lines.subsetFromInterval(line, 2, 4));
+
+ EmbeddedTreeLineSubset treeSubset = new EmbeddedTreeLineSubset(otherLine);
+ treeSubset.add(Lines.subsetFromInterval(otherLine, 1, 3));
+ builder.add(treeSubset);
+
+ LineSubset subset = (LineSubset) builder.build();
+
+ // assert
+ Assert.assertFalse(subset.isFull());
+ Assert.assertFalse(subset.isEmpty());
+
+ List<LineConvexSubset> segments = subset.toConvex();
+ Assert.assertEquals(1, segments.size());
+
+ EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), segments.get(0).getStartPoint(), TEST_EPS);
+ EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(4, 1), segments.get(0).getEndPoint(), TEST_EPS);
+ }
+
+ @Test
+ public void testBuilder_addMixed_treeSubsetFirst() {
+ // arrange
+ Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+ Line otherLine = Lines.fromPointAndAngle(Vector2D.of(0, 1), 1e-11, TEST_PRECISION);
+
+ HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+
+ // act
+ EmbeddedTreeLineSubset treeSubset = new EmbeddedTreeLineSubset(otherLine);
+ treeSubset.add(Lines.subsetFromInterval(otherLine, 1, 3));
+ builder.add(treeSubset);
+
+ builder.add(Lines.subsetFromInterval(line, 2, 4));
+
+ LineSubset subset = (LineSubset) builder.build();
+
+ // assert
+ Assert.assertFalse(subset.isFull());
+ Assert.assertFalse(subset.isEmpty());
+
+ List<LineConvexSubset> segments = subset.toConvex();
+ Assert.assertEquals(1, segments.size());
+
+ EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), segments.get(0).getStartPoint(), TEST_EPS);
+ EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(4, 1), segments.get(0).getEndPoint(), TEST_EPS);
+ }
+
+ @Test
+ public void testBuilder_nullArgs() {
+ // arrange
+ Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+ HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+
+ // act/assert
+ GeometryTestUtils.assertThrows(() -> {
+ builder.add((HyperplaneSubset<Vector2D>) null);
+ }, NullPointerException.class, "Hyperplane subset must not be null");
+
+ GeometryTestUtils.assertThrows(() -> {
+ builder.add((HyperplaneConvexSubset<Vector2D>) null);
+ }, NullPointerException.class, "Hyperplane subset must not be null");
+ }
+
+ @Test
+ public void testBuilder_argumentsFromDifferentLine() {
+ // arrange
+ Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+ Line otherLine = Lines.fromPointAndAngle(Vector2D.of(0, 1), 1e-2, TEST_PRECISION);
+
+ HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+
+ // act/assert
+ GeometryTestUtils.assertThrows(() -> {
+ builder.add(Lines.subsetFromInterval(otherLine, 0, 1));
+ }, IllegalArgumentException.class);
+
+ GeometryTestUtils.assertThrows(() -> {
+ builder.add(new EmbeddedTreeLineSubset(otherLine));
+ }, IllegalArgumentException.class);
+ }
+
+ @Test
+ public void testBuilder_unknownSubsetType() {
+ // arrange
+ Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+
+ LineSubset unknownType = new LineSubset(line) {
+ @Override
+ public boolean isInfinite() {
+ return false;
+ }
+
+ @Override
+ public boolean isFinite() {
+ return true;
+ }
+
+ @Override
+ public List<LineConvexSubset> toConvex() {
+ return null;
+ }
+
+ @Override
+ public HyperplaneBoundedRegion<Vector1D> getSubspaceRegion() {
+ return null;
+ }
+
+ @Override
+ public Split<? extends HyperplaneSubset<Vector2D>> split(Hyperplane<Vector2D> splitter) {
+ return null;
+ }
+
+ @Override
+ public HyperplaneSubset<Vector2D> transform(Transform<Vector2D> transform) {
+ return null;
+ }
+
+ @Override
+ public Vector2D closest(Vector2D point) {
+ return null;
+ }
+
+ @Override
+ public boolean isFull() {
+ return false;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public double getSize() {
+ return 0;
+ }
+
+ @Override
+ RegionLocation classifyAbscissa(double abscissa) {
+ return null;
+ }
+ };
+
+ HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+
+ // act/assert
+ GeometryTestUtils.assertThrows(() -> {
+ builder.add(unknownType);
+ }, IllegalArgumentException.class);
+ }
+}
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/CutAngle.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/CutAngle.java
index 81e5a9b..ec3e5dc 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/CutAngle.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/CutAngle.java
@@ -239,7 +239,7 @@ public final class CutAngle extends AbstractHyperplane<Point1S> {
* this is effectively a stub implementation, its main use being to allow for the correct functioning of
* partitioning code.
*/
- private static class CutAngleConvexSubset implements HyperplaneConvexSubset<Point1S> {
+ private static final class CutAngleConvexSubset implements HyperplaneConvexSubset<Point1S> {
/** The hyperplane containing for this instance. */
private final CutAngle hyperplane;
@@ -380,7 +380,7 @@ public final class CutAngle extends AbstractHyperplane<Point1S> {
* a stub implementation since there are no subspaces of 1D space. Its primary use is to allow
* for the correct functioning of partitioning code.
*/
- public static final class CutAngleSubsetBuilder implements HyperplaneSubset.Builder<Point1S> {
+ private static final class CutAngleSubsetBuilder implements HyperplaneSubset.Builder<Point1S> {
/** Base hyperplane subset for the builder. */
private final CutAngleConvexSubset base;
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EmbeddedTreeGreatCircleSubset.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EmbeddedTreeGreatCircleSubset.java
index 71c7b99..b9054af 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EmbeddedTreeGreatCircleSubset.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EmbeddedTreeGreatCircleSubset.java
@@ -21,8 +21,6 @@ import java.util.stream.Collectors;
import org.apache.commons.geometry.core.Transform;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
-import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
-import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.partitioning.SplitLocation;
import org.apache.commons.geometry.spherical.oned.CutAngle;
@@ -136,7 +134,7 @@ public final class EmbeddedTreeGreatCircleSubset extends GreatCircleSubset {
* a great circle equivalent to this instance
*/
public void add(final GreatArc arc) {
- validateGreatCircle(arc.getCircle());
+ GreatCircles.validateGreatCirclesEquivalent(getCircle(), arc.getCircle());
region.add(arc.getSubspaceRegion());
}
@@ -148,7 +146,7 @@ public final class EmbeddedTreeGreatCircleSubset extends GreatCircleSubset {
* a great circle equivalent to this instance
*/
public void add(final EmbeddedTreeGreatCircleSubset subcircle) {
- validateGreatCircle(subcircle.getCircle());
+ GreatCircles.validateGreatCirclesEquivalent(getCircle(), subcircle.getCircle());
region.union(subcircle.getSubspaceRegion());
}
@@ -167,66 +165,4 @@ public final class EmbeddedTreeGreatCircleSubset extends GreatCircleSubset {
return sb.toString();
}
-
- /** Validate that the given great circle is equivalent to the circle
- * defining this instance.
- * @param inputCircle the great circle to validate
- * @throws IllegalArgumentException if the argument is not equivalent
- * to the great circle for this instance
- */
- private void validateGreatCircle(final GreatCircle inputCircle) {
- final GreatCircle circle = getCircle();
-
- if (!circle.eq(inputCircle, circle.getPrecision())) {
- throw new IllegalArgumentException("Argument is not on the same " +
- "great circle. Expected " + circle + " but was " +
- inputCircle);
- }
- }
-
- /** {@link HyperplaneSubset.Builder} implementation for great circle subsets.
- */
- public static final class Builder implements HyperplaneSubset.Builder<Point2S> {
-
- /** SubGreatCircle instance created by this builder. */
- private final EmbeddedTreeGreatCircleSubset subcircle;
-
- /** Construct a new instance for building regions for the given great circle.
- * @param circle the underlying great circle for the region
- */
- public Builder(final GreatCircle circle) {
- this.subcircle = new EmbeddedTreeGreatCircleSubset(circle);
- }
-
- /** {@inheritDoc} */
- @Override
- public void add(final HyperplaneSubset<Point2S> sub) {
- addInternal(sub);
- }
-
- /** {@inheritDoc} */
- @Override
- public void add(final HyperplaneConvexSubset<Point2S> sub) {
- addInternal(sub);
- }
-
- /** {@inheritDoc} */
- @Override
- public EmbeddedTreeGreatCircleSubset build() {
- return subcircle;
- }
-
- /** Internal method for adding hyperplane subsets to this builder.
- * @param sub the hyperplane subset to add; either convex or non-convex
- */
- private void addInternal(final HyperplaneSubset<Point2S> sub) {
- if (sub instanceof GreatArc) {
- subcircle.add((GreatArc) sub);
- } else if (sub instanceof EmbeddedTreeGreatCircleSubset) {
- subcircle.add((EmbeddedTreeGreatCircleSubset) sub);
- } else {
- throw new IllegalArgumentException("Unsupported hyperplane subset type: " + sub.getClass().getName());
- }
- }
- }
}
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircleSubset.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircleSubset.java
index ab8a76b..5fbf0f5 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircleSubset.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircleSubset.java
@@ -16,7 +16,12 @@
*/
package org.apache.commons.geometry.spherical.twod;
+import java.util.List;
+import java.util.Objects;
+
import org.apache.commons.geometry.core.partitioning.AbstractRegionEmbeddingHyperplaneSubset;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
+import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
import org.apache.commons.geometry.spherical.oned.Point1S;
@@ -51,8 +56,12 @@ public abstract class GreatCircleSubset
/** {@inheritDoc} */
@Override
- public EmbeddedTreeGreatCircleSubset.Builder builder() {
- return new EmbeddedTreeGreatCircleSubset.Builder(circle);
+ public abstract List<GreatArc> toConvex();
+
+ /** {@inheritDoc} */
+ @Override
+ public HyperplaneSubset.Builder<Point2S> builder() {
+ return new Builder(circle);
}
/** Return the object used to perform floating point comparisons, which is the
@@ -62,4 +71,103 @@ public abstract class GreatCircleSubset
public DoublePrecisionContext getPrecision() {
return circle.getPrecision();
}
+
+ /** Internal implementation of the {@link HyperplaneSubset.Builder} interface. In cases where only a single
+ * convex subset is given to the builder, this class returns the convex subset instance directly. In all other
+ * cases, an {@link EmbeddedTreeGreatCircleSubset} is used to construct the final subset.
+ */
+ private static final class Builder implements HyperplaneSubset.Builder<Point2S> {
+ /** Great circle that a subset is being constructed for. */
+ private final GreatCircle circle;
+
+ /** Embedded tree subset. */
+ private EmbeddedTreeGreatCircleSubset treeSubset;
+
+ /** Convex subset added as the first subset to the builder. This is returned directly if
+ * no other subsets are added.
+ */
+ private GreatArc convexSubset;
+
+ /** Create a new subset builder for the given great circle.
+ * @param circle great circle to build a subset for
+ */
+ Builder(final GreatCircle circle) {
+ this.circle = circle;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void add(final HyperplaneSubset<Point2S> sub) {
+ addInternal(sub);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void add(final HyperplaneConvexSubset<Point2S> sub) {
+ addInternal(sub);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public GreatCircleSubset build() {
+ // return the convex subset directly if that was all we were given
+ if (convexSubset != null) {
+ return convexSubset;
+ }
+ return getTreeSubset();
+ }
+
+ /** Internal method for adding hyperplane subsets to this builder.
+ * @param sub the hyperplane subset to add; may be either convex or non-convex
+ */
+ private void addInternal(final HyperplaneSubset<Point2S> sub) {
+ Objects.requireNonNull(sub, "Hyperplane subset must not be null");
+
+ if (sub instanceof GreatArc) {
+ addConvexSubset((GreatArc) sub);
+ } else if (sub instanceof EmbeddedTreeGreatCircleSubset) {
+ addTreeSubset((EmbeddedTreeGreatCircleSubset) sub);
+ } else {
+ throw new IllegalArgumentException("Unsupported hyperplane subset type: " + sub.getClass().getName());
+ }
+ }
+
+ /** Add a convex subset to the builder.
+ * @param convex convex subset to add
+ */
+ private void addConvexSubset(final GreatArc convex) {
+ GreatCircles.validateGreatCirclesEquivalent(circle, convex.getCircle());
+
+ if (treeSubset == null && convexSubset == null) {
+ convexSubset = convex;
+ } else {
+ getTreeSubset().add(convex);
+ }
+ }
+
+ /** Add an embedded tree subset to the builder.
+ * @param tree embedded tree subset to add
+ */
+ private void addTreeSubset(final EmbeddedTreeGreatCircleSubset tree) {
+ // no need to validate the line here since the add() method does that for us
+ getTreeSubset().add(tree);
+ }
+
+ /** Get the tree subset for the builder, creating it if needed.
+ * @return the tree subset for the builder
+ */
+ private EmbeddedTreeGreatCircleSubset getTreeSubset() {
+ if (treeSubset == null) {
+ treeSubset = new EmbeddedTreeGreatCircleSubset(circle);
+
+ if (convexSubset != null) {
+ treeSubset.add(convexSubset);
+
+ convexSubset = null;
+ }
+ }
+
+ return treeSubset;
+ }
+ }
}
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircles.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircles.java
index 1bec679..44ce6f9 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircles.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircles.java
@@ -127,4 +127,18 @@ public final class GreatCircles {
public static GreatArc arcFromInterval(final GreatCircle circle, final AngularInterval.Convex interval) {
return new GreatArc(circle, interval);
}
+
+ /** Validate that the actual great circle is equivalent to the expected great circle,
+ * throwing an exception if not.
+ * @param expected the expected great circle
+ * @param actual the actual great circle
+ * @throws IllegalArgumentException if the actual great circle is not equivalent to the
+ * expected great circle
+ */
+ static void validateGreatCirclesEquivalent(final GreatCircle expected, final GreatCircle actual) {
+ if (!expected.eq(actual, expected.getPrecision())) {
+ throw new IllegalArgumentException("Arguments do not represent the same great circle. Expected " +
+ expected + " but was " + actual + ".");
+ }
+ }
}
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/EmbeddedTreeSubGreatCircleTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/EmbeddedTreeSubGreatCircleTest.java
index a05c729..742f2fe 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/EmbeddedTreeSubGreatCircleTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/EmbeddedTreeSubGreatCircleTest.java
@@ -20,9 +20,6 @@ import java.util.List;
import org.apache.commons.geometry.core.GeometryTestUtils;
import org.apache.commons.geometry.core.RegionLocation;
-import org.apache.commons.geometry.core.Transform;
-import org.apache.commons.geometry.core.partitioning.Hyperplane;
-import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.partitioning.SplitLocation;
@@ -386,57 +383,6 @@ public class EmbeddedTreeSubGreatCircleTest {
}
@Test
- public void testBuilder() {
- // arrange
- GreatCircle circle = GreatCircles.fromPoints(Point2S.MINUS_K, Point2S.MINUS_J, TEST_PRECISION);
-
- EmbeddedTreeGreatCircleSubset sub = new EmbeddedTreeGreatCircleSubset(circle);
-
- RegionBSPTree1S region = RegionBSPTree1S.empty();
- region.add(AngularInterval.of(PlaneAngleRadians.PI, 1.25 * PlaneAngleRadians.PI, TEST_PRECISION));
- region.add(AngularInterval.of(0.25 * PlaneAngleRadians.PI, PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION));
-
- // act
- EmbeddedTreeGreatCircleSubset.Builder builder = sub.builder();
-
- builder.add(new EmbeddedTreeGreatCircleSubset(circle, region));
- builder.add(circle.arc(1.5 * PlaneAngleRadians.PI, 0.25 * PlaneAngleRadians.PI));
-
- EmbeddedTreeGreatCircleSubset result = builder.build();
-
- // assert
- List<GreatArc> arcs = result.toConvex();
-
- Assert.assertEquals(2, arcs.size());
- checkArc(arcs.get(0), Point2S.of(PlaneAngleRadians.PI_OVER_TWO, 0), Point2S.of(PlaneAngleRadians.PI_OVER_TWO, 0.25 * PlaneAngleRadians.PI));
- checkArc(arcs.get(1), Point2S.PLUS_J, Point2S.MINUS_J);
- }
-
- @Test
- public void testBuilder_invalidArgs() {
- // arrange
- GreatCircle circle = GreatCircles.fromPoints(Point2S.MINUS_K, Point2S.MINUS_J, TEST_PRECISION);
- GreatCircle otherCircle = GreatCircles.fromPoints(Point2S.PLUS_I, Point2S.PLUS_J, TEST_PRECISION);
-
- EmbeddedTreeGreatCircleSubset sub = new EmbeddedTreeGreatCircleSubset(circle);
-
- EmbeddedTreeGreatCircleSubset.Builder builder = sub.builder();
-
- // act/assert
- GeometryTestUtils.assertThrows(() -> {
- builder.add(otherCircle.span());
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- builder.add(new EmbeddedTreeGreatCircleSubset(otherCircle));
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- builder.add(new UnknownHyperplaneSubset());
- }, IllegalArgumentException.class);
- }
-
- @Test
public void testToString() {
// arrange
GreatCircle circle = GreatCircles.fromPoints(Point2S.PLUS_I, Point2S.PLUS_J, TEST_PRECISION);
@@ -461,67 +407,4 @@ public class EmbeddedTreeSubGreatCircleTest {
SphericalTestUtils.assertPointsEq(start, arc.getStartPoint(), TEST_EPS);
SphericalTestUtils.assertPointsEq(end, arc.getEndPoint(), TEST_EPS);
}
-
- private static class UnknownHyperplaneSubset implements HyperplaneSubset<Point2S> {
-
- @Override
- public Split<? extends HyperplaneSubset<Point2S>> split(Hyperplane<Point2S> splitter) {
- return null;
- }
-
- @Override
- public Hyperplane<Point2S> getHyperplane() {
- return null;
- }
-
- @Override
- public boolean isFull() {
- return false;
- }
-
- @Override
- public boolean isEmpty() {
- return false;
- }
-
- @Override
- public boolean isInfinite() {
- return false;
- }
-
- @Override
- public boolean isFinite() {
- return false;
- }
-
- @Override
- public double getSize() {
- return 0;
- }
-
- @Override
- public RegionLocation classify(Point2S point) {
- return null;
- }
-
- @Override
- public Point2S closest(Point2S point) {
- return null;
- }
-
- @Override
- public Builder<Point2S> builder() {
- return null;
- }
-
- @Override
- public HyperplaneSubset<Point2S> transform(Transform<Point2S> transform) {
- return null;
- }
-
- @Override
- public List<? extends HyperplaneConvexSubset<Point2S>> toConvex() {
- return null;
- }
- }
}
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/GreatCircleSubsetTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/GreatCircleSubsetTest.java
new file mode 100644
index 0000000..3f99b10
--- /dev/null
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/GreatCircleSubsetTest.java
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.spherical.twod;
+
+import java.util.List;
+
+import org.apache.commons.geometry.core.GeometryTestUtils;
+import org.apache.commons.geometry.core.RegionLocation;
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
+import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
+import org.apache.commons.geometry.core.partitioning.Split;
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.geometry.spherical.SphericalTestUtils;
+import org.apache.commons.geometry.spherical.oned.AngularInterval;
+import org.apache.commons.geometry.spherical.oned.RegionBSPTree1S;
+import org.apache.commons.numbers.angle.PlaneAngleRadians;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class GreatCircleSubsetTest {
+
+ private static final double TEST_EPS = 1e-10;
+
+ private static final DoublePrecisionContext TEST_PRECISION =
+ new EpsilonDoublePrecisionContext(TEST_EPS);
+
+ private static final GreatCircle XY_CIRCLE = GreatCircles.fromPoleAndU(
+ Vector3D.Unit.PLUS_Z, Vector3D.Unit.PLUS_X, TEST_PRECISION);
+
+ @Test
+ public void testBuilder_empty() {
+ // act
+ HyperplaneSubset.Builder<Point2S> builder = XY_CIRCLE.span().builder();
+
+ GreatCircleSubset result = (GreatCircleSubset) builder.build();
+
+ // assert
+ Assert.assertFalse(result.isFull());
+ Assert.assertTrue(result.isEmpty());
+ Assert.assertFalse(result.isInfinite());
+ Assert.assertTrue(result.isFinite());
+
+ Assert.assertEquals(0, result.getSize(), TEST_EPS);
+ }
+
+ @Test
+ public void testBuilder_addSingleConvex_returnsSameInstance() {
+ // arrange
+ GreatArc convex = XY_CIRCLE.arc(0, 1);
+
+ // act
+ HyperplaneSubset.Builder<Point2S> builder = XY_CIRCLE.span().builder();
+
+ builder.add(convex);
+
+ GreatCircleSubset result = (GreatCircleSubset) builder.build();
+
+ // assert
+ Assert.assertSame(convex, result);
+ }
+
+ @Test
+ public void testBuilder_addSingleTreeSubset() {
+ // arrange
+ GreatCircle circle = GreatCircles.fromPoints(Point2S.MINUS_K, Point2S.PLUS_J, TEST_PRECISION);
+
+ RegionBSPTree1S region = RegionBSPTree1S.empty();
+ region.add(AngularInterval.of(PlaneAngleRadians.PI, 1.5 * PlaneAngleRadians.PI, TEST_PRECISION));
+ region.add(AngularInterval.of(0, PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION));
+
+ EmbeddedTreeGreatCircleSubset sub = new EmbeddedTreeGreatCircleSubset(circle, region);
+
+ // act
+ HyperplaneSubset.Builder<Point2S> builder = circle.span().builder();
+
+ builder.add(sub);
+
+ GreatCircleSubset result = (GreatCircleSubset) builder.build();
+
+ // assert
+ Assert.assertNotSame(sub, result);
+
+ List<GreatArc> arcs = result.toConvex();
+
+ Assert.assertEquals(2, arcs.size());
+ checkArc(arcs.get(0), Point2S.MINUS_K, Point2S.PLUS_J);
+ checkArc(arcs.get(1), Point2S.PLUS_K, Point2S.MINUS_J);
+ }
+
+ @Test
+ public void testBuilder_addMixed_convexFirst() {
+ // arrange
+ GreatCircle circle = GreatCircles.fromPoints(Point2S.MINUS_K, Point2S.MINUS_J, TEST_PRECISION);
+
+ RegionBSPTree1S region = RegionBSPTree1S.empty();
+ region.add(AngularInterval.of(PlaneAngleRadians.PI, 1.25 * PlaneAngleRadians.PI, TEST_PRECISION));
+ region.add(AngularInterval.of(0.25 * PlaneAngleRadians.PI, PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION));
+
+ EmbeddedTreeGreatCircleSubset sub = new EmbeddedTreeGreatCircleSubset(circle, region);
+
+ // act
+ HyperplaneSubset.Builder<Point2S> builder = circle.span().builder();
+
+ builder.add(circle.arc(1.5 * PlaneAngleRadians.PI, 0.25 * PlaneAngleRadians.PI));
+ builder.add(circle.arc(1.6 * PlaneAngleRadians.PI, 0.2 * PlaneAngleRadians.PI));
+ builder.add(sub);
+
+ GreatCircleSubset result = (GreatCircleSubset) builder.build();
+
+ // assert
+ List<GreatArc> arcs = result.toConvex();
+
+ Assert.assertEquals(2, arcs.size());
+ checkArc(arcs.get(0), Point2S.of(PlaneAngleRadians.PI_OVER_TWO, 0), Point2S.of(PlaneAngleRadians.PI_OVER_TWO, 0.25 * PlaneAngleRadians.PI));
+ checkArc(arcs.get(1), Point2S.PLUS_J, Point2S.MINUS_J);
+ }
+
+ @Test
+ public void testBuilder_addMixed_treeSubsetFirst() {
+ // arrange
+ GreatCircle circle = GreatCircles.fromPoints(Point2S.MINUS_K, Point2S.MINUS_J, TEST_PRECISION);
+
+ RegionBSPTree1S region = RegionBSPTree1S.empty();
+ region.add(AngularInterval.of(PlaneAngleRadians.PI, 1.25 * PlaneAngleRadians.PI, TEST_PRECISION));
+ region.add(AngularInterval.of(0.25 * PlaneAngleRadians.PI, PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION));
+
+ EmbeddedTreeGreatCircleSubset sub = new EmbeddedTreeGreatCircleSubset(circle, region);
+
+ // act
+ HyperplaneSubset.Builder<Point2S> builder = circle.span().builder();
+
+ builder.add(sub);
+ builder.add(circle.arc(1.5 * PlaneAngleRadians.PI, 0.25 * PlaneAngleRadians.PI));
+
+ GreatCircleSubset result = (GreatCircleSubset) builder.build();
+
+ // assert
+ List<GreatArc> arcs = result.toConvex();
+
+ Assert.assertEquals(2, arcs.size());
+ checkArc(arcs.get(0), Point2S.of(PlaneAngleRadians.PI_OVER_TWO, 0), Point2S.of(PlaneAngleRadians.PI_OVER_TWO, 0.25 * PlaneAngleRadians.PI));
+ checkArc(arcs.get(1), Point2S.PLUS_J, Point2S.MINUS_J);
+ }
+
+ @Test
+ public void testBuilder_nullArguments() {
+ // arrange
+ GreatCircle circle = GreatCircles.fromPoints(Point2S.MINUS_K, Point2S.MINUS_J, TEST_PRECISION);
+
+ HyperplaneSubset.Builder<Point2S> builder = circle.span().builder();
+
+ // act/assert
+ GeometryTestUtils.assertThrows(() -> {
+ builder.add((HyperplaneSubset<Point2S>) null);
+ }, NullPointerException.class);
+
+ GeometryTestUtils.assertThrows(() -> {
+ builder.add((HyperplaneConvexSubset<Point2S>) null);
+ }, NullPointerException.class, "Hyperplane subset must not be null");
+ }
+
+ @Test
+ public void testBuilder_argumentsFromDifferentGreatCircle() {
+ // arrange
+ GreatCircle circle = GreatCircles.fromPoints(Point2S.MINUS_K, Point2S.MINUS_J, TEST_PRECISION);
+ GreatCircle otherCircle = GreatCircles.fromPoints(Point2S.PLUS_I, Point2S.PLUS_J, TEST_PRECISION);
+
+ EmbeddedTreeGreatCircleSubset sub = new EmbeddedTreeGreatCircleSubset(circle);
+
+ HyperplaneSubset.Builder<Point2S> builder = sub.builder();
+
+ // act/assert
+ GeometryTestUtils.assertThrows(() -> {
+ builder.add(otherCircle.span());
+ }, IllegalArgumentException.class);
+
+ GeometryTestUtils.assertThrows(() -> {
+ builder.add(new EmbeddedTreeGreatCircleSubset(otherCircle));
+ }, IllegalArgumentException.class);
+
+ GeometryTestUtils.assertThrows(() -> {
+ builder.add(new UnknownHyperplaneSubset());
+ }, IllegalArgumentException.class);
+ }
+
+ @Test
+ public void testBuilder_unknownSubsetType() {
+ // arrange
+ GreatCircle circle = GreatCircles.fromPoints(Point2S.MINUS_K, Point2S.MINUS_J, TEST_PRECISION);
+
+ HyperplaneSubset.Builder<Point2S> builder = circle.span().builder();
+
+ // act/assert
+ GeometryTestUtils.assertThrows(() -> {
+ builder.add(new UnknownHyperplaneSubset());
+ }, IllegalArgumentException.class);
+ }
+
+ private static void checkArc(GreatArc arc, Point2S start, Point2S end) {
+ SphericalTestUtils.assertPointsEq(start, arc.getStartPoint(), TEST_EPS);
+ SphericalTestUtils.assertPointsEq(end, arc.getEndPoint(), TEST_EPS);
+ }
+
+ private static class UnknownHyperplaneSubset implements HyperplaneSubset<Point2S> {
+
+ @Override
+ public Split<? extends HyperplaneSubset<Point2S>> split(Hyperplane<Point2S> splitter) {
+ return null;
+ }
+
+ @Override
+ public Hyperplane<Point2S> getHyperplane() {
+ return null;
+ }
+
+ @Override
+ public boolean isFull() {
+ return false;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean isInfinite() {
+ return false;
+ }
+
+ @Override
+ public boolean isFinite() {
+ return false;
+ }
+
+ @Override
+ public double getSize() {
+ return 0;
+ }
+
+ @Override
+ public RegionLocation classify(Point2S point) {
+ return null;
+ }
+
+ @Override
+ public Point2S closest(Point2S point) {
+ return null;
+ }
+
+ @Override
+ public Builder<Point2S> builder() {
+ return null;
+ }
+
+ @Override
+ public HyperplaneSubset<Point2S> transform(Transform<Point2S> transform) {
+ return null;
+ }
+
+ @Override
+ public List<? extends HyperplaneConvexSubset<Point2S>> toConvex() {
+ return null;
+ }
+ }
+}