ArcsSet.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.geometry.spherical.oned;

  22. import java.util.ArrayList;
  23. import java.util.Collection;
  24. import java.util.Iterator;
  25. import java.util.List;
  26. import java.util.NoSuchElementException;

  27. import org.hipparchus.exception.LocalizedCoreFormats;
  28. import org.hipparchus.exception.MathIllegalArgumentException;
  29. import org.hipparchus.exception.MathIllegalStateException;
  30. import org.hipparchus.exception.MathRuntimeException;
  31. import org.hipparchus.geometry.LocalizedGeometryFormats;
  32. import org.hipparchus.geometry.partitioning.AbstractRegion;
  33. import org.hipparchus.geometry.partitioning.BSPTree;
  34. import org.hipparchus.geometry.partitioning.BoundaryProjection;
  35. import org.hipparchus.geometry.partitioning.Side;
  36. import org.hipparchus.util.FastMath;
  37. import org.hipparchus.util.MathUtils;
  38. import org.hipparchus.util.Precision;

  39. /** This class represents a region of a circle: a set of arcs.
  40.  * <p>
  41.  * Note that due to the wrapping around \(2 \pi\), barycenter is
  42.  * ill-defined here. It was defined only in order to fulfill
  43.  * the requirements of the {@link
  44.  * org.hipparchus.geometry.partitioning.Region Region}
  45.  * interface, but its use is discouraged.
  46.  * </p>
  47.  */
  48. public class ArcsSet extends AbstractRegion<Sphere1D, S1Point, LimitAngle, SubLimitAngle, Sphere1D, S1Point, LimitAngle, SubLimitAngle>
  49.     implements Iterable<double[]> {

  50.     /** Build an arcs set representing the whole circle.
  51.      * @param tolerance tolerance below which close sub-arcs are merged together
  52.      * @exception MathIllegalArgumentException if tolerance is smaller than {@link Sphere1D#SMALLEST_TOLERANCE}
  53.      */
  54.     public ArcsSet(final double tolerance)
  55.         throws MathIllegalArgumentException {
  56.         super(tolerance);
  57.         Sphere1D.checkTolerance(tolerance);
  58.     }

  59.     /** Build an arcs set corresponding to a single arc.
  60.      * <p>
  61.      * If either {@code lower} is equals to {@code upper} or
  62.      * the interval exceeds \( 2 \pi \), the arc is considered
  63.      * to be the full circle and its initial defining boundaries
  64.      * will be forgotten. {@code lower} is not allowed to be greater
  65.      * than {@code upper} (an exception is thrown in this case).
  66.      * </p>
  67.      * @param lower lower bound of the arc
  68.      * @param upper upper bound of the arc
  69.      * @param tolerance tolerance below which close sub-arcs are merged together
  70.      * @exception MathIllegalArgumentException if lower is greater than upper
  71.      * or tolerance is smaller than {@link Sphere1D#SMALLEST_TOLERANCE}
  72.      */
  73.     public ArcsSet(final double lower, final double upper, final double tolerance)
  74.         throws MathIllegalArgumentException {
  75.         super(buildTree(lower, upper, tolerance), tolerance);
  76.         Sphere1D.checkTolerance(tolerance);
  77.     }

  78.     /** Build an arcs set from an inside/outside BSP tree.
  79.      * <p>The leaf nodes of the BSP tree <em>must</em> have a
  80.      * {@code Boolean} attribute representing the inside status of
  81.      * the corresponding cell (true for inside cells, false for outside
  82.      * cells). In order to avoid building too many small objects, it is
  83.      * recommended to use the predefined constants
  84.      * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p>
  85.      * @param tree inside/outside BSP tree representing the arcs set
  86.      * @param tolerance tolerance below which close sub-arcs are merged together
  87.      * @exception InconsistentStateAt2PiWrapping if the tree leaf nodes are not
  88.      * consistent across the \( 0, 2 \pi \) crossing
  89.      * @exception MathIllegalArgumentException if tolerance is smaller than {@link Sphere1D#SMALLEST_TOLERANCE}
  90.      */
  91.     public ArcsSet(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> tree, final double tolerance)
  92.         throws InconsistentStateAt2PiWrapping, MathIllegalArgumentException {
  93.         super(tree, tolerance);
  94.         Sphere1D.checkTolerance(tolerance);
  95.         check2PiConsistency();
  96.     }

  97.     /** Build an arcs set from a Boundary REPresentation (B-rep).
  98.      * <p>The boundary is provided as a collection of {@link
  99.      * org.hipparchus.geometry.partitioning.SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
  100.      * interior part of the region on its minus side and the exterior on
  101.      * its plus side.</p>
  102.      * <p>The boundary elements can be in any order, and can form
  103.      * several non-connected sets (like for example polygons with holes
  104.      * or a set of disjoints polyhedrons considered as a whole). In
  105.      * fact, the elements do not even need to be connected together
  106.      * (their topological connections are not used here). However, if the
  107.      * boundary does not really separate an inside open from an outside
  108.      * open (open having here its topological meaning), then subsequent
  109.      * calls to the {@link
  110.      * org.hipparchus.geometry.partitioning.Region#checkPoint(org.hipparchus.geometry.Point)
  111.      * checkPoint} method will not be meaningful anymore.</p>
  112.      * <p>If the boundary is empty, the region will represent the whole
  113.      * space.</p>
  114.      * @param boundary collection of boundary elements
  115.      * @param tolerance tolerance below which close sub-arcs are merged together
  116.      * @exception InconsistentStateAt2PiWrapping if the tree leaf nodes are not
  117.      * consistent across the \( 0, 2 \pi \) crossing
  118.      * @exception MathIllegalArgumentException if tolerance is smaller than {@link Sphere1D#SMALLEST_TOLERANCE}
  119.      */
  120.     public ArcsSet(final Collection<SubLimitAngle> boundary, final double tolerance)
  121.         throws InconsistentStateAt2PiWrapping, MathIllegalArgumentException {
  122.         super(boundary, tolerance);
  123.         Sphere1D.checkTolerance(tolerance);
  124.         check2PiConsistency();
  125.     }

  126.     /** Build an inside/outside tree representing a single arc.
  127.      * @param lower lower angular bound of the arc
  128.      * @param upper upper angular bound of the arc
  129.      * @param tolerance tolerance below which close sub-arcs are merged together
  130.      * @return the built tree
  131.      * @exception MathIllegalArgumentException if lower is greater than upper
  132.      * or tolerance is smaller than {@link Sphere1D#SMALLEST_TOLERANCE}
  133.      */
  134.     private static BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
  135.         buildTree(final double lower, final double upper, final double tolerance)
  136.         throws MathIllegalArgumentException {

  137.         Sphere1D.checkTolerance(tolerance);
  138.         if (Precision.equals(lower, upper, 0) || (upper - lower) >= MathUtils.TWO_PI) {
  139.             // the tree must cover the whole circle
  140.             return new BSPTree<>(Boolean.TRUE);
  141.         } else  if (lower > upper) {
  142.             throw new MathIllegalArgumentException(LocalizedCoreFormats.ENDPOINTS_NOT_AN_INTERVAL,
  143.                                                 lower, upper, true);
  144.         }

  145.         // this is a regular arc, covering only part of the circle
  146.         final double normalizedLower = MathUtils.normalizeAngle(lower, FastMath.PI);
  147.         final double normalizedUpper = normalizedLower + (upper - lower);
  148.         final SubLimitAngle lowerCut = new LimitAngle(new S1Point(normalizedLower), false, tolerance).wholeHyperplane();

  149.         if (normalizedUpper <= MathUtils.TWO_PI) {
  150.             // simple arc starting after 0 and ending before 2 \pi
  151.             final SubLimitAngle upperCut = new LimitAngle(new S1Point(normalizedUpper), true, tolerance).wholeHyperplane();
  152.             return new BSPTree<>(lowerCut,
  153.                                  new BSPTree<>(Boolean.FALSE),
  154.                                  new BSPTree<>(upperCut,
  155.                                                new BSPTree<>(Boolean.FALSE),
  156.                                                new BSPTree<>(Boolean.TRUE),
  157.                                                null),
  158.                                  null);
  159.         } else {
  160.             // arc wrapping around 2 \pi
  161.             final SubLimitAngle upperCut = new LimitAngle(new S1Point(normalizedUpper - MathUtils.TWO_PI), true, tolerance).wholeHyperplane();
  162.             return new BSPTree<>(lowerCut,
  163.                                  new BSPTree<>(upperCut,
  164.                                                new BSPTree<>(Boolean.FALSE),
  165.                                                new BSPTree<>(Boolean.TRUE),
  166.                                                null),
  167.                                  new BSPTree<>(Boolean.TRUE),
  168.                                  null);
  169.         }

  170.     }

  171.     /** Check consistency.
  172.     * @exception InconsistentStateAt2PiWrapping if the tree leaf nodes are not
  173.     * consistent across the \( 0, 2 \pi \) crossing
  174.     */
  175.     private void check2PiConsistency() throws InconsistentStateAt2PiWrapping {

  176.         // start search at the tree root
  177.         BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> root = getTree(false);
  178.         if (root.getCut() == null) {
  179.             return;
  180.         }

  181.         // find the inside/outside state before the smallest internal node
  182.         final Boolean stateBefore = (Boolean) getFirstLeaf(root).getAttribute();

  183.         // find the inside/outside state after the largest internal node
  184.         final Boolean stateAfter = (Boolean) getLastLeaf(root).getAttribute();

  185.         if (stateBefore ^ stateAfter) {
  186.             throw new InconsistentStateAt2PiWrapping();
  187.         }

  188.     }

  189.     /** Get the first leaf node of a tree.
  190.      * @param root tree root
  191.      * @return first leaf node (i.e. node corresponding to the region just after 0.0 radians)
  192.      */
  193.     private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
  194.         getFirstLeaf(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> root) {

  195.         if (root.getCut() == null) {
  196.             return root;
  197.         }

  198.         // find the smallest internal node
  199.         BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> smallest = null;
  200.         for (BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> n = root; n != null; n = previousInternalNode(n)) {
  201.             smallest = n;
  202.         }

  203.         return leafBefore(smallest);

  204.     }

  205.     /** Get the last leaf node of a tree.
  206.      * @param root tree root
  207.      * @return last leaf node (i.e. node corresponding to the region just before \( 2 \pi \) radians)
  208.      */
  209.     private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
  210.         getLastLeaf(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> root) {

  211.         if (root.getCut() == null) {
  212.             return root;
  213.         }

  214.         // find the largest internal node
  215.         BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> largest = null;
  216.         for (BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> n = root; n != null; n = nextInternalNode(n)) {
  217.             largest = n;
  218.         }

  219.         return leafAfter(largest);

  220.     }

  221.     /** Get the node corresponding to the first arc start.
  222.      * @return smallest internal node (i.e. first after 0.0 radians, in trigonometric direction),
  223.      * or null if there are no internal nodes (i.e. the set is either empty or covers the full circle)
  224.      */
  225.     private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> getFirstArcStart() {

  226.         // start search at the tree root
  227.         BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node = getTree(false);
  228.         if (node.getCut() == null) {
  229.             return null;
  230.         }

  231.         // walk tree until we find the smallest internal node
  232.         node = getFirstLeaf(node).getParent();

  233.         // walk tree until we find an arc start
  234.         while (node != null && !isArcStart(node)) {
  235.             node = nextInternalNode(node);
  236.         }

  237.         return node;

  238.     }

  239.     /** Check if an internal node corresponds to the start angle of an arc.
  240.      * @param node internal node to check
  241.      * @return true if the node corresponds to the start angle of an arc
  242.      */
  243.     private boolean isArcStart(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {

  244.         if ((Boolean) leafBefore(node).getAttribute()) {
  245.             // it has an inside cell before it, it may end an arc but not start it
  246.             return false;
  247.         }

  248.         if (!(Boolean) leafAfter(node).getAttribute()) {
  249.             // it has an outside cell after it, it is a dummy cut away from real arcs
  250.             return false;
  251.         }

  252.         // the cell has an outside before and an inside after it
  253.         // it is the start of an arc
  254.         return true;

  255.     }

  256.     /** Check if an internal node corresponds to the end angle of an arc.
  257.      * @param node internal node to check
  258.      * @return true if the node corresponds to the end angle of an arc
  259.      */
  260.     private boolean isArcEnd(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {

  261.         if (!(Boolean) leafBefore(node).getAttribute()) {
  262.             // it has an outside cell before it, it may start an arc but not end it
  263.             return false;
  264.         }

  265.         if ((Boolean) leafAfter(node).getAttribute()) {
  266.             // it has an inside cell after it, it is a dummy cut in the middle of an arc
  267.             return false;
  268.         }

  269.         // the cell has an inside before and an outside after it
  270.         // it is the end of an arc
  271.         return true;

  272.     }

  273.     /** Get the next internal node.
  274.      * @param node current internal node
  275.      * @return next internal node in trigonometric order, or null
  276.      * if this is the last internal node
  277.      */
  278.     private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
  279.         nextInternalNode(BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {

  280.         if (childAfter(node).getCut() != null) {
  281.             // the next node is in the sub-tree
  282.             return leafAfter(node).getParent();
  283.         }

  284.         // there is nothing left deeper in the tree, we backtrack
  285.         while (isAfterParent(node)) {
  286.             node = node.getParent();
  287.         }
  288.         return node.getParent();

  289.     }

  290.     /** Get the previous internal node.
  291.      * @param node current internal node
  292.      * @return previous internal node in trigonometric order, or null
  293.      * if this is the first internal node
  294.      */
  295.     private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
  296.         previousInternalNode(BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {

  297.         if (childBefore(node).getCut() != null) {
  298.             // the next node is in the sub-tree
  299.             return leafBefore(node).getParent();
  300.         }

  301.         // there is nothing left deeper in the tree, we backtrack
  302.         while (isBeforeParent(node)) {
  303.             node = node.getParent();
  304.         }
  305.         return node.getParent();

  306.     }

  307.     /** Find the leaf node just before an internal node.
  308.      * @param node internal node at which the sub-tree starts
  309.      * @return leaf node just before the internal node
  310.      */
  311.     private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
  312.         leafBefore(BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {

  313.         node = childBefore(node);
  314.         while (node.getCut() != null) {
  315.             node = childAfter(node);
  316.         }

  317.         return node;

  318.     }

  319.     /** Find the leaf node just after an internal node.
  320.      * @param node internal node at which the sub-tree starts
  321.      * @return leaf node just after the internal node
  322.      */
  323.     private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
  324.         leafAfter(BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {

  325.         node = childAfter(node);
  326.         while (node.getCut() != null) {
  327.             node = childBefore(node);
  328.         }

  329.         return node;

  330.     }

  331.     /** Check if a node is the child before its parent in trigonometric order.
  332.      * @param node child node considered
  333.      * @return true is the node has a parent end is before it in trigonometric order
  334.      */
  335.     private boolean isBeforeParent(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
  336.         final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> parent = node.getParent();
  337.         if (parent == null) {
  338.             return false;
  339.         } else {
  340.             return node == childBefore(parent);
  341.         }
  342.     }

  343.     /** Check if a node is the child after its parent in trigonometric order.
  344.      * @param node child node considered
  345.      * @return true is the node has a parent end is after it in trigonometric order
  346.      */
  347.     private boolean isAfterParent(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
  348.         final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> parent = node.getParent();
  349.         if (parent == null) {
  350.             return false;
  351.         } else {
  352.             return node == childAfter(parent);
  353.         }
  354.     }

  355.     /** Find the child node just before an internal node.
  356.      * @param node internal node at which the sub-tree starts
  357.      * @return child node just before the internal node
  358.      */
  359.     private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
  360.         childBefore(BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
  361.         if (isDirect(node)) {
  362.             // smaller angles are on minus side, larger angles are on plus side
  363.             return node.getMinus();
  364.         } else {
  365.             // smaller angles are on plus side, larger angles are on minus side
  366.             return node.getPlus();
  367.         }
  368.     }

  369.     /** Find the child node just after an internal node.
  370.      * @param node internal node at which the sub-tree starts
  371.      * @return child node just after the internal node
  372.      */
  373.     private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle>
  374.         childAfter(BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
  375.         if (isDirect(node)) {
  376.             // smaller angles are on minus side, larger angles are on plus side
  377.             return node.getPlus();
  378.         } else {
  379.             // smaller angles are on plus side, larger angles are on minus side
  380.             return node.getMinus();
  381.         }
  382.     }

  383.     /** Check if an internal node has a direct limit angle.
  384.      * @param node internal node to check
  385.      * @return true if the limit angle is direct
  386.      */
  387.     private boolean isDirect(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
  388.         return node.getCut().getHyperplane().isDirect();
  389.     }

  390.     /** Get the limit angle of an internal node.
  391.      * @param node internal node to check
  392.      * @return limit angle
  393.      */
  394.     private double getAngle(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node) {
  395.         return node.getCut().getHyperplane().getLocation().getAlpha();
  396.     }

  397.     /** {@inheritDoc} */
  398.     @Override
  399.     public ArcsSet buildNew(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> tree) {
  400.         return new ArcsSet(tree, getTolerance());
  401.     }

  402.     /** {@inheritDoc} */
  403.     @Override
  404.     public S1Point getInteriorPoint() {

  405.         // look for the midpoint of the longest arc
  406.         double selectedPoint  = Double.NaN;
  407.         double selectedLength = 0;
  408.         for (final double[] a : this) {
  409.             final double length = a[1] - a[0];
  410.             if (length > selectedLength) {
  411.                 // this arc is longer than the selected one, change selection
  412.                 selectedPoint  = 0.5 * (a[0] + a[1]);
  413.                 selectedLength = length;
  414.             }
  415.         }

  416.         return Double.isNaN(selectedPoint) ? null : new S1Point(selectedPoint);

  417.     }

  418.     /** {@inheritDoc} */
  419.     @Override
  420.     protected void computeGeometricalProperties() {
  421.         if (getTree(false).getCut() == null) {
  422.             setBarycenter(S1Point.NaN);
  423.             setSize(((Boolean) getTree(false).getAttribute()) ? MathUtils.TWO_PI : 0);
  424.         } else {
  425.             double size = 0.0;
  426.             double sum  = 0.0;
  427.             for (final double[] a : this) {
  428.                 final double length = a[1] - a[0];
  429.                 size += length;
  430.                 sum  += length * (a[0] + a[1]);
  431.             }
  432.             setSize(size);
  433.             if (Precision.equals(size, MathUtils.TWO_PI, 0)) {
  434.                 setBarycenter(S1Point.NaN);
  435.             } else if (size >= Precision.SAFE_MIN) {
  436.                 setBarycenter(new S1Point(sum / (2 * size)));
  437.             } else {
  438.                 final LimitAngle limit = getTree(false).getCut().getHyperplane();
  439.                 setBarycenter(limit.getLocation());
  440.             }
  441.         }
  442.     }

  443.     /** {@inheritDoc}
  444.      */
  445.     @Override
  446.     public BoundaryProjection<Sphere1D, S1Point> projectToBoundary(final S1Point point) {

  447.         // get position of test point
  448.         final double alpha = point.getAlpha();

  449.         boolean wrapFirst = false;
  450.         double first      = Double.NaN;
  451.         double previous   = Double.NaN;
  452.         for (final double[] a : this) {

  453.             if (Double.isNaN(first)) {
  454.                 // remember the first angle in case we need it later
  455.                 first = a[0];
  456.             }

  457.             if (!wrapFirst) {
  458.                 if (alpha < a[0]) {
  459.                     // the test point lies between the previous and the current arcs
  460.                     // offset will be positive
  461.                     if (Double.isNaN(previous)) {
  462.                         // we need to wrap around the circle
  463.                         wrapFirst = true;
  464.                     } else {
  465.                         final double previousOffset = alpha - previous;
  466.                         final double currentOffset  = a[0] - alpha;
  467.                         if (previousOffset < currentOffset) {
  468.                             return new BoundaryProjection<>(point, new S1Point(previous), previousOffset);
  469.                         } else {
  470.                             return new BoundaryProjection<>(point, new S1Point(a[0]), currentOffset);
  471.                         }
  472.                     }
  473.                 } else if (alpha <= a[1]) {
  474.                     // the test point lies within the current arc
  475.                     // offset will be negative
  476.                     final double offset0 = a[0] - alpha;
  477.                     final double offset1 = alpha - a[1];
  478.                     if (offset0 < offset1) {
  479.                         return new BoundaryProjection<>(point, new S1Point(a[1]), offset1);
  480.                     } else {
  481.                         return new BoundaryProjection<>(point, new S1Point(a[0]), offset0);
  482.                     }
  483.                 }
  484.             }
  485.             previous = a[1];
  486.         }

  487.         if (Double.isNaN(previous)) {

  488.             // there are no points at all in the arcs set
  489.             return new BoundaryProjection<>(point, null, MathUtils.TWO_PI);

  490.         } else {

  491.             // the test point if before first arc and after last arc,
  492.             // somewhere around the 0/2 \pi crossing
  493.             if (wrapFirst) {
  494.                 // the test point is between 0 and first
  495.                 final double previousOffset = alpha - (previous - MathUtils.TWO_PI);
  496.                 final double currentOffset  = first - alpha;
  497.                 if (previousOffset < currentOffset) {
  498.                     return new BoundaryProjection<>(point, new S1Point(previous), previousOffset);
  499.                 } else {
  500.                     return new BoundaryProjection<>(point, new S1Point(first), currentOffset);
  501.                 }
  502.             } else {
  503.                 // the test point is between last and 2\pi
  504.                 final double previousOffset = alpha - previous;
  505.                 final double currentOffset  = first + MathUtils.TWO_PI - alpha;
  506.                 if (previousOffset < currentOffset) {
  507.                     return new BoundaryProjection<>(point, new S1Point(previous), previousOffset);
  508.                 } else {
  509.                     return new BoundaryProjection<>(point, new S1Point(first), currentOffset);
  510.                 }
  511.             }

  512.         }

  513.     }

  514.     /** Build an ordered list of arcs representing the instance.
  515.      * <p>This method builds this arcs set as an ordered list of
  516.      * {@link Arc Arc} elements. An empty tree will build an empty list
  517.      * while a tree representing the whole circle will build a one
  518.      * element list with bounds set to \( 0 and 2 \pi \).</p>
  519.      * @return a new ordered list containing {@link Arc Arc} elements
  520.      */
  521.     public List<Arc> asList() {
  522.         final List<Arc> list = new ArrayList<>();
  523.         for (final double[] a : this) {
  524.             list.add(new Arc(a[0], a[1], getTolerance()));
  525.         }
  526.         return list;
  527.     }

  528.     /** {@inheritDoc}
  529.      * <p>
  530.      * The iterator returns the limit angles pairs of sub-arcs in trigonometric order.
  531.      * </p>
  532.      * <p>
  533.      * The iterator does <em>not</em> support the optional {@code remove} operation.
  534.      * </p>
  535.      */
  536.     @Override
  537.     public Iterator<double[]> iterator() {
  538.         return new SubArcsIterator();
  539.     }

  540.     /** Local iterator for sub-arcs. */
  541.     private class SubArcsIterator implements Iterator<double[]> {

  542.         /** Start of the first arc. */
  543.         private final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> firstStart;

  544.         /** Current node. */
  545.         private BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> current;

  546.         /** Sub-arc no yet returned. */
  547.         private double[] pending;

  548.         /** Simple constructor.
  549.          */
  550.         SubArcsIterator() {

  551.             firstStart = getFirstArcStart();
  552.             current    = firstStart;

  553.             if (firstStart == null) {
  554.                 // all the leaf tree nodes share the same inside/outside status
  555.                 if ((Boolean) getFirstLeaf(getTree(false)).getAttribute()) {
  556.                     // it is an inside node, it represents the full circle
  557.                     pending = new double[] {
  558.                         0, MathUtils.TWO_PI
  559.                     };
  560.                 } else {
  561.                     pending = null;
  562.                 }
  563.             } else {
  564.                 selectPending();
  565.             }
  566.         }

  567.         /** Walk the tree to select the pending sub-arc.
  568.          */
  569.         private void selectPending() {

  570.             // look for the start of the arc
  571.             BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> start = current;
  572.             while (start != null && !isArcStart(start)) {
  573.                 start = nextInternalNode(start);
  574.             }

  575.             if (start == null) {
  576.                 // we have exhausted the iterator
  577.                 current = null;
  578.                 pending = null;
  579.                 return;
  580.             }

  581.             // look for the end of the arc
  582.             BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> end = start;
  583.             while (end != null && !isArcEnd(end)) {
  584.                 end = nextInternalNode(end);
  585.             }

  586.             if (end != null) {

  587.                 // we have identified the arc
  588.                 pending = new double[] {
  589.                     getAngle(start), getAngle(end)
  590.                 };

  591.                 // prepare search for next arc
  592.                 current = end;

  593.             } else {

  594.                 // the final arc wraps around 2\pi, its end is before the first start
  595.                 end = firstStart;
  596.                 while (end != null && !isArcEnd(end)) {
  597.                     end = previousInternalNode(end);
  598.                 }
  599.                 if (end == null) {
  600.                     // this should never happen
  601.                     throw MathRuntimeException.createInternalError();
  602.                 }

  603.                 // we have identified the last arc
  604.                 pending = new double[] {
  605.                     getAngle(start), getAngle(end) + MathUtils.TWO_PI
  606.                 };

  607.                 // there won't be any other arcs
  608.                 current = null;

  609.             }

  610.         }

  611.         /** {@inheritDoc} */
  612.         @Override
  613.         public boolean hasNext() {
  614.             return pending != null;
  615.         }

  616.         /** {@inheritDoc} */
  617.         @Override
  618.         public double[] next() {
  619.             if (pending == null) {
  620.                 throw new NoSuchElementException();
  621.             }
  622.             final double[] next = pending;
  623.             selectPending();
  624.             return next;
  625.         }

  626.         /** {@inheritDoc} */
  627.         @Override
  628.         public void remove() {
  629.             throw new UnsupportedOperationException();
  630.         }

  631.     }

  632.     /** Split the instance in two parts by an arc.
  633.      * @param arc splitting arc
  634.      * @return an object containing both the part of the instance
  635.      * on the plus side of the arc and the part of the
  636.      * instance on the minus side of the arc
  637.      */
  638.     public Split split(final Arc arc) {

  639.         final List<Double> minus = new ArrayList<>();
  640.         final List<Double>  plus = new ArrayList<>();

  641.         final double reference = FastMath.PI + arc.getInf();
  642.         final double arcLength = arc.getSup() - arc.getInf();

  643.         for (final double[] a : this) {
  644.             final double syncedStart = MathUtils.normalizeAngle(a[0], reference) - arc.getInf();
  645.             final double arcOffset   = a[0] - syncedStart;
  646.             final double syncedEnd   = a[1] - arcOffset;
  647.             if (syncedStart < arcLength) {
  648.                 // the start point a[0] is in the minus part of the arc
  649.                 minus.add(a[0]);
  650.                 if (syncedEnd > arcLength) {
  651.                     // the end point a[1] is past the end of the arc
  652.                     // so we leave the minus part and enter the plus part
  653.                     final double minusToPlus = arcLength + arcOffset;
  654.                     minus.add(minusToPlus);
  655.                     plus.add(minusToPlus);
  656.                     if (syncedEnd > MathUtils.TWO_PI) {
  657.                         // in fact the end point a[1] goes far enough that we
  658.                         // leave the plus part of the arc and enter the minus part again
  659.                         final double plusToMinus = MathUtils.TWO_PI + arcOffset;
  660.                         plus.add(plusToMinus);
  661.                         minus.add(plusToMinus);
  662.                         minus.add(a[1]);
  663.                     } else {
  664.                         // the end point a[1] is in the plus part of the arc
  665.                         plus.add(a[1]);
  666.                     }
  667.                 } else {
  668.                     // the end point a[1] is in the minus part of the arc
  669.                     minus.add(a[1]);
  670.                 }
  671.             } else {
  672.                 // the start point a[0] is in the plus part of the arc
  673.                 plus.add(a[0]);
  674.                 if (syncedEnd > MathUtils.TWO_PI) {
  675.                     // the end point a[1] wraps around to the start of the arc
  676.                     // so we leave the plus part and enter the minus part
  677.                     final double plusToMinus = MathUtils.TWO_PI + arcOffset;
  678.                     plus.add(plusToMinus);
  679.                     minus.add(plusToMinus);
  680.                     if (syncedEnd > MathUtils.TWO_PI + arcLength) {
  681.                         // in fact the end point a[1] goes far enough that we
  682.                         // leave the minus part of the arc and enter the plus part again
  683.                         final double minusToPlus = MathUtils.TWO_PI + arcLength + arcOffset;
  684.                         minus.add(minusToPlus);
  685.                         plus.add(minusToPlus);
  686.                         plus.add(a[1]);
  687.                     } else {
  688.                         // the end point a[1] is in the minus part of the arc
  689.                         minus.add(a[1]);
  690.                     }
  691.                 } else {
  692.                     // the end point a[1] is in the plus part of the arc
  693.                     plus.add(a[1]);
  694.                 }
  695.             }
  696.         }

  697.         return new Split(createSplitPart(plus), createSplitPart(minus));

  698.     }

  699.     /** Add an arc limit to a BSP tree under construction.
  700.      * @param tree BSP tree under construction
  701.      * @param alpha arc limit
  702.      * @param isStart if true, the limit is the start of an arc
  703.      */
  704.     private void addArcLimit(final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> tree,
  705.                              final double alpha, final boolean isStart) {

  706.         final LimitAngle limit = new LimitAngle(new S1Point(alpha), !isStart, getTolerance());
  707.         final BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> node = tree.getCell(limit.getLocation(), getTolerance());
  708.         if (node.getCut() != null) {
  709.             // this should never happen
  710.             throw MathRuntimeException.createInternalError();
  711.         }

  712.         node.insertCut(limit);
  713.         node.setAttribute(null);
  714.         node.getPlus().setAttribute(Boolean.FALSE);
  715.         node.getMinus().setAttribute(Boolean.TRUE);

  716.     }

  717.     /** Create a split part.
  718.      * <p>
  719.      * As per construction, the list of limit angles is known to have
  720.      * an even number of entries, with start angles at even indices and
  721.      * end angles at odd indices.
  722.      * </p>
  723.      * @param limits limit angles of the split part
  724.      * @return split part (may be null)
  725.      */
  726.     private ArcsSet createSplitPart(final List<Double> limits) {
  727.         if (limits.isEmpty()) {
  728.             return null;
  729.         } else {

  730.             // collapse close limit angles
  731.             for (int i = 0; i < limits.size(); ++i) {
  732.                 final int    j  = (i + 1) % limits.size();
  733.                 final double lA = limits.get(i);
  734.                 final double lB = MathUtils.normalizeAngle(limits.get(j), lA);
  735.                 if (FastMath.abs(lB - lA) <= getTolerance()) {
  736.                     // the two limits are too close to each other, we remove both of them
  737.                     if (j > 0) {
  738.                         // regular case, the two entries are consecutive ones
  739.                         limits.remove(j);
  740.                         limits.remove(i);
  741.                         i = i - 1;
  742.                     } else {
  743.                         // special case, i the the last entry and j is the first entry
  744.                         // we have wrapped around list end
  745.                         final double lEnd   = limits.remove(limits.size() - 1);
  746.                         final double lStart = limits.remove(0);
  747.                         if (limits.isEmpty()) {
  748.                             // the ends were the only limits, is it a full circle or an empty circle?
  749.                             if (lEnd - lStart > FastMath.PI) {
  750.                                 // it was full circle
  751.                                 return new ArcsSet(new BSPTree<>(Boolean.TRUE), getTolerance());
  752.                             } else {
  753.                                 // it was an empty circle
  754.                                 return null;
  755.                             }
  756.                         } else {
  757.                             // we have removed the first interval start, so our list
  758.                             // currently starts with an interval end, which is wrong
  759.                             // we need to move this interval end to the end of the list
  760.                             limits.add(limits.remove(0) + MathUtils.TWO_PI);
  761.                         }
  762.                     }
  763.                 }
  764.             }

  765.             // build the tree by adding all angular sectors
  766.             BSPTree<Sphere1D, S1Point, LimitAngle, SubLimitAngle> tree = new BSPTree<>(Boolean.FALSE);
  767.             for (int i = 0; i < limits.size() - 1; i += 2) {
  768.                 addArcLimit(tree, limits.get(i),     true);
  769.                 addArcLimit(tree, limits.get(i + 1), false);
  770.             }

  771.             if (tree.getCut() == null) {
  772.                 // we did not insert anything
  773.                 return null;
  774.             }

  775.             return new ArcsSet(tree, getTolerance());

  776.         }
  777.     }

  778.     /** Class holding the results of the {@link #split split} method.
  779.      */
  780.     public static class Split {

  781.         /** Part of the arcs set on the plus side of the splitting arc. */
  782.         private final ArcsSet plus;

  783.         /** Part of the arcs set on the minus side of the splitting arc. */
  784.         private final ArcsSet minus;

  785.         /** Build a Split from its parts.
  786.          * @param plus part of the arcs set on the plus side of the
  787.          * splitting arc
  788.          * @param minus part of the arcs set on the minus side of the
  789.          * splitting arc
  790.          */
  791.         private Split(final ArcsSet plus, final ArcsSet minus) {
  792.             this.plus  = plus;
  793.             this.minus = minus;
  794.         }

  795.         /** Get the part of the arcs set on the plus side of the splitting arc.
  796.          * @return part of the arcs set on the plus side of the splitting arc
  797.          */
  798.         public ArcsSet getPlus() {
  799.             return plus;
  800.         }

  801.         /** Get the part of the arcs set on the minus side of the splitting arc.
  802.          * @return part of the arcs set on the minus side of the splitting arc
  803.          */
  804.         public ArcsSet getMinus() {
  805.             return minus;
  806.         }

  807.         /** Get the side of the split arc with respect to its splitter.
  808.          * @return {@link Side#PLUS} if only {@link #getPlus()} returns non-null,
  809.          * {@link Side#MINUS} if only {@link #getMinus()} returns non-null,
  810.          * {@link Side#BOTH} if both {@link #getPlus()} and {@link #getMinus()}
  811.          * return non-null or {@link Side#HYPER} if both {@link #getPlus()} and
  812.          * {@link #getMinus()} return null
  813.          */
  814.         public Side getSide() {
  815.             if (plus != null) {
  816.                 if (minus != null) {
  817.                     return Side.BOTH;
  818.                 } else {
  819.                     return Side.PLUS;
  820.                 }
  821.             } else if (minus != null) {
  822.                 return Side.MINUS;
  823.             } else {
  824.                 return Side.HYPER;
  825.             }
  826.         }

  827.     }

  828.     /** Specialized exception for inconsistent BSP tree state inconsistency.
  829.      * <p>
  830.      * This exception is thrown at {@link ArcsSet} construction time when the
  831.      * {@link org.hipparchus.geometry.partitioning.Region.Location inside/outside}
  832.      * state is not consistent at the 0, \(2 \pi \) crossing.
  833.      * </p>
  834.      */
  835.     public static class InconsistentStateAt2PiWrapping extends MathIllegalStateException
  836.     {

  837.         /** Serializable UID. */
  838.         private static final long serialVersionUID = 20140107L;

  839.         /** Simple constructor.
  840.          */
  841.         public InconsistentStateAt2PiWrapping() {
  842.             super(LocalizedGeometryFormats.INCONSISTENT_STATE_AT_2_PI_WRAPPING);
  843.         }

  844.     }

  845. }