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 <mattjuntu...@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; + } + } +}