diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/oned/ArcsSet.java b/src/main/java/org/apache/commons/math3/geometry/spherical/oned/ArcsSet.java index 44feb6b88..21efb5591 100644 --- a/src/main/java/org/apache/commons/math3/geometry/spherical/oned/ArcsSet.java +++ b/src/main/java/org/apache/commons/math3/geometry/spherical/oned/ArcsSet.java @@ -18,13 +18,16 @@ package org.apache.commons.math3.geometry.spherical.oned; import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; import org.apache.commons.math3.exception.MathInternalError; import org.apache.commons.math3.exception.NumberIsTooLargeException; import org.apache.commons.math3.exception.util.LocalizedFormats; import org.apache.commons.math3.geometry.partitioning.AbstractRegion; import org.apache.commons.math3.geometry.partitioning.BSPTree; +import org.apache.commons.math3.geometry.partitioning.Side; import org.apache.commons.math3.geometry.partitioning.SubHyperplane; import org.apache.commons.math3.util.FastMath; import org.apache.commons.math3.util.MathUtils; @@ -41,7 +44,7 @@ import org.apache.commons.math3.util.Precision; * @version $Id$ * @since 3.3 */ -public class ArcsSet extends AbstractRegion { +public class ArcsSet extends AbstractRegion implements Iterable { /** Tolerance below which close sub-arcs are merged together. */ private final double tolerance; @@ -183,32 +186,27 @@ public class ArcsSet extends AbstractRegion { } // walk tree until we find the smallest internal node - BSPTree previous = previousNode(node); + BSPTree previous = previousInternalNode(node); while (previous != null) { node = previous; - previous = previousNode(node); + previous = previousInternalNode(node); } // walk tree until we find an arc start while (node != null && !isArcStart(node)) { - node = nextNode(node); + node = nextInternalNode(node); } return node; } - /** Check if a node corresponds to the start angle of an arc. - * @param node node to check + /** Check if an internal node corresponds to the start angle of an arc. + * @param node internal node to check * @return true if the node corresponds to the start angle of an arc */ private boolean isArcStart(final BSPTree node) { - if (node.getCut() == null) { - // it's not even a limit angle, it cannot start an arc! - return false; - } - if ((Boolean) leafBefore(node).getAttribute()) { // it has an inside cell before it, it may end an arc but not start it return false; @@ -219,23 +217,18 @@ public class ArcsSet extends AbstractRegion { return false; } - // the cell defines a limit angle, with an outside before and an inside after + // the cell has an outside before and an inside after it // it is the start of an arc return true; } - /** Check if a node corresponds to the end angle of an arc. - * @param node node to check + /** Check if an internal node corresponds to the end angle of an arc. + * @param node internal node to check * @return true if the node corresponds to the end angle of an arc */ private boolean isArcEnd(final BSPTree node) { - if (node.getCut() == null) { - // it's not even a limit angle, it cannot start an arc! - return false; - } - if (!(Boolean) leafBefore(node).getAttribute()) { // it has an outside cell before it, it may start an arc but not end it return false; @@ -246,18 +239,18 @@ public class ArcsSet extends AbstractRegion { return false; } - // the cell defines a limit angle, with an inside before and an outside after + // the cell has an inside before and an outside after it // it is the end of an arc return true; } /** Get the next internal node. - * @param node current node + * @param node current internal node * @return next internal node in trigonometric order, or null * if this is the last internal node */ - private BSPTree nextNode(BSPTree node) { + private BSPTree nextInternalNode(BSPTree node) { if (childAfter(node).getCut() != null) { // the next node is in the sub-tree @@ -273,11 +266,11 @@ public class ArcsSet extends AbstractRegion { } /** Get the previous internal node. - * @param node current node + * @param node current internal node * @return previous internal node in trigonometric order, or null * if this is the first internal node */ - private BSPTree previousNode(BSPTree node) { + private BSPTree previousInternalNode(BSPTree node) { if (childBefore(node).getCut() != null) { // the next node is in the sub-tree @@ -293,15 +286,12 @@ public class ArcsSet extends AbstractRegion { } /** Find the leaf node just before an internal node. - * @param node node at which the sub-tree starts + * @param node internal node at which the sub-tree starts * @return leaf node just before the internal node */ private BSPTree leafBefore(BSPTree node) { - if (node.getCut() != null) { - node = childBefore(node); - } - + node = childBefore(node); while (node.getCut() != null) { node = childAfter(node); } @@ -311,15 +301,12 @@ public class ArcsSet extends AbstractRegion { } /** Find the leaf node just after an internal node. - * @param node node at which the sub-tree starts + * @param node internal node at which the sub-tree starts * @return leaf node just after the internal node */ private BSPTree leafAfter(BSPTree node) { - if (node.getCut() != null) { - node = childAfter(node); - } - + node = childAfter(node); while (node.getCut() != null) { node = childBefore(node); } @@ -355,7 +342,7 @@ public class ArcsSet extends AbstractRegion { } /** Find the child node just before an internal node. - * @param node node at which the sub-tree starts + * @param node internal node at which the sub-tree starts * @return child node just before the internal node */ private BSPTree childBefore(BSPTree node) { @@ -369,7 +356,7 @@ public class ArcsSet extends AbstractRegion { } /** Find the child node just after an internal node. - * @param node node at which the sub-tree starts + * @param node internal node at which the sub-tree starts * @return child node just after the internal node */ private BSPTree childAfter(BSPTree node) { @@ -383,21 +370,37 @@ public class ArcsSet extends AbstractRegion { } /** Check if an internal node has a direct limit angle. - * @param node node to check + * @param node internal node to check * @return true if the limit angle is direct */ private boolean isDirect(final BSPTree node) { return ((LimitAngle) node.getCut().getHyperplane()).isDirect(); } - /** Get the limit angle of a node. - * @param node node to check + /** Get the limit angle of an internal node. + * @param node internal node to check * @return true if the limit angle is direct */ private double getAngle(final BSPTree node) { return ((LimitAngle) node.getCut().getHyperplane()).getLocation().getAlpha(); } + /** Build a sub-hyperplane corresponding to an arc start. + * @param alpha arc start + * @return sub-hyperplane for start of arc + */ + private SubLimitAngle arcStart(final double alpha) { + return new LimitAngle(new S1Point(alpha), false, tolerance).wholeHyperplane(); + } + + /** Build a sub-hyperplane corresponding to an arc end. + * @param alpha arc end + * @return sub-hyperplane for end of arc + */ + private SubLimitAngle arcEnd(final double alpha) { + return new LimitAngle(new S1Point(alpha), true, tolerance).wholeHyperplane(); + } + /** {@inheritDoc} */ @Override public ArcsSet buildNew(final BSPTree tree) { @@ -413,15 +416,16 @@ public class ArcsSet extends AbstractRegion { } else { double size = 0.0; double sum = 0.0; - for (final Arc arc : asList()) { - size += arc.getSize(); - sum += arc.getSize() * arc.getBarycenter(); + for (final double[] a : this) { + final double length = a[1] - a[0]; + size += length; + sum += length * (a[0] + a[1]); } setSize(size); if (Precision.equals(size, MathUtils.TWO_PI, 0)) { setBarycenter(S1Point.NaN); } else if (size >= Precision.SAFE_MIN) { - setBarycenter(new S1Point(sum / size)); + setBarycenter(new S1Point(sum / (2 * size))); } else { final LimitAngle limit = (LimitAngle) getTree(false).getCut().getHyperplane(); setBarycenter(limit.getLocation()); @@ -437,67 +441,297 @@ public class ArcsSet extends AbstractRegion { * @return a new ordered list containing {@link Arc Arc} elements */ public List asList() { - final List list = new ArrayList(); - final BSPTree firstStart = getFirstArcStart(); + for (final double[] a : this) { + list.add(new Arc(a[0], a[1], tolerance)); + } + return list; + } - if (firstStart == null) { + /** {@inheritDoc} + *

+ * The iterator returns the limit angles pairs of sub-arcs in trigonometric order. + *

+ *

+ * The iterator does not support the optional {@code remove} operation. + *

+ */ + public Iterator iterator() { + return new SubArcsIterator(); + } + + /** Local iterator for sub-arcs. */ + private class SubArcsIterator implements Iterator { + + /** Start of the first arc. */ + private final BSPTree firstStart; + + /** Current node. */ + private BSPTree node; + + /** Sub-arc no yet returned. */ + private double[] pending; + + /** Simple constructor. + */ + public SubArcsIterator() { + + firstStart = getFirstArcStart(); + node = firstStart; + + if (firstStart == null) { // the tree has a single node if ((Boolean) getTree(false).getAttribute()) { // it is an inside node, it represents the full circle - list.add(new Arc(0.0, 0.0, tolerance)); // since lower == upper, the arc covers the full circle - } - } else if (previousNode(firstStart) == null && nextNode(firstStart) == null) { - // the tree is a degenerate tree (probably build from a custom collection of hyperplanes) with a single cut - // we ignore the cut and consider the tree represents the full circle - list.add(new Arc(0.0, 0.0, tolerance)); // since lower == upper, the arc covers the full circle - } else { - - // find all arcs - BSPTree start = firstStart; - while (start != null) { - - // look for the end of the current arc - BSPTree end = start; - while (end != null && !isArcEnd(end)) { - end = nextNode(end); - } - - if (end != null) { - - // we have identified the current arc - list.add(new Arc(getAngle(start), getAngle(end), tolerance)); - - // prepare search for next arc - start = end; - while (start != null && !isArcStart(start)) { - start = nextNode(start); - } - + pending = new double[] { + 0, MathUtils.TWO_PI + }; } else { - // the final arc wraps around 2\pi, its end is before the first start - end = firstStart; - while (end != null && !isArcEnd(end)) { - end = previousNode(end); - } - if (end == null) { - // this should never happen - throw new MathInternalError(); - } - - // we have identified the last arc - list.add(new Arc(getAngle(start), getAngle(end) + MathUtils.TWO_PI, tolerance)); - - // stop the loop - start = null; - + pending = null; } + } else if (previousInternalNode(firstStart) == null && nextInternalNode(firstStart) == null) { + // the tree is a degenerate tree (probably build from a custom collection of hyperplanes) with a single cut + // we ignore the cut and consider the tree represents the full circle + node = null; + pending = new double[] { + 0, MathUtils.TWO_PI + }; + } else { + selectPending(); + } + } + + /** Walk the tree to select the pending sub-arc. + */ + private void selectPending() { + + // look for the start of the arc + BSPTree start = node; + while (start != null && !isArcStart(start)) { + start = nextInternalNode(start); + } + + if (start == null) { + // we have exhausted the iterator + node = null; + pending = null; + return; + } + + // look for the end of the arc + BSPTree end = start; + while (end != null && !isArcEnd(end)) { + end = nextInternalNode(end); + } + + if (end != null) { + + // we have identified the arc + pending = new double[] { + getAngle(start), getAngle(end) + }; + + // prepare search for next arc + node = end; + + } else { + + // the final arc wraps around 2\pi, its end is before the first start + end = firstStart; + while (end != null && !isArcEnd(end)) { + end = previousInternalNode(end); + } + if (end == null) { + // this should never happen + throw new MathInternalError(); + } + + // we have identified the last arc + pending = new double[] { + getAngle(start), getAngle(end) + MathUtils.TWO_PI + }; + + // there won't be any other arcs + node = null; } } - return list; + /** {@inheritDoc} */ + public boolean hasNext() { + return pending != null; + } + + /** {@inheritDoc} */ + public double[] next() { + if (pending == null) { + throw new NoSuchElementException(); + } + final double[] next = pending; + selectPending(); + return next; + } + + /** {@inheritDoc} */ + public void remove() { + throw new UnsupportedOperationException(); + } + + } + + /** Compute the relative position of the instance with respect + * to an arc. + *

+ * The {@link Side#MINUS} side of the arc is the one covered by the arc. + *

+ * @param arc arc to check instance against + * @return one of {@link Side#PLUS}, {@link Side#MINUS}, {@link Side#BOTH} + * or {@link Side#HYPER} + */ + public Side side(final Arc arc) { + + final double reference = FastMath.PI + arc.getInf(); + final double arcLength = arc.getSup() - arc.getInf(); + + boolean inMinus = false; + boolean inPlus = false; + for (final double[] a : this) { + final double syncedStart = MathUtils.normalizeAngle(a[0], reference) - arc.getInf(); + final double arcOffset = a[0] - syncedStart; + final double syncedEnd = a[1] - arcOffset; + if (syncedStart < arcLength || syncedEnd > MathUtils.TWO_PI) { + inMinus = true; + } + if (syncedEnd > arcLength) { + inPlus = true; + } + } + + if (inMinus) { + if (inPlus) { + return Side.BOTH; + } else { + return Side.MINUS; + } + } else { + if (inPlus) { + return Side.PLUS; + } else { + return Side.HYPER; + } + } + + } + + /** Split the instance in two parts by an arc. + * @param arc splitting arc + * @return an object containing both the part of the instance + * on the plus side of the arc and the part of the + * instance on the minus side of the arc + */ + public Split split(final Arc arc) { + + final List> minus = new ArrayList>(); + final List> plus = new ArrayList>(); + + final double reference = FastMath.PI + arc.getInf(); + final double arcLength = arc.getSup() - arc.getInf(); + + for (final double[] a : this) { + final double syncedStart = MathUtils.normalizeAngle(a[0], reference) - arc.getInf(); + final double arcOffset = a[0] - syncedStart; + final double syncedEnd = a[1] - arcOffset; + if (syncedStart < arcLength) { + // the start point a[0] is in the minus part of the arc + minus.add(arcStart(a[0])); + if (syncedEnd > arcLength) { + // the end point a[1] is past the end of the arc + // so we leave the minus part and enter the plus part + final double minusToPlus = arcLength + arcOffset; + minus.add(arcEnd(minusToPlus)); + plus.add(arcStart(minusToPlus)); + if (syncedEnd > MathUtils.TWO_PI) { + // in fact the end point a[1] goes far enough that we + // leave the plus part of the arc and enter the minus part again + final double plusToMinus = MathUtils.TWO_PI + arcOffset; + plus.add(arcEnd(plusToMinus)); + minus.add(arcStart(plusToMinus)); + minus.add(arcEnd(a[1])); + } else { + // the end point a[1] is in the plus part of the arc + plus.add(arcEnd(a[1])); + } + } else { + // the end point a[1] is in the minus part of the arc + minus.add(arcEnd(a[1])); + } + } else { + // the start point a[0] is in the plus part of the arc + plus.add(arcStart(a[0])); + if (syncedEnd > MathUtils.TWO_PI) { + // the end point a[1] wraps around to the start of the arc + // so we leave the plus part and enter the minus part + final double plusToMinus = MathUtils.TWO_PI + arcOffset; + plus.add(arcEnd(plusToMinus)); + minus.add(arcStart(plusToMinus)); + if (syncedEnd > MathUtils.TWO_PI + arcLength) { + // in fact the end point a[1] goes far enough that we + // leave the minus part of the arc and enter the plus part again + final double minusToPlus = MathUtils.TWO_PI + arcLength + arcOffset; + minus.add(arcEnd(minusToPlus)); + plus.add(arcStart(minusToPlus)); + plus.add(arcEnd(a[1])); + } else { + // the end point a[1] is in the minus part of the arc + minus.add(arcEnd(a[1])); + } + } else { + // the end point a[1] is in the plus part of the arc + plus.add(arcEnd(a[1])); + } + } + } + + return new Split(plus.isEmpty() ? null : new ArcsSet(plus, tolerance), + minus.isEmpty() ? null : new ArcsSet(minus,tolerance)); + + } + + /** Class holding the results of the {@link #split split} method. + */ + public static class Split { + + /** Part of the arcs set on the plus side of the splitting arc. */ + private final ArcsSet plus; + + /** Part of the arcs set on the minus side of the splitting arc. */ + private final ArcsSet minus; + + /** Build a Split from its parts. + * @param plus part of the arcs set on the plus side of the + * splitting arc + * @param minus part of the arcs set on the minus side of the + * splitting arc + */ + private Split(final ArcsSet plus, final ArcsSet minus) { + this.plus = plus; + this.minus = minus; + } + + /** Get the part of the arcs set on the plus side of the splitting arc. + * @return part of the arcs set on the plus side of the splitting arc + */ + public ArcsSet getPlus() { + return plus; + } + + /** Get the part of the arcs set on the minus side of the splitting arc. + * @return part of the arcs set on the minus side of the splitting arc + */ + public ArcsSet getMinus() { + return minus; + } } diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSet.java b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSet.java index a305ecc0e..746a94eb5 100644 --- a/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSet.java +++ b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSet.java @@ -489,27 +489,27 @@ public class SphericalPolygonsSet extends AbstractRegion { final List outsideList, final List insideList) { // get the inside chord, synchronizing its phase with the edge itself - final double edgeStart = circle.getPhase(start.getLocation().getVector()); - final double chordRelativeStart = MathUtils.normalizeAngle(circle.getChord(splitCircle).getStart(), - edgeStart + FastMath.PI) - edgeStart; - final double chordRelativeEnd = chordRelativeStart + FastMath.PI; - final double unwrappedEnd = chordRelativeStart - FastMath.PI; + final double edgeStart = circle.getPhase(start.getLocation().getVector()); + final double arcRelativeStart = MathUtils.normalizeAngle(circle.getInsideArc(splitCircle).getInf(), + edgeStart + FastMath.PI) - edgeStart; + final double arcRelativeEnd = arcRelativeStart + FastMath.PI; + final double unwrappedEnd = arcRelativeStart - FastMath.PI; // build the sub-edges - if (chordRelativeStart < length) { + if (arcRelativeStart < length) { if (unwrappedEnd > 0) { // the edge starts inside the circle, then goes outside, then comes back inside // create intermediate vertices - final Vertex vExit = new Vertex(new S2Point(circle.getPointAt(edgeStart + chordRelativeEnd))); - final Vertex vEnter = new Vertex(new S2Point(circle.getPointAt(edgeStart + chordRelativeStart))); + final Vertex vExit = new Vertex(new S2Point(circle.getPointAt(edgeStart + arcRelativeEnd))); + final Vertex vEnter = new Vertex(new S2Point(circle.getPointAt(edgeStart + arcRelativeStart))); vExit.bindWith(splitCircle); vEnter.bindWith(splitCircle); // create sub-edges final Edge eStartIn = new Edge(start, vExit, unwrappedEnd, circle); - final Edge eMiddleOut = new Edge(vExit, vEnter, chordRelativeStart - unwrappedEnd, circle); - final Edge eEndIn = new Edge(vEnter, end, length - chordRelativeStart, circle); + final Edge eMiddleOut = new Edge(vExit, vEnter, arcRelativeStart - unwrappedEnd, circle); + final Edge eEndIn = new Edge(vEnter, end, length - arcRelativeStart, circle); eStartIn.setNode(node); eMiddleOut.setNode(node); eEndIn.setNode(node); @@ -523,12 +523,12 @@ public class SphericalPolygonsSet extends AbstractRegion { // the edge starts outside of the circle, then comes inside // create intermediate vertices - final Vertex vEnter = new Vertex(new S2Point(circle.getPointAt(edgeStart + chordRelativeStart))); + final Vertex vEnter = new Vertex(new S2Point(circle.getPointAt(edgeStart + arcRelativeStart))); vEnter.bindWith(splitCircle); // create sub-edges - final Edge eStartOut = new Edge(start, vEnter, chordRelativeStart, circle); - final Edge eEndIn = new Edge(vEnter, end, length - chordRelativeStart, circle); + final Edge eStartOut = new Edge(start, vEnter, arcRelativeStart, circle); + final Edge eEndIn = new Edge(vEnter, end, length - arcRelativeStart, circle); eStartOut.setNode(node); eEndIn.setNode(node); @@ -547,12 +547,12 @@ public class SphericalPolygonsSet extends AbstractRegion { // the edge starts inside the circle, then goes outside // create intermediate vertices - final Vertex vExit = new Vertex(new S2Point(circle.getPointAt(edgeStart + chordRelativeEnd))); + final Vertex vExit = new Vertex(new S2Point(circle.getPointAt(edgeStart + arcRelativeEnd))); vExit.bindWith(splitCircle); // create sub-edges - final Edge eStartIn = new Edge(start, vExit, chordRelativeEnd, circle); - final Edge eEndOut = new Edge(vExit, end, length - chordRelativeEnd, circle); + final Edge eStartIn = new Edge(start, vExit, arcRelativeEnd, circle); + final Edge eEndOut = new Edge(vExit, end, length - arcRelativeEnd, circle); eStartIn.setNode(node); eEndOut.setNode(node); diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SubCircle.java b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SubCircle.java index ba7145fea..314adb2e5 100644 --- a/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SubCircle.java +++ b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SubCircle.java @@ -17,14 +17,11 @@ package org.apache.commons.math3.geometry.spherical.twod; import org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane; -import org.apache.commons.math3.geometry.partitioning.BSPTree; import org.apache.commons.math3.geometry.partitioning.Hyperplane; import org.apache.commons.math3.geometry.partitioning.Region; import org.apache.commons.math3.geometry.partitioning.Side; -import org.apache.commons.math3.geometry.partitioning.SubHyperplane; import org.apache.commons.math3.geometry.spherical.oned.Arc; import org.apache.commons.math3.geometry.spherical.oned.ArcsSet; -import org.apache.commons.math3.geometry.spherical.oned.Chord; import org.apache.commons.math3.geometry.spherical.oned.Sphere1D; /** This class represents a sub-hyperplane for {@link Circle}. @@ -64,24 +61,12 @@ public class SubCircle extends AbstractSubHyperplane { @Override public SplitSubHyperplane split(final Hyperplane hyperplane) { - final Circle thisCircle = (Circle) getHyperplane(); - final Circle otherCircle = (Circle) hyperplane; - final Arc arc = thisCircle.getInsideArc(otherCircle); - - final SubHyperplane subMinus = chord.wholeHyperplane(); - final SubHyperplane subPlus = chord.getReverse().wholeHyperplane(); - final BSPTree splitTree = getRemainingRegion().getTree(false).split(subMinus); - final BSPTree plusTree = getRemainingRegion().isEmpty(splitTree.getPlus()) ? - new BSPTree(Boolean.FALSE) : - new BSPTree(subPlus, new BSPTree(Boolean.FALSE), - splitTree.getPlus(), null); - final BSPTree minusTree = getRemainingRegion().isEmpty(splitTree.getMinus()) ? - new BSPTree(Boolean.FALSE) : - new BSPTree(subMinus, new BSPTree(Boolean.FALSE), - splitTree.getMinus(), null); - - return new SplitSubHyperplane(new SubCircle(thisCircle.copySelf(), new ArcsSet(plusTree, thisCircle.getTolerance())), - new SubCircle(thisCircle.copySelf(), new ArcsSet(minusTree, thisCircle.getTolerance()))); + final Circle thisCircle = (Circle) getHyperplane(); + final Circle otherCircle = (Circle) hyperplane; + final Arc arc = thisCircle.getInsideArc(otherCircle); + final ArcsSet.Split split = ((ArcsSet) getRemainingRegion()).split(arc); + return new SplitSubHyperplane(new SubCircle(thisCircle.copySelf(), split.getPlus()), + new SubCircle(thisCircle.copySelf(), split.getMinus())); } diff --git a/src/test/java/org/apache/commons/math3/geometry/spherical/oned/ArcsSetTest.java b/src/test/java/org/apache/commons/math3/geometry/spherical/oned/ArcsSetTest.java index 90510965b..7b54fc4cd 100644 --- a/src/test/java/org/apache/commons/math3/geometry/spherical/oned/ArcsSetTest.java +++ b/src/test/java/org/apache/commons/math3/geometry/spherical/oned/ArcsSetTest.java @@ -17,12 +17,15 @@ package org.apache.commons.math3.geometry.spherical.oned; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; import org.apache.commons.math3.exception.NumberIsTooLargeException; import org.apache.commons.math3.geometry.partitioning.Region; import org.apache.commons.math3.geometry.partitioning.Region.Location; import org.apache.commons.math3.geometry.partitioning.RegionFactory; +import org.apache.commons.math3.geometry.partitioning.Side; import org.apache.commons.math3.geometry.partitioning.SubHyperplane; import org.apache.commons.math3.util.FastMath; import org.apache.commons.math3.util.MathUtils; @@ -250,4 +253,207 @@ public class ArcsSetTest { Assert.assertEquals(2 * Precision.EPSILON, set.getSize(), Precision.SAFE_MIN); } + @Test + public void testIteration() { + ArcsSet set = (ArcsSet) new RegionFactory().difference(new ArcsSet(1.0, 6.0, 1.0e-10), + new ArcsSet(3.0, 5.0, 1.0e-10)); + Iterator iterator = set.iterator(); + try { + iterator.remove(); + Assert.fail("an exception should have been thrown"); + } catch (UnsupportedOperationException uoe) { + // expected + } + + Assert.assertTrue(iterator.hasNext()); + double[] a0 = iterator.next(); + Assert.assertEquals(2, a0.length); + Assert.assertEquals(1.0, a0[0], 1.0e-10); + Assert.assertEquals(3.0, a0[1], 1.0e-10); + + Assert.assertTrue(iterator.hasNext()); + double[] a1 = iterator.next(); + Assert.assertEquals(2, a1.length); + Assert.assertEquals(5.0, a1[0], 1.0e-10); + Assert.assertEquals(6.0, a1[1], 1.0e-10); + + Assert.assertFalse(iterator.hasNext()); + try { + iterator.next(); + Assert.fail("an exception should have been thrown"); + } catch (NoSuchElementException nsee) { + // expected + } + + } + + @Test + public void testSide() { + ArcsSet set = (ArcsSet) new RegionFactory().difference(new ArcsSet(1.0, 6.0, 1.0e-10), + new ArcsSet(3.0, 5.0, 1.0e-10)); + for (int k = -2; k < 3; ++k) { + Assert.assertEquals(Side.MINUS, set.side(new Arc(0.5 + k * MathUtils.TWO_PI, + 6.1 + k * MathUtils.TWO_PI, + set.getTolerance()))); + Assert.assertEquals(Side.PLUS, set.side(new Arc(0.5 + k * MathUtils.TWO_PI, + 0.8 + k * MathUtils.TWO_PI, + set.getTolerance()))); + Assert.assertEquals(Side.PLUS, set.side(new Arc(6.2 + k * MathUtils.TWO_PI, + 6.3 + k * MathUtils.TWO_PI, + set.getTolerance()))); + Assert.assertEquals(Side.PLUS, set.side(new Arc(3.5 + k * MathUtils.TWO_PI, + 4.5 + k * MathUtils.TWO_PI, + set.getTolerance()))); + Assert.assertEquals(Side.BOTH, set.side(new Arc(2.9 + k * MathUtils.TWO_PI, + 4.5 + k * MathUtils.TWO_PI, + set.getTolerance()))); + Assert.assertEquals(Side.BOTH, set.side(new Arc(0.5 + k * MathUtils.TWO_PI, + 1.2 + k * MathUtils.TWO_PI, + set.getTolerance()))); + Assert.assertEquals(Side.BOTH, set.side(new Arc(0.5 + k * MathUtils.TWO_PI, + 5.9 + k * MathUtils.TWO_PI, + set.getTolerance()))); + } + } + + @Test + public void testSideEmbedded() { + + ArcsSet s35 = new ArcsSet(3.0, 5.0, 1.0e-10); + ArcsSet s16 = new ArcsSet(1.0, 6.0, 1.0e-10); + + Assert.assertEquals(Side.BOTH, s16.side(new Arc(3.0, 5.0, 1.0e-10))); + Assert.assertEquals(Side.BOTH, s16.side(new Arc(5.0, 3.0 + MathUtils.TWO_PI, 1.0e-10))); + Assert.assertEquals(Side.MINUS, s35.side(new Arc(1.0, 6.0, 1.0e-10))); + Assert.assertEquals(Side.PLUS, s35.side(new Arc(6.0, 1.0 + MathUtils.TWO_PI, 1.0e-10))); + + } + + @Test + public void testSideOverlapping() { + ArcsSet s35 = new ArcsSet(3.0, 5.0, 1.0e-10); + ArcsSet s46 = new ArcsSet(4.0, 6.0, 1.0e-10); + + Assert.assertEquals(Side.BOTH, s46.side(new Arc(3.0, 5.0, 1.0e-10))); + Assert.assertEquals(Side.BOTH, s46.side(new Arc(5.0, 3.0 + MathUtils.TWO_PI, 1.0e-10))); + Assert.assertEquals(Side.BOTH, s35.side(new Arc(4.0, 6.0, 1.0e-10))); + Assert.assertEquals(Side.BOTH, s35.side(new Arc(6.0, 4.0 + MathUtils.TWO_PI, 1.0e-10))); + } + + @Test + public void testSideHyper() { + ArcsSet sub = (ArcsSet) new RegionFactory().getComplement(new ArcsSet(1.0e-10)); + Assert.assertTrue(sub.isEmpty()); + Assert.assertEquals(Side.HYPER, sub.side(new Arc(2.0, 3.0, 1.0e-10))); + } + + @Test + public void testSplitEmbedded() { + + ArcsSet s35 = new ArcsSet(3.0, 5.0, 1.0e-10); + ArcsSet s16 = new ArcsSet(1.0, 6.0, 1.0e-10); + + ArcsSet.Split split1 = s16.split(new Arc(3.0, 5.0, 1.0e-10)); + ArcsSet split1Plus = (ArcsSet) split1.getPlus(); + ArcsSet split1Minus = (ArcsSet) split1.getMinus(); + Assert.assertEquals(3.0, split1Plus.getSize(), 1.0e-10); + Assert.assertEquals(2, split1Plus.asList().size()); + Assert.assertEquals(1.0, split1Plus.asList().get(0).getInf(), 1.0e-10); + Assert.assertEquals(3.0, split1Plus.asList().get(0).getSup(), 1.0e-10); + Assert.assertEquals(5.0, split1Plus.asList().get(1).getInf(), 1.0e-10); + Assert.assertEquals(6.0, split1Plus.asList().get(1).getSup(), 1.0e-10); + Assert.assertEquals(2.0, split1Minus.getSize(), 1.0e-10); + Assert.assertEquals(1, split1Minus.asList().size()); + Assert.assertEquals(3.0, split1Minus.asList().get(0).getInf(), 1.0e-10); + Assert.assertEquals(5.0, split1Minus.asList().get(0).getSup(), 1.0e-10); + + ArcsSet.Split split2 = s16.split(new Arc(5.0, 3.0 + MathUtils.TWO_PI, 1.0e-10)); + ArcsSet split2Plus = (ArcsSet) split2.getPlus(); + ArcsSet split2Minus = (ArcsSet) split2.getMinus(); + Assert.assertEquals(2.0, split2Plus.getSize(), 1.0e-10); + Assert.assertEquals(1, split2Plus.asList().size()); + Assert.assertEquals(3.0, split2Plus.asList().get(0).getInf(), 1.0e-10); + Assert.assertEquals(5.0, split2Plus.asList().get(0).getSup(), 1.0e-10); + Assert.assertEquals(3.0, split2Minus.getSize(), 1.0e-10); + Assert.assertEquals(2, split2Minus.asList().size()); + Assert.assertEquals(1.0, split2Minus.asList().get(0).getInf(), 1.0e-10); + Assert.assertEquals(3.0, split2Minus.asList().get(0).getSup(), 1.0e-10); + Assert.assertEquals(5.0, split2Minus.asList().get(1).getInf(), 1.0e-10); + Assert.assertEquals(6.0, split2Minus.asList().get(1).getSup(), 1.0e-10); + + ArcsSet.Split split3 = s35.split(new Arc(1.0, 6.0, 1.0e-10)); + ArcsSet split3Plus = (ArcsSet) split3.getPlus(); + ArcsSet split3Minus = (ArcsSet) split3.getMinus(); + Assert.assertNull(split3Plus); + Assert.assertEquals(2.0, split3Minus.getSize(), 1.0e-10); + Assert.assertEquals(1, split3Minus.asList().size()); + Assert.assertEquals(3.0, split3Minus.asList().get(0).getInf(), 1.0e-10); + Assert.assertEquals(5.0, split3Minus.asList().get(0).getSup(), 1.0e-10); + + ArcsSet.Split split4 = s35.split(new Arc(6.0, 1.0 + MathUtils.TWO_PI, 1.0e-10)); + ArcsSet split4Plus = (ArcsSet) split4.getPlus(); + ArcsSet split4Minus = (ArcsSet) split4.getMinus(); + Assert.assertEquals(2.0, split4Plus.getSize(), 1.0e-10); + Assert.assertEquals(1, split4Plus.asList().size()); + Assert.assertEquals(3.0, split4Plus.asList().get(0).getInf(), 1.0e-10); + Assert.assertEquals(5.0, split4Plus.asList().get(0).getSup(), 1.0e-10); + Assert.assertNull(split4Minus); + + } + + @Test + public void testSplitOverlapping() { + + ArcsSet s35 = new ArcsSet(3.0, 5.0, 1.0e-10); + ArcsSet s46 = new ArcsSet(4.0, 6.0, 1.0e-10); + + ArcsSet.Split split1 = s46.split(new Arc(3.0, 5.0, 1.0e-10)); + ArcsSet split1Plus = (ArcsSet) split1.getPlus(); + ArcsSet split1Minus = (ArcsSet) split1.getMinus(); + Assert.assertEquals(1.0, split1Plus.getSize(), 1.0e-10); + Assert.assertEquals(1, split1Plus.asList().size()); + Assert.assertEquals(5.0, split1Plus.asList().get(0).getInf(), 1.0e-10); + Assert.assertEquals(6.0, split1Plus.asList().get(0).getSup(), 1.0e-10); + Assert.assertEquals(1.0, split1Minus.getSize(), 1.0e-10); + Assert.assertEquals(1, split1Minus.asList().size()); + Assert.assertEquals(4.0, split1Minus.asList().get(0).getInf(), 1.0e-10); + Assert.assertEquals(5.0, split1Minus.asList().get(0).getSup(), 1.0e-10); + + ArcsSet.Split split2 = s46.split(new Arc(5.0, 3.0 + MathUtils.TWO_PI, 1.0e-10)); + ArcsSet split2Plus = (ArcsSet) split2.getPlus(); + ArcsSet split2Minus = (ArcsSet) split2.getMinus(); + Assert.assertEquals(1.0, split2Plus.getSize(), 1.0e-10); + Assert.assertEquals(1, split2Plus.asList().size()); + Assert.assertEquals(4.0, split2Plus.asList().get(0).getInf(), 1.0e-10); + Assert.assertEquals(5.0, split2Plus.asList().get(0).getSup(), 1.0e-10); + Assert.assertEquals(1.0, split2Minus.getSize(), 1.0e-10); + Assert.assertEquals(1, split2Minus.asList().size()); + Assert.assertEquals(5.0, split2Minus.asList().get(0).getInf(), 1.0e-10); + Assert.assertEquals(6.0, split2Minus.asList().get(0).getSup(), 1.0e-10); + + ArcsSet.Split split3 = s35.split(new Arc(4.0, 6.0, 1.0e-10)); + ArcsSet split3Plus = (ArcsSet) split3.getPlus(); + ArcsSet split3Minus = (ArcsSet) split3.getMinus(); + Assert.assertEquals(1.0, split3Plus.getSize(), 1.0e-10); + Assert.assertEquals(1, split3Plus.asList().size()); + Assert.assertEquals(3.0, split3Plus.asList().get(0).getInf(), 1.0e-10); + Assert.assertEquals(4.0, split3Plus.asList().get(0).getSup(), 1.0e-10); + Assert.assertEquals(1.0, split3Minus.getSize(), 1.0e-10); + Assert.assertEquals(1, split3Minus.asList().size()); + Assert.assertEquals(4.0, split3Minus.asList().get(0).getInf(), 1.0e-10); + Assert.assertEquals(5.0, split3Minus.asList().get(0).getSup(), 1.0e-10); + + ArcsSet.Split split4 = s35.split(new Arc(6.0, 4.0 + MathUtils.TWO_PI, 1.0e-10)); + ArcsSet split4Plus = (ArcsSet) split4.getPlus(); + ArcsSet split4Minus = (ArcsSet) split4.getMinus(); + Assert.assertEquals(1.0, split4Plus.getSize(), 1.0e-10); + Assert.assertEquals(1, split4Plus.asList().size()); + Assert.assertEquals(4.0, split4Plus.asList().get(0).getInf(), 1.0e-10); + Assert.assertEquals(5.0, split4Plus.asList().get(0).getSup(), 1.0e-10); + Assert.assertEquals(1.0, split4Minus.getSize(), 1.0e-10); + Assert.assertEquals(1, split4Minus.asList().size()); + Assert.assertEquals(3.0, split4Minus.asList().get(0).getInf(), 1.0e-10); + Assert.assertEquals(4.0, split4Minus.asList().get(0).getSup(), 1.0e-10); + + } }