View Javadoc
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    *      http://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  package org.apache.commons.math3.geometry.euclidean.twod;
18  
19  import java.util.ArrayList;
20  import java.util.Iterator;
21  
22  import org.apache.commons.math3.exception.MathIllegalArgumentException;
23  import org.apache.commons.math3.exception.util.LocalizedFormats;
24  import org.apache.commons.math3.geometry.Point;
25  import org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet;
26  import org.apache.commons.math3.geometry.partitioning.Region;
27  import org.apache.commons.math3.geometry.partitioning.RegionFactory;
28  import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
29  
30  /** This class represent a tree of nested 2D boundary loops.
31  
32   * <p>This class is used for piecewise polygons construction.
33   * Polygons are built using the outline edges as
34   * representative of boundaries, the orientation of these lines are
35   * meaningful. However, we want to allow the user to specify its
36   * outline loops without having to take care of this orientation. This
37   * class is devoted to correct mis-oriented loops.<p>
38  
39   * <p>Orientation is computed assuming the piecewise polygon is finite,
40   * i.e. the outermost loops have their exterior side facing points at
41   * infinity, and hence are oriented counter-clockwise. The orientation of
42   * internal loops is computed as the reverse of the orientation of
43   * their immediate surrounding loop.</p>
44  
45   * @since 3.0
46   */
47  class NestedLoops {
48  
49      /** Boundary loop. */
50      private Vector2D[] loop;
51  
52      /** Surrounded loops. */
53      private ArrayList<NestedLoops> surrounded;
54  
55      /** Polygon enclosing a finite region. */
56      private Region<Euclidean2D> polygon;
57  
58      /** Indicator for original loop orientation. */
59      private boolean originalIsClockwise;
60  
61      /** Tolerance below which points are considered identical. */
62      private final double tolerance;
63  
64      /** Simple Constructor.
65       * <p>Build an empty tree of nested loops. This instance will become
66       * the root node of a complete tree, it is not associated with any
67       * loop by itself, the outermost loops are in the root tree child
68       * nodes.</p>
69       * @param tolerance tolerance below which points are considered identical
70       * @since 3.3
71       */
72      public NestedLoops(final double tolerance) {
73          this.surrounded = new ArrayList<NestedLoops>();
74          this.tolerance  = tolerance;
75      }
76  
77      /** Constructor.
78       * <p>Build a tree node with neither parent nor children</p>
79       * @param loop boundary loop (will be reversed in place if needed)
80       * @param tolerance tolerance below which points are considered identical
81       * @exception MathIllegalArgumentException if an outline has an open boundary loop
82       * @since 3.3
83       */
84      private NestedLoops(final Vector2D[] loop, final double tolerance)
85          throws MathIllegalArgumentException {
86  
87          if (loop[0] == null) {
88              throw new MathIllegalArgumentException(LocalizedFormats.OUTLINE_BOUNDARY_LOOP_OPEN);
89          }
90  
91          this.loop       = loop;
92          this.surrounded = new ArrayList<NestedLoops>();
93          this.tolerance  = tolerance;
94  
95          // build the polygon defined by the loop
96          final ArrayList<SubHyperplane<Euclidean2D>> edges = new ArrayList<SubHyperplane<Euclidean2D>>();
97          Vector2D current = loop[loop.length - 1];
98          for (int i = 0; i < loop.length; ++i) {
99              final Vector2D previous = current;
100             current = loop[i];
101             final Line   line   = new Line(previous, current, tolerance);
102             final IntervalsSet region =
103                 new IntervalsSet(line.toSubSpace((Point<Euclidean2D>) previous).getX(),
104                                  line.toSubSpace((Point<Euclidean2D>) current).getX(),
105                                  tolerance);
106             edges.add(new SubLine(line, region));
107         }
108         polygon = new PolygonsSet(edges, tolerance);
109 
110         // ensure the polygon encloses a finite region of the plane
111         if (Double.isInfinite(polygon.getSize())) {
112             polygon = new RegionFactory<Euclidean2D>().getComplement(polygon);
113             originalIsClockwise = false;
114         } else {
115             originalIsClockwise = true;
116         }
117 
118     }
119 
120     /** Add a loop in a tree.
121      * @param bLoop boundary loop (will be reversed in place if needed)
122      * @exception MathIllegalArgumentException if an outline has crossing
123      * boundary loops or open boundary loops
124      */
125     public void add(final Vector2D[] bLoop) throws MathIllegalArgumentException {
126         add(new NestedLoops(bLoop, tolerance));
127     }
128 
129     /** Add a loop in a tree.
130      * @param node boundary loop (will be reversed in place if needed)
131      * @exception MathIllegalArgumentException if an outline has boundary
132      * loops that cross each other
133      */
134     private void add(final NestedLoops node) throws MathIllegalArgumentException {
135 
136         // check if we can go deeper in the tree
137         for (final NestedLoops child : surrounded) {
138             if (child.polygon.contains(node.polygon)) {
139                 child.add(node);
140                 return;
141             }
142         }
143 
144         // check if we can absorb some of the instance children
145         for (final Iterator<NestedLoops> iterator = surrounded.iterator(); iterator.hasNext();) {
146             final NestedLoops child = iterator.next();
147             if (node.polygon.contains(child.polygon)) {
148                 node.surrounded.add(child);
149                 iterator.remove();
150             }
151         }
152 
153         // we should be separate from the remaining children
154         RegionFactory<Euclidean2D> factory = new RegionFactory<Euclidean2D>();
155         for (final NestedLoops child : surrounded) {
156             if (!factory.intersection(node.polygon, child.polygon).isEmpty()) {
157                 throw new MathIllegalArgumentException(LocalizedFormats.CROSSING_BOUNDARY_LOOPS);
158             }
159         }
160 
161         surrounded.add(node);
162 
163     }
164 
165     /** Correct the orientation of the loops contained in the tree.
166      * <p>This is this method that really inverts the loops that where
167      * provided through the {@link #add(Vector2D[]) add} method if
168      * they are mis-oriented</p>
169      */
170     public void correctOrientation() {
171         for (NestedLoops child : surrounded) {
172             child.setClockWise(true);
173         }
174     }
175 
176     /** Set the loop orientation.
177      * @param clockwise if true, the loop should be set to clockwise
178      * orientation
179      */
180     private void setClockWise(final boolean clockwise) {
181 
182         if (originalIsClockwise ^ clockwise) {
183             // we need to inverse the original loop
184             int min = -1;
185             int max = loop.length;
186             while (++min < --max) {
187                 final Vector2D tmp = loop[min];
188                 loop[min] = loop[max];
189                 loop[max] = tmp;
190             }
191         }
192 
193         // go deeper in the tree
194         for (final NestedLoops child : surrounded) {
195             child.setClockWise(!clockwise);
196         }
197 
198     }
199 
200 }