PolygonsSet.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.euclidean.twod;

  22. import java.util.ArrayList;
  23. import java.util.Collection;
  24. import java.util.IdentityHashMap;
  25. import java.util.List;
  26. import java.util.Map;

  27. import org.hipparchus.exception.MathIllegalStateException;
  28. import org.hipparchus.geometry.LocalizedGeometryFormats;
  29. import org.hipparchus.geometry.euclidean.oned.Euclidean1D;
  30. import org.hipparchus.geometry.euclidean.oned.Interval;
  31. import org.hipparchus.geometry.euclidean.oned.IntervalsSet;
  32. import org.hipparchus.geometry.euclidean.oned.OrientedPoint;
  33. import org.hipparchus.geometry.euclidean.oned.SubOrientedPoint;
  34. import org.hipparchus.geometry.euclidean.oned.Vector1D;
  35. import org.hipparchus.geometry.partitioning.AbstractRegion;
  36. import org.hipparchus.geometry.partitioning.BSPTree;
  37. import org.hipparchus.geometry.partitioning.BSPTreeVisitor;
  38. import org.hipparchus.geometry.partitioning.BoundaryAttribute;
  39. import org.hipparchus.geometry.partitioning.InteriorPointFinder;
  40. import org.hipparchus.geometry.partitioning.Side;
  41. import org.hipparchus.geometry.partitioning.SubHyperplane;
  42. import org.hipparchus.util.FastMath;
  43. import org.hipparchus.util.Precision;

  44. /** This class represents a 2D region: a set of polygons.
  45.  */
  46. public class PolygonsSet
  47.     extends AbstractRegion<Euclidean2D, Vector2D, Line, SubLine, Euclidean1D, Vector1D, OrientedPoint, SubOrientedPoint> {

  48.     /** Vertices organized as boundary loops. */
  49.     private Vector2D[][] vertices;

  50.     /** Build a polygons set representing the whole plane.
  51.      * @param tolerance tolerance below which points are considered identical
  52.      */
  53.     public PolygonsSet(final double tolerance) {
  54.         super(tolerance);
  55.     }

  56.     /** Build a polygons set from a BSP tree.
  57.      * <p>The leaf nodes of the BSP tree <em>must</em> have a
  58.      * {@code Boolean} attribute representing the inside status of
  59.      * the corresponding cell (true for inside cells, false for outside
  60.      * cells). In order to avoid building too many small objects, it is
  61.      * recommended to use the predefined constants
  62.      * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p>
  63.      * <p>
  64.      * This constructor is aimed at expert use, as building the tree may
  65.      * be a difficult task. It is not intended for general use and for
  66.      * performances reasons does not check thoroughly its input, as this would
  67.      * require walking the full tree each time. Failing to provide a tree with
  68.      * the proper attributes, <em>will</em> therefore generate problems like
  69.      * {@link NullPointerException} or {@link ClassCastException} only later on.
  70.      * This limitation is known and explains why this constructor is for expert
  71.      * use only. The caller does have the responsibility to provided correct arguments.
  72.      * </p>
  73.      * @param tree inside/outside BSP tree representing the region
  74.      * @param tolerance tolerance below which points are considered identical
  75.      */
  76.     public PolygonsSet(final BSPTree<Euclidean2D, Vector2D, Line, SubLine> tree, final double tolerance) {
  77.         super(tree, tolerance);
  78.     }

  79.     /** Build a polygons set from a Boundary REPresentation (B-rep).
  80.      * <p>The boundary is provided as a collection of {@link
  81.      * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
  82.      * interior part of the region on its minus side and the exterior on
  83.      * its plus side.</p>
  84.      * <p>The boundary elements can be in any order, and can form
  85.      * several non-connected sets (like for example polygons with holes
  86.      * or a set of disjoint polygons considered as a whole). In
  87.      * fact, the elements do not even need to be connected together
  88.      * (their topological connections are not used here). However, if the
  89.      * boundary does not really separate an inside open from an outside
  90.      * open (open having here its topological meaning), then subsequent
  91.      * calls to the {@link
  92.      * org.hipparchus.geometry.partitioning.Region#checkPoint(org.hipparchus.geometry.Point)
  93.      * checkPoint} method will not be meaningful anymore.</p>
  94.      * <p>If the boundary is empty, the region will represent the whole
  95.      * space.</p>
  96.      * @param boundary collection of boundary elements, as a
  97.      * collection of {@link SubHyperplane SubHyperplane} objects
  98.      * @param tolerance tolerance below which points are considered identical
  99.      */
  100.     public PolygonsSet(final Collection<SubLine> boundary, final double tolerance) {
  101.         super(boundary, tolerance);
  102.     }

  103.     /** Build a parallellepipedic box.
  104.      * @param xMin low bound along the x direction
  105.      * @param xMax high bound along the x direction
  106.      * @param yMin low bound along the y direction
  107.      * @param yMax high bound along the y direction
  108.      * @param tolerance tolerance below which points are considered identical
  109.      */
  110.     public PolygonsSet(final double xMin, final double xMax,
  111.                        final double yMin, final double yMax,
  112.                        final double tolerance) {
  113.         super(boxBoundary(xMin, xMax, yMin, yMax, tolerance), tolerance);
  114.     }

  115.     /** Build a polygon from a simple list of vertices.
  116.      * <p>The boundary is provided as a list of points considering to
  117.      * represent the vertices of a simple loop. The interior part of the
  118.      * region is on the left side of this path and the exterior is on its
  119.      * right side.</p>
  120.      * <p>This constructor does not handle polygons with a boundary
  121.      * forming several disconnected paths (such as polygons with holes).</p>
  122.      * <p>For cases where this simple constructor applies, it is expected to
  123.      * be numerically more robust than the {@link #PolygonsSet(Collection,double) general
  124.      * constructor} using {@link SubHyperplane subhyperplanes}.</p>
  125.      * <p>If the list is empty, the region will represent the whole
  126.      * space.</p>
  127.      * <p>
  128.      * Polygons with thin pikes or dents are inherently difficult to handle because
  129.      * they involve lines with almost opposite directions at some vertices. Polygons
  130.      * whose vertices come from some physical measurement with noise are also
  131.      * difficult because an edge that should be straight may be broken in lots of
  132.      * different pieces with almost equal directions. In both cases, computing the
  133.      * lines intersections is not numerically robust due to the almost 0 or almost
  134.      * &pi; angle. Such cases need to carefully adjust the {@code hyperplaneThickness}
  135.      * parameter. A too small value would often lead to completely wrong polygons
  136.      * with large area wrongly identified as inside or outside. Large values are
  137.      * often much safer. As a rule of thumb, a value slightly below the size of the
  138.      * most accurate detail needed is a good value for the {@code hyperplaneThickness}
  139.      * parameter.
  140.      * </p>
  141.      * @param hyperplaneThickness tolerance below which points are considered to
  142.      * belong to the hyperplane (which is therefore more a slab)
  143.      * @param vertices vertices of the simple loop boundary
  144.      */
  145.     public PolygonsSet(final double hyperplaneThickness, final Vector2D ... vertices) {
  146.         super(verticesToTree(hyperplaneThickness, vertices), hyperplaneThickness);
  147.     }

  148.     /** Create a list of hyperplanes representing the boundary of a box.
  149.      * @param xMin low bound along the x direction
  150.      * @param xMax high bound along the x direction
  151.      * @param yMin low bound along the y direction
  152.      * @param yMax high bound along the y direction
  153.      * @param tolerance tolerance below which points are considered identical
  154.      * @return boundary of the box
  155.      */
  156.     private static Line[] boxBoundary(final double xMin, final double xMax,
  157.                                       final double yMin, final double yMax,
  158.                                       final double tolerance) {
  159.         if ((xMin >= xMax - tolerance) || (yMin >= yMax - tolerance)) {
  160.             // too thin box, build an empty polygons set
  161.             return null; // NOPMD
  162.         }
  163.         final Vector2D minMin = new Vector2D(xMin, yMin);
  164.         final Vector2D minMax = new Vector2D(xMin, yMax);
  165.         final Vector2D maxMin = new Vector2D(xMax, yMin);
  166.         final Vector2D maxMax = new Vector2D(xMax, yMax);
  167.         return new Line[] {
  168.             new Line(minMin, maxMin, tolerance),
  169.             new Line(maxMin, maxMax, tolerance),
  170.             new Line(maxMax, minMax, tolerance),
  171.             new Line(minMax, minMin, tolerance)
  172.         };
  173.     }

  174.     /** Build the BSP tree of a polygons set from a simple list of vertices.
  175.      * <p>The boundary is provided as a list of points considering to
  176.      * represent the vertices of a simple loop. The interior part of the
  177.      * region is on the left side of this path and the exterior is on its
  178.      * right side.</p>
  179.      * <p>This constructor does not handle polygons with a boundary
  180.      * forming several disconnected paths (such as polygons with holes).</p>
  181.      * <p>For cases where this simple constructor applies, it is expected to
  182.      * be numerically more robust than the {@link #PolygonsSet(Collection,double) general
  183.      * constructor} using {@link SubHyperplane subhyperplanes}.</p>
  184.      * @param hyperplaneThickness tolerance below which points are consider to
  185.      * belong to the hyperplane (which is therefore more a slab)
  186.      * @param vertices vertices of the simple loop boundary
  187.      * @return the BSP tree of the input vertices
  188.      */
  189.     private static BSPTree<Euclidean2D, Vector2D, Line, SubLine> verticesToTree(final double hyperplaneThickness,
  190.                                                                                 final Vector2D ... vertices) {

  191.         final int n = vertices.length;
  192.         if (n == 0) {
  193.             // the tree represents the whole space
  194.             return new BSPTree<>(Boolean.TRUE);
  195.         }

  196.         // build the vertices
  197.         final Vertex[] vArray = new Vertex[n];
  198.         final Map<Vertex, List<Line>> bindings = new IdentityHashMap<>(n);
  199.         for (int i = 0; i < n; ++i) {
  200.             vArray[i] = new Vertex(vertices[i]);
  201.             bindings.put(vArray[i], new ArrayList<>());
  202.         }

  203.         // build the edges
  204.         List<Edge> edges = new ArrayList<>(n);
  205.         for (int i = 0; i < n; ++i) {

  206.             // get the endpoints of the edge
  207.             final Vertex start = vArray[i];
  208.             final Vertex end   = vArray[(i + 1) % n];

  209.             // get the line supporting the edge, taking care not to recreate it if it was
  210.             // already created earlier due to another edge being aligned with the current one
  211.             final Line line = supportingLine(start, end, vArray, bindings, hyperplaneThickness);

  212.             // create the edge and store it
  213.             edges.add(new Edge(start, end, line));

  214.         }

  215.         // build the tree top-down
  216.         final BSPTree<Euclidean2D, Vector2D, Line, SubLine> tree = new BSPTree<>();
  217.         insertEdges(hyperplaneThickness, tree, edges);

  218.         return tree;

  219.     }

  220.     /** Get the supporting line for two vertices.
  221.      * @param start start vertex of an edge being built
  222.      * @param end end vertex of an edge being built
  223.      * @param vArray array containing all vertices
  224.      * @param bindings bindings between vertices and lines
  225.      * @param hyperplaneThickness tolerance below which points are consider to
  226.      * belong to the hyperplane (which is therefore more a slab)
  227.      * @return line bound with both start and end and in the proper orientation
  228.      */
  229.     private static Line supportingLine(final Vertex start, final Vertex end,
  230.                                        final Vertex[] vArray,
  231.                                        final Map<Vertex, List<Line>> bindings,
  232.                                        final double hyperplaneThickness) {

  233.         Line toBeReversed = null;
  234.         for (final Line line1 : bindings.get(start)) {
  235.             for (final Line line2 : bindings.get(end)) {
  236.                 if (line1 == line2) {
  237.                     // we already know a line to which both vertices belong
  238.                     final double xs = line1.toSubSpace(start.getLocation()).getX();
  239.                     final double xe = line1.toSubSpace(end.getLocation()).getX();
  240.                     if (xe >= xs) {
  241.                         // the known line has the proper orientation
  242.                         return line1;
  243.                     } else {
  244.                         toBeReversed = line1;
  245.                     }
  246.                 }
  247.             }
  248.         }

  249.         // we need to create a new circle
  250.         final Line newLine = (toBeReversed == null) ?
  251.                              new Line(start.getLocation(), end.getLocation(), hyperplaneThickness) :
  252.                              toBeReversed.getReverse();

  253.         bindings.get(start).add(newLine);
  254.         bindings.get(end).add(newLine);

  255.         // check if another vertex also happens to be on this line
  256.         for (final Vertex vertex : vArray) {
  257.             if (vertex != start && vertex != end &&
  258.                 FastMath.abs(newLine.getOffset(vertex.getLocation())) <= hyperplaneThickness) {
  259.                 bindings.get(vertex).add(newLine);
  260.             }
  261.         }

  262.         return newLine;

  263.     }

  264.     /** Recursively build a tree by inserting cut sub-hyperplanes.
  265.      * @param hyperplaneThickness tolerance below which points are consider to
  266.      * belong to the hyperplane (which is therefore more a slab)
  267.      * @param node current tree node (it is a leaf node at the beginning
  268.      * of the call)
  269.      * @param edges list of edges to insert in the cell defined by this node
  270.      * (excluding edges not belonging to the cell defined by this node)
  271.      */
  272.     private static void insertEdges(final double hyperplaneThickness,
  273.                                     final BSPTree<Euclidean2D, Vector2D, Line, SubLine> node,
  274.                                     final List<Edge> edges) {

  275.         // find an edge with an hyperplane that can be inserted in the node
  276.         int index = 0;
  277.         Edge inserted =null;
  278.         while (inserted == null && index < edges.size()) {
  279.             inserted = edges.get(index++);
  280.             if (inserted.getNode() == null) {
  281.                 if (node.insertCut(inserted.getLine())) {
  282.                     inserted.setNode(node);
  283.                 } else {
  284.                     inserted = null;
  285.                 }
  286.             } else {
  287.                 inserted = null;
  288.             }
  289.         }

  290.         if (inserted == null) {
  291.             // no suitable edge was found, the node remains a leaf node
  292.             // we need to set its inside/outside boolean indicator
  293.             final BSPTree<Euclidean2D, Vector2D, Line, SubLine> parent = node.getParent();
  294.             if (parent == null || node == parent.getMinus()) {
  295.                 node.setAttribute(Boolean.TRUE);
  296.             } else {
  297.                 node.setAttribute(Boolean.FALSE);
  298.             }
  299.             return;
  300.         }

  301.         // we have split the node by inserting an edge as a cut sub-hyperplane
  302.         // distribute the remaining edges in the two sub-trees
  303.         final List<Edge> plusList  = new ArrayList<>();
  304.         final List<Edge> minusList = new ArrayList<>();
  305.         for (final Edge edge : edges) {
  306.             if (edge != inserted) {
  307.                 final double startOffset = inserted.getLine().getOffset(edge.getStart().getLocation());
  308.                 final double endOffset   = inserted.getLine().getOffset(edge.getEnd().getLocation());
  309.                 Side startSide = (FastMath.abs(startOffset) <= hyperplaneThickness) ?
  310.                                  Side.HYPER : ((startOffset < 0) ? Side.MINUS : Side.PLUS);
  311.                 Side endSide   = (FastMath.abs(endOffset) <= hyperplaneThickness) ?
  312.                                  Side.HYPER : ((endOffset < 0) ? Side.MINUS : Side.PLUS);
  313.                 switch (startSide) {
  314.                     case PLUS:
  315.                         if (endSide == Side.MINUS) {
  316.                             // we need to insert a split point on the hyperplane
  317.                             final Vertex splitPoint = edge.split(inserted.getLine());
  318.                             minusList.add(splitPoint.getOutgoing());
  319.                             plusList.add(splitPoint.getIncoming());
  320.                         } else {
  321.                             plusList.add(edge);
  322.                         }
  323.                         break;
  324.                     case MINUS:
  325.                         if (endSide == Side.PLUS) {
  326.                             // we need to insert a split point on the hyperplane
  327.                             final Vertex splitPoint = edge.split(inserted.getLine());
  328.                             minusList.add(splitPoint.getIncoming());
  329.                             plusList.add(splitPoint.getOutgoing());
  330.                         } else {
  331.                             minusList.add(edge);
  332.                         }
  333.                         break;
  334.                     default:
  335.                         if (endSide == Side.PLUS) {
  336.                             plusList.add(edge);
  337.                         } else if (endSide == Side.MINUS) {
  338.                             minusList.add(edge);
  339.                         }
  340.                         break;
  341.                 }
  342.             }
  343.         }

  344.         // recurse through lower levels
  345.         if (!plusList.isEmpty()) {
  346.             insertEdges(hyperplaneThickness, node.getPlus(),  plusList);
  347.         } else {
  348.             node.getPlus().setAttribute(Boolean.FALSE);
  349.         }
  350.         if (!minusList.isEmpty()) {
  351.             insertEdges(hyperplaneThickness, node.getMinus(), minusList);
  352.         } else {
  353.             node.getMinus().setAttribute(Boolean.TRUE);
  354.         }

  355.     }

  356.     /** Internal class for holding vertices while they are processed to build a BSP tree. */
  357.     private static class Vertex {

  358.         /** Vertex location. */
  359.         private final Vector2D location;

  360.         /** Incoming edge. */
  361.         private Edge incoming;

  362.         /** Outgoing edge. */
  363.         private Edge outgoing;

  364.         /** Build a non-processed vertex not owned by any node yet.
  365.          * @param location vertex location
  366.          */
  367.         Vertex(final Vector2D location) {
  368.             this.location = location;
  369.             this.incoming = null;
  370.             this.outgoing = null;
  371.         }

  372.         /** Get Vertex location.
  373.          * @return vertex location
  374.          */
  375.         public Vector2D getLocation() {
  376.             return location;
  377.         }

  378.         /** Set incoming edge.
  379.          * <p>
  380.          * The line supporting the incoming edge is automatically bound
  381.          * with the instance.
  382.          * </p>
  383.          * @param incoming incoming edge
  384.          */
  385.         public void setIncoming(final Edge incoming) {
  386.             this.incoming = incoming;
  387.         }

  388.         /** Get incoming edge.
  389.          * @return incoming edge
  390.          */
  391.         public Edge getIncoming() {
  392.             return incoming;
  393.         }

  394.         /** Set outgoing edge.
  395.          * <p>
  396.          * The line supporting the outgoing edge is automatically bound
  397.          * with the instance.
  398.          * </p>
  399.          * @param outgoing outgoing edge
  400.          */
  401.         public void setOutgoing(final Edge outgoing) {
  402.             this.outgoing = outgoing;
  403.         }

  404.         /** Get outgoing edge.
  405.          * @return outgoing edge
  406.          */
  407.         public Edge getOutgoing() {
  408.             return outgoing;
  409.         }

  410.     }

  411.     /** Internal class for holding edges while they are processed to build a BSP tree. */
  412.     private static class Edge {

  413.         /** Start vertex. */
  414.         private final Vertex start;

  415.         /** End vertex. */
  416.         private final Vertex end;

  417.         /** Line supporting the edge. */
  418.         private final Line line;

  419.         /** Node whose cut hyperplane contains this edge. */
  420.         private BSPTree<Euclidean2D, Vector2D, Line, SubLine> node;

  421.         /** Build an edge not contained in any node yet.
  422.          * @param start start vertex
  423.          * @param end end vertex
  424.          * @param line line supporting the edge
  425.          */
  426.         Edge(final Vertex start, final Vertex end, final Line line) {

  427.             this.start = start;
  428.             this.end   = end;
  429.             this.line  = line;
  430.             this.node  = null;

  431.             // connect the vertices back to the edge
  432.             start.setOutgoing(this);
  433.             end.setIncoming(this);

  434.         }

  435.         /** Get start vertex.
  436.          * @return start vertex
  437.          */
  438.         public Vertex getStart() {
  439.             return start;
  440.         }

  441.         /** Get end vertex.
  442.          * @return end vertex
  443.          */
  444.         public Vertex getEnd() {
  445.             return end;
  446.         }

  447.         /** Get the line supporting this edge.
  448.          * @return line supporting this edge
  449.          */
  450.         public Line getLine() {
  451.             return line;
  452.         }

  453.         /** Set the node whose cut hyperplane contains this edge.
  454.          * @param node node whose cut hyperplane contains this edge
  455.          */
  456.         public void setNode(final BSPTree<Euclidean2D, Vector2D, Line, SubLine> node) {
  457.             this.node = node;
  458.         }

  459.         /** Get the node whose cut hyperplane contains this edge.
  460.          * @return node whose cut hyperplane contains this edge
  461.          * (null if edge has not yet been inserted into the BSP tree)
  462.          */
  463.         public BSPTree<Euclidean2D, Vector2D, Line, SubLine> getNode() {
  464.             return node;
  465.         }

  466.         /** Split the edge.
  467.          * <p>
  468.          * Once split, this edge is not referenced anymore by the vertices,
  469.          * it is replaced by the two half-edges and an intermediate splitting
  470.          * vertex is introduced to connect these two halves.
  471.          * </p>
  472.          * @param splitLine line splitting the edge in two halves
  473.          * @return split vertex (its incoming and outgoing edges are the two halves)
  474.          */
  475.         public Vertex split(final Line splitLine) {
  476.             final Vertex splitVertex = new Vertex(line.intersection(splitLine));
  477.             final Edge startHalf = new Edge(start, splitVertex, line);
  478.             final Edge endHalf   = new Edge(splitVertex, end, line);
  479.             startHalf.node = node;
  480.             endHalf.node   = node;
  481.             return splitVertex;
  482.         }

  483.     }

  484.     /** {@inheritDoc} */
  485.     @Override
  486.     public PolygonsSet buildNew(final BSPTree<Euclidean2D, Vector2D, Line, SubLine> tree) {
  487.         return new PolygonsSet(tree, getTolerance());
  488.     }

  489.     /** {@inheritDoc} */
  490.     @Override
  491.     public Vector2D getInteriorPoint() {
  492.         final InteriorPointFinder<Euclidean2D, Vector2D, Line, SubLine> finder =
  493.                 new InteriorPointFinder<>(Vector2D.ZERO);
  494.         getTree(false).visit(finder);
  495.         final BSPTree.InteriorPoint<Euclidean2D, Vector2D> interior = finder.getPoint();
  496.         return interior == null ? null : interior.getPoint();
  497.     }

  498.     /** {@inheritDoc} */
  499.     @Override
  500.     protected void computeGeometricalProperties() {

  501.         final Vector2D[][] v = getVertices();

  502.         if (v.length == 0) {
  503.             final BSPTree<Euclidean2D, Vector2D, Line, SubLine> tree = getTree(false);
  504.             if (tree.getCut() == null && (Boolean) tree.getAttribute()) {
  505.                 // the instance covers the whole space
  506.                 setSize(Double.POSITIVE_INFINITY);
  507.                 setBarycenter(Vector2D.NaN);
  508.             } else {
  509.                 setSize(0);
  510.                 setBarycenter(new Vector2D(0, 0));
  511.             }
  512.         } else if (v[0][0] == null) {
  513.             // there is at least one open-loop: the polygon is infinite
  514.             setSize(Double.POSITIVE_INFINITY);
  515.             setBarycenter(Vector2D.NaN);
  516.         } else {
  517.             // all loops are closed, we compute some integrals around the shape

  518.             double sum  = 0;
  519.             double sumX = 0;
  520.             double sumY = 0;

  521.             for (Vector2D[] loop : v) {
  522.                 double x1 = loop[loop.length - 1].getX();
  523.                 double y1 = loop[loop.length - 1].getY();
  524.                 for (final Vector2D point : loop) {
  525.                     final double x0 = x1;
  526.                     final double y0 = y1;
  527.                     x1 = point.getX();
  528.                     y1 = point.getY();
  529.                     final double factor = x0 * y1 - y0 * x1;
  530.                     sum  += factor;
  531.                     sumX += factor * (x0 + x1);
  532.                     sumY += factor * (y0 + y1);
  533.                 }
  534.             }

  535.             if (sum < 0) {
  536.                 // the polygon as a finite outside surrounded by an infinite inside
  537.                 setSize(Double.POSITIVE_INFINITY);
  538.                 setBarycenter(Vector2D.NaN);
  539.             } else {
  540.                 setSize(sum / 2);
  541.                 setBarycenter(new Vector2D(sumX / (3 * sum), sumY / (3 * sum)));
  542.             }

  543.         }

  544.     }

  545.     /** Get the vertices of the polygon.
  546.      * <p>The polygon boundary can be represented as an array of loops,
  547.      * each loop being itself an array of vertices.</p>
  548.      * <p>In order to identify open loops which start and end by
  549.      * infinite edges, the open loops arrays start with a null point. In
  550.      * this case, the first non null point and the last point of the
  551.      * array do not represent real vertices, they are dummy points
  552.      * intended only to get the direction of the first and last edge. An
  553.      * open loop consisting of a single infinite line will therefore be
  554.      * represented by a three elements array with one null point
  555.      * followed by two dummy points. The open loops are always the first
  556.      * ones in the loops array.</p>
  557.      * <p>If the polygon has no boundary at all, a zero length loop
  558.      * array will be returned.</p>
  559.      * <p>All line segments in the various loops have the inside of the
  560.      * region on their left side and the outside on their right side
  561.      * when moving in the underlying line direction. This means that
  562.      * closed loops surrounding finite areas obey the direct
  563.      * trigonometric orientation.</p>
  564.      * @return vertices of the polygon, organized as oriented boundary
  565.      * loops with the open loops first (the returned value is guaranteed
  566.      * to be non-null)
  567.      */
  568.     public Vector2D[][] getVertices() {
  569.         if (vertices == null) {
  570.             if (getTree(false).getCut() == null) {
  571.                 vertices = new Vector2D[0][];
  572.             } else {

  573.                 // build the unconnected segments
  574.                 final SegmentsBuilder visitor = new SegmentsBuilder(getTolerance());
  575.                 getTree(true).visit(visitor);
  576.                 final List<ConnectableSegment> segments = visitor.getSegments();

  577.                 // connect all segments, using topological criteria first
  578.                 // and using Euclidean distance only as a last resort
  579.                 int pending = segments.size();
  580.                 pending -= naturalFollowerConnections(segments);
  581.                 if (pending > 0) {
  582.                     pending -= splitEdgeConnections(segments);
  583.                 }
  584.                 if (pending > 0) {
  585.                     pending -= closeVerticesConnections(segments);
  586.                 }

  587.                 // create the segment loops
  588.                 final ArrayList<List<Segment>> loops = new ArrayList<>();
  589.                 for (ConnectableSegment s = getUnprocessed(segments); s != null; s = getUnprocessed(segments)) {
  590.                     final List<Segment> loop = followLoop(s);
  591.                     if (loop != null && !loop.isEmpty()) {
  592.                         if (loop.get(0).getStart() == null) {
  593.                             // this is an open loop, we put it on the front
  594.                             loops.add(0, loop);
  595.                             --pending;
  596.                         } else {
  597.                             // this is a closed loop, we put it on the back
  598.                             loops.add(loop);
  599.                         }
  600.                     }
  601.                 }

  602.                 if (pending != 0) {
  603.                     // this should not happen
  604.                     throw new MathIllegalStateException(LocalizedGeometryFormats.OUTLINE_BOUNDARY_LOOP_OPEN);
  605.                 }

  606.                 // transform the loops in an array of arrays of points
  607.                 vertices = new Vector2D[loops.size()][];
  608.                 int i = 0;

  609.                 for (final List<Segment> loop : loops) {
  610.                     if (loop.size() < 2) {
  611.                         // single infinite line
  612.                         final Line line = loop.get(0).getLine();
  613.                         vertices[i++] = new Vector2D[] {
  614.                             null,
  615.                             line.toSpace(new Vector1D(-Float.MAX_VALUE)),
  616.                             line.toSpace(new Vector1D(+Float.MAX_VALUE))
  617.                         };
  618.                     } else if (loop.get(0).getStart() == null) {
  619.                         // open loop with at least one real point
  620.                         final Vector2D[] array = new Vector2D[loop.size() + 3];
  621.                         int j = 0;
  622.                         for (Segment segment : loop) {

  623.                             if (j == 0) {
  624.                                 // null point and first dummy point
  625.                                 double x = segment.getLine().toSubSpace(segment.getEnd()).getX();
  626.                                 x -= FastMath.max(1.0, FastMath.abs(x / 2));
  627.                                 array[j++] = null;
  628.                                 array[j++] = segment.getLine().toSpace(new Vector1D(x));
  629.                             } else {

  630.                                 if (j < (array.length - 2)) {
  631.                                     // current point
  632.                                     array[j++] = segment.getStart();
  633.                                 }

  634.                                 if (j == (array.length - 2)) {
  635.                                     // last dummy point
  636.                                     double x = segment.getLine().toSubSpace(segment.getStart()).getX();
  637.                                     x += FastMath.max(1.0, FastMath.abs(x / 2));
  638.                                     array[j++] = segment.getLine().toSpace(new Vector1D(x));
  639.                                 }
  640.                             }

  641.                         }
  642.                         vertices[i++] = array;
  643.                     } else {
  644.                         final Vector2D[] array = new Vector2D[loop.size()];
  645.                         int j = 0;
  646.                         for (Segment segment : loop) {
  647.                             array[j++] = segment.getStart();
  648.                         }
  649.                         vertices[i++] = array;
  650.                     }
  651.                 }

  652.             }
  653.         }

  654.         return vertices.clone();

  655.     }

  656.     /** Connect the segments using only natural follower information.
  657.      * @param segments segments complete segments list
  658.      * @return number of connections performed
  659.      */
  660.     private int naturalFollowerConnections(final List<ConnectableSegment> segments) {
  661.         int connected = 0;
  662.         for (final ConnectableSegment segment : segments) {
  663.             if (segment.getNext() == null) {
  664.                 final BSPTree<Euclidean2D, Vector2D, Line, SubLine> node = segment.getNode();
  665.                 final BSPTree<Euclidean2D, Vector2D, Line, SubLine> end  = segment.getEndNode();
  666.                 for (final ConnectableSegment candidateNext : segments) {
  667.                     if (candidateNext.getPrevious()  == null &&
  668.                         candidateNext.getNode()      == end &&
  669.                         candidateNext.getStartNode() == node) {
  670.                         // connect the two segments
  671.                         segment.setNext(candidateNext);
  672.                         candidateNext.setPrevious(segment);
  673.                         ++connected;
  674.                         break;
  675.                     }
  676.                 }
  677.             }
  678.         }
  679.         return connected;
  680.     }

  681.     /** Connect the segments resulting from a line splitting a straight edge.
  682.      * @param segments segments complete segments list
  683.      * @return number of connections performed
  684.      */
  685.     private int splitEdgeConnections(final List<ConnectableSegment> segments) {
  686.         int connected = 0;
  687.         for (final ConnectableSegment segment : segments) {
  688.             if (segment.getNext() == null) {
  689.                 final Line hyperplane = segment.getNode().getCut().getHyperplane();
  690.                 final BSPTree<Euclidean2D, Vector2D, Line, SubLine> end  = segment.getEndNode();
  691.                 for (final ConnectableSegment candidateNext : segments) {
  692.                     if (candidateNext.getPrevious()                      == null &&
  693.                         candidateNext.getNode().getCut().getHyperplane() == hyperplane &&
  694.                         candidateNext.getStartNode()                     == end) {
  695.                         // connect the two segments
  696.                         segment.setNext(candidateNext);
  697.                         candidateNext.setPrevious(segment);
  698.                         ++connected;
  699.                         break;
  700.                     }
  701.                 }
  702.             }
  703.         }
  704.         return connected;
  705.     }

  706.     /** Connect the segments using Euclidean distance.
  707.      * <p>
  708.      * This connection heuristic should be used last, as it relies
  709.      * only on a fuzzy distance criterion.
  710.      * </p>
  711.      * @param segments segments complete segments list
  712.      * @return number of connections performed
  713.      */
  714.     private int closeVerticesConnections(final List<ConnectableSegment> segments) {
  715.         int connected = 0;
  716.         for (final ConnectableSegment segment : segments) {
  717.             if (segment.getNext() == null && segment.getEnd() != null) {
  718.                 final Vector2D end = segment.getEnd();
  719.                 ConnectableSegment selectedNext = null;
  720.                 double min = Double.POSITIVE_INFINITY;
  721.                 for (final ConnectableSegment candidateNext : segments) {
  722.                     if (candidateNext.getPrevious() == null && candidateNext.getStart() != null) {
  723.                         final double distance = Vector2D.distance(end, candidateNext.getStart());
  724.                         if (distance < min) {
  725.                             selectedNext = candidateNext;
  726.                             min          = distance;
  727.                         }
  728.                     }
  729.                 }
  730.                 if (min <= getTolerance()) {
  731.                     // connect the two segments
  732.                     segment.setNext(selectedNext);
  733.                     selectedNext.setPrevious(segment);
  734.                     ++connected;
  735.                 }
  736.             }
  737.         }
  738.         return connected;
  739.     }

  740.     /** Get first unprocessed segment from a list.
  741.      * @param segments segments list
  742.      * @return first segment that has not been processed yet
  743.      * or null if all segments have been processed
  744.      */
  745.     private ConnectableSegment getUnprocessed(final List<ConnectableSegment> segments) {
  746.         for (final ConnectableSegment segment : segments) {
  747.             if (!segment.isProcessed()) {
  748.                 return segment;
  749.             }
  750.         }
  751.         return null;
  752.     }

  753.     /** Build the loop containing a segment.
  754.      * <p>
  755.      * The segment put in the loop will be marked as processed.
  756.      * </p>
  757.      * @param defining segment used to define the loop
  758.      * @return loop containing the segment (may be null if the loop is a
  759.      * degenerated infinitely thin 2 points loop)
  760.      */
  761.     private List<Segment> followLoop(final ConnectableSegment defining) {

  762.         final List<Segment> loop = new ArrayList<>();
  763.         loop.add(defining);
  764.         defining.setProcessed(true);

  765.         // add segments in connection order
  766.         ConnectableSegment next = defining.getNext();
  767.         while (next != defining && next != null) {
  768.             loop.add(next);
  769.             next.setProcessed(true);
  770.             next = next.getNext();
  771.         }

  772.         if (next == null) {
  773.             // the loop is open and we have found its end,
  774.             // we need to find its start too
  775.             ConnectableSegment previous = defining.getPrevious();
  776.             while (previous != null) {
  777.                 loop.add(0, previous);
  778.                 previous.setProcessed(true);
  779.                 previous = previous.getPrevious();
  780.             }
  781.         }

  782.         // filter out spurious vertices
  783.         filterSpuriousVertices(loop);

  784.         if (loop.size() == 2 && loop.get(0).getStart() != null) {
  785.             // this is a degenerated infinitely thin closed loop, we simply ignore it
  786.             return null; // NOPMD
  787.         } else {
  788.             return loop;
  789.         }

  790.     }

  791.     /** Filter out spurious vertices on straight lines (at machine precision).
  792.      * @param loop segments loop to filter (will be modified in-place)
  793.      */
  794.     private void filterSpuriousVertices(final List<Segment> loop) {
  795.         for (int i = 0; i < loop.size(); ++i) {
  796.             final Segment previous = loop.get(i);
  797.             int j = (i + 1) % loop.size();
  798.             final Segment next = loop.get(j);
  799.             if (next != null &&
  800.                 Precision.equals(previous.getLine().getAngle(), next.getLine().getAngle(), Precision.EPSILON)) {
  801.                 // the vertex between the two edges is a spurious one
  802.                 // replace the two segments by a single one
  803.                 loop.set(j, new Segment(previous.getStart(), next.getEnd(), previous.getLine()));
  804.                 loop.remove(i--);
  805.             }
  806.         }
  807.     }

  808.     /** Private extension of Segment allowing connection. */
  809.     private static class ConnectableSegment extends Segment {

  810.         /** Node containing segment. */
  811.         private final BSPTree<Euclidean2D, Vector2D, Line, SubLine> node;

  812.         /** Node whose intersection with current node defines start point. */
  813.         private final BSPTree<Euclidean2D, Vector2D, Line, SubLine> startNode;

  814.         /** Node whose intersection with current node defines end point. */
  815.         private final BSPTree<Euclidean2D, Vector2D, Line, SubLine> endNode;

  816.         /** Previous segment. */
  817.         private ConnectableSegment previous;

  818.         /** Next segment. */
  819.         private ConnectableSegment next;

  820.         /** Indicator for completely processed segments. */
  821.         private boolean processed;

  822.         /** Build a segment.
  823.          * @param start start point of the segment
  824.          * @param end end point of the segment
  825.          * @param line line containing the segment
  826.          * @param node node containing the segment
  827.          * @param startNode node whose intersection with current node defines start point
  828.          * @param endNode node whose intersection with current node defines end point
  829.          */
  830.         ConnectableSegment(final Vector2D start, final Vector2D end, final Line line,
  831.                            final BSPTree<Euclidean2D, Vector2D, Line, SubLine> node,
  832.                            final BSPTree<Euclidean2D, Vector2D, Line, SubLine> startNode,
  833.                            final BSPTree<Euclidean2D, Vector2D, Line, SubLine> endNode) {
  834.             super(start, end, line);
  835.             this.node      = node;
  836.             this.startNode = startNode;
  837.             this.endNode   = endNode;
  838.             this.previous  = null;
  839.             this.next      = null;
  840.             this.processed = false;
  841.         }

  842.         /** Get the node containing segment.
  843.          * @return node containing segment
  844.          */
  845.         public BSPTree<Euclidean2D, Vector2D, Line, SubLine> getNode() {
  846.             return node;
  847.         }

  848.         /** Get the node whose intersection with current node defines start point.
  849.          * @return node whose intersection with current node defines start point
  850.          */
  851.         public BSPTree<Euclidean2D, Vector2D, Line, SubLine> getStartNode() {
  852.             return startNode;
  853.         }

  854.         /** Get the node whose intersection with current node defines end point.
  855.          * @return node whose intersection with current node defines end point
  856.          */
  857.         public BSPTree<Euclidean2D, Vector2D, Line, SubLine> getEndNode() {
  858.             return endNode;
  859.         }

  860.         /** Get the previous segment.
  861.          * @return previous segment
  862.          */
  863.         public ConnectableSegment getPrevious() {
  864.             return previous;
  865.         }

  866.         /** Set the previous segment.
  867.          * @param previous previous segment
  868.          */
  869.         public void setPrevious(final ConnectableSegment previous) {
  870.             this.previous = previous;
  871.         }

  872.         /** Get the next segment.
  873.          * @return next segment
  874.          */
  875.         public ConnectableSegment getNext() {
  876.             return next;
  877.         }

  878.         /** Set the next segment.
  879.          * @param next previous segment
  880.          */
  881.         public void setNext(final ConnectableSegment next) {
  882.             this.next = next;
  883.         }

  884.         /** Set the processed flag.
  885.          * @param processed processed flag to set
  886.          */
  887.         public void setProcessed(final boolean processed) {
  888.             this.processed = processed;
  889.         }

  890.         /** Check if the segment has been processed.
  891.          * @return true if the segment has been processed
  892.          */
  893.         public boolean isProcessed() {
  894.             return processed;
  895.         }

  896.     }

  897.     /** Visitor building segments. */
  898.     private static class SegmentsBuilder implements BSPTreeVisitor<Euclidean2D, Vector2D, Line, SubLine> {

  899.         /** Tolerance for close nodes connection. */
  900.         private final double tolerance;

  901.         /** Built segments. */
  902.         private final List<ConnectableSegment> segments;

  903.         /** Simple constructor.
  904.          * @param tolerance tolerance for close nodes connection
  905.          */
  906.         SegmentsBuilder(final double tolerance) {
  907.             this.tolerance = tolerance;
  908.             this.segments  = new ArrayList<>();
  909.         }

  910.         /** {@inheritDoc} */
  911.         @Override
  912.         public Order visitOrder(final BSPTree<Euclidean2D, Vector2D, Line, SubLine> node) {
  913.             return Order.MINUS_SUB_PLUS;
  914.         }

  915.         /** {@inheritDoc} */
  916.         @Override
  917.         public void visitInternalNode(final BSPTree<Euclidean2D, Vector2D, Line, SubLine> node) {
  918.             @SuppressWarnings("unchecked")
  919.             final BoundaryAttribute<Euclidean2D, Vector2D, Line, SubLine> attribute =
  920.                     (BoundaryAttribute<Euclidean2D, Vector2D, Line, SubLine>) node.getAttribute();
  921.             final Iterable<BSPTree<Euclidean2D, Vector2D, Line, SubLine>> splitters = attribute.getSplitters();
  922.             if (attribute.getPlusOutside() != null) {
  923.                 addContribution(attribute.getPlusOutside(), node, splitters, false);
  924.             }
  925.             if (attribute.getPlusInside() != null) {
  926.                 addContribution(attribute.getPlusInside(), node, splitters, true);
  927.             }
  928.         }

  929.         /** {@inheritDoc} */
  930.         @Override
  931.         public void visitLeafNode(final BSPTree<Euclidean2D, Vector2D, Line, SubLine> node) {
  932.         }

  933.         /** Add the contribution of a boundary facet.
  934.          * @param sub boundary facet
  935.          * @param node node containing segment
  936.          * @param splitters splitters for the boundary facet
  937.          * @param reversed if true, the facet has the inside on its plus side
  938.          */
  939.         private void addContribution(final SubLine sub,
  940.                                      final BSPTree<Euclidean2D, Vector2D, Line, SubLine> node,
  941.                                      final Iterable<BSPTree<Euclidean2D, Vector2D, Line, SubLine>> splitters,
  942.                                      final boolean reversed) {
  943.             final List<Interval> intervals = ((IntervalsSet) sub.getRemainingRegion()).asList();
  944.             for (final Interval i : intervals) {

  945.                 // find the 2D points
  946.                 final Vector2D startV = Double.isInfinite(i.getInf()) ?
  947.                                         null : sub.getHyperplane().toSpace(new Vector1D(i.getInf()));
  948.                 final Vector2D endV   = Double.isInfinite(i.getSup()) ?
  949.                                         null : sub.getHyperplane().toSpace(new Vector1D(i.getSup()));

  950.                 // recover the connectivity information
  951.                 final BSPTree<Euclidean2D, Vector2D, Line, SubLine> startN = selectClosest(startV, splitters);
  952.                 final BSPTree<Euclidean2D, Vector2D, Line, SubLine> endN   = selectClosest(endV, splitters);

  953.                 if (reversed) {
  954.                     segments.add(new ConnectableSegment(endV, startV, sub.getHyperplane().getReverse(),
  955.                                                         node, endN, startN));
  956.                 } else {
  957.                     segments.add(new ConnectableSegment(startV, endV, sub.getHyperplane(),
  958.                                                         node, startN, endN));
  959.                 }

  960.             }
  961.         }

  962.         /** Select the node whose cut sub-hyperplane is closest to specified point.
  963.          * @param point reference point
  964.          * @param candidates candidate nodes
  965.          * @return node closest to point, or null if no node is closer than tolerance
  966.          */
  967.         private BSPTree<Euclidean2D, Vector2D, Line, SubLine>
  968.             selectClosest(final Vector2D point, final Iterable<BSPTree<Euclidean2D, Vector2D, Line, SubLine>> candidates) {

  969.             if (point == null) {
  970.                 return null;
  971.             }

  972.             BSPTree<Euclidean2D, Vector2D, Line, SubLine> selected = null;

  973.             double min = Double.POSITIVE_INFINITY;
  974.             for (final BSPTree<Euclidean2D, Vector2D, Line, SubLine> node : candidates) {
  975.                 final double distance = FastMath.abs(node.getCut().getHyperplane().getOffset(point));
  976.                 if (distance < min) {
  977.                     selected = node;
  978.                     min      = distance;
  979.                 }
  980.             }

  981.             return min <= tolerance ? selected : null;

  982.         }

  983.         /** Get the segments.
  984.          * @return built segments
  985.          */
  986.         public List<ConnectableSegment> getSegments() {
  987.             return segments;
  988.         }

  989.     }

  990. }