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    *      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  package org.hipparchus.geometry.euclidean.twod.hull;
18  
19  import java.io.Serializable;
20  
21  import org.hipparchus.exception.LocalizedCoreFormats;
22  import org.hipparchus.exception.MathIllegalArgumentException;
23  import org.hipparchus.geometry.LocalizedGeometryFormats;
24  import org.hipparchus.geometry.euclidean.twod.Euclidean2D;
25  import org.hipparchus.geometry.euclidean.twod.Line;
26  import org.hipparchus.geometry.euclidean.twod.Segment;
27  import org.hipparchus.geometry.euclidean.twod.SubLine;
28  import org.hipparchus.geometry.euclidean.twod.Vector2D;
29  import org.hipparchus.geometry.hull.ConvexHull;
30  import org.hipparchus.geometry.partitioning.Region;
31  import org.hipparchus.geometry.partitioning.RegionFactory;
32  import org.hipparchus.util.MathArrays;
33  import org.hipparchus.util.Precision;
34  
35  /**
36   * This class represents a convex hull in an two-dimensional euclidean space.
37   *
38   */
39  public class ConvexHull2D implements ConvexHull<Euclidean2D, Vector2D, Line, SubLine>, Serializable {
40  
41      /** Serializable UID. */
42      private static final long serialVersionUID = 20140129L;
43  
44      /** Vertices of the hull. */
45      private final Vector2D[] vertices;
46  
47      /** Tolerance threshold used during creation of the hull vertices. */
48      private final double tolerance;
49  
50      /**
51       * Line segments of the hull.
52       * The array is not serialized and will be created from the vertices on first access.
53       */
54      private transient Segment[] lineSegments;
55  
56      /**
57       * Simple constructor.
58       * @param vertices the vertices of the convex hull, must be ordered
59       * @param tolerance tolerance below which points are considered identical
60       * @throws MathIllegalArgumentException if the vertices do not form a convex hull
61       */
62      public ConvexHull2D(final Vector2D[] vertices, final double tolerance)
63          throws MathIllegalArgumentException {
64  
65          // assign tolerance as it will be used by the isConvex method
66          this.tolerance = tolerance;
67  
68          if (!isConvex(vertices)) {
69              throw new MathIllegalArgumentException(LocalizedGeometryFormats.NOT_CONVEX);
70          }
71  
72          this.vertices = vertices.clone();
73      }
74  
75      /**
76       * Checks whether the given hull vertices form a convex hull.
77       * @param hullVertices the hull vertices
78       * @return {@code true} if the vertices form a convex hull, {@code false} otherwise
79       */
80      private boolean isConvex(final Vector2D[] hullVertices) {
81          if (hullVertices.length < 3) {
82              return true;
83          }
84  
85          int sign = 0;
86          for (int i = 0; i < hullVertices.length; i++) {
87              final Vector2D p1 = hullVertices[i == 0 ? hullVertices.length - 1 : i - 1];
88              final Vector2D p2 = hullVertices[i];
89              final Vector2D p3 = hullVertices[i == hullVertices.length - 1 ? 0 : i + 1];
90  
91              final Vector2D d1 = p2.subtract(p1);
92              final Vector2D d2 = p3.subtract(p2);
93  
94              final double crossProduct = MathArrays.linearCombination(d1.getX(), d2.getY(), -d1.getY(), d2.getX());
95              final int cmp = Precision.compareTo(crossProduct, 0.0, tolerance);
96              // in case of collinear points the cross product will be zero
97              if (cmp != 0.0) {
98                  if (sign != 0.0 && cmp != sign) {
99                      return false;
100                 }
101                 sign = cmp;
102             }
103         }
104 
105         return true;
106     }
107 
108     /** {@inheritDoc} */
109     @Override
110     public Vector2D[] getVertices() {
111         return vertices.clone();
112     }
113 
114     /**
115      * Get the line segments of the convex hull, ordered.
116      * @return the line segments of the convex hull
117      */
118     public Segment[] getLineSegments() {
119         return retrieveLineSegments().clone();
120     }
121 
122     /**
123      * Retrieve the line segments from the cached array or create them if needed.
124      *
125      * @return the array of line segments
126      */
127     private Segment[] retrieveLineSegments() {
128         if (lineSegments == null) {
129             // construct the line segments - handle special cases of 1 or 2 points
130             final int size = vertices.length;
131             if (size <= 1) {
132                 this.lineSegments = new Segment[0];
133             } else if (size == 2) {
134                 this.lineSegments = new Segment[1];
135                 final Vector2D p1 = vertices[0];
136                 final Vector2D p2 = vertices[1];
137                 this.lineSegments[0] = new Segment(p1, p2, new Line(p1, p2, tolerance));
138             } else {
139                 this.lineSegments = new Segment[size];
140                 Vector2D firstPoint = null;
141                 Vector2D lastPoint = null;
142                 int index = 0;
143                 for (Vector2D point : vertices) {
144                     if (lastPoint == null) {
145                         firstPoint = point;
146                         lastPoint = point;
147                     } else {
148                         this.lineSegments[index++] =
149                                 new Segment(lastPoint, point, new Line(lastPoint, point, tolerance));
150                         lastPoint = point;
151                     }
152                 }
153                 this.lineSegments[index] =
154                         new Segment(lastPoint, firstPoint, new Line(lastPoint, firstPoint, tolerance));
155             }
156         }
157         return lineSegments;
158     }
159 
160     /** {@inheritDoc} */
161     @Override
162     public Region<Euclidean2D, Vector2D, Line, SubLine> createRegion() throws MathIllegalArgumentException {
163         if (vertices.length < 3) {
164             throw new MathIllegalArgumentException(LocalizedCoreFormats.INSUFFICIENT_DATA);
165         }
166         final RegionFactory<Euclidean2D, Vector2D, Line, SubLine> factory = new RegionFactory<>();
167         final Segment[] segments = retrieveLineSegments();
168         final Line[] lineArray = new Line[segments.length];
169         for (int i = 0; i < segments.length; i++) {
170             lineArray[i] = segments[i].getLine();
171         }
172         return factory.buildConvex(lineArray);
173     }
174 }