ConvexHull2D.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. package org.hipparchus.geometry.euclidean.twod.hull;

  18. import java.io.Serializable;

  19. import org.hipparchus.exception.LocalizedCoreFormats;
  20. import org.hipparchus.exception.MathIllegalArgumentException;
  21. import org.hipparchus.geometry.LocalizedGeometryFormats;
  22. import org.hipparchus.geometry.euclidean.twod.Euclidean2D;
  23. import org.hipparchus.geometry.euclidean.twod.Line;
  24. import org.hipparchus.geometry.euclidean.twod.Segment;
  25. import org.hipparchus.geometry.euclidean.twod.SubLine;
  26. import org.hipparchus.geometry.euclidean.twod.Vector2D;
  27. import org.hipparchus.geometry.hull.ConvexHull;
  28. import org.hipparchus.geometry.partitioning.Region;
  29. import org.hipparchus.geometry.partitioning.RegionFactory;
  30. import org.hipparchus.util.MathArrays;
  31. import org.hipparchus.util.Precision;

  32. /**
  33.  * This class represents a convex hull in an two-dimensional euclidean space.
  34.  *
  35.  */
  36. public class ConvexHull2D implements ConvexHull<Euclidean2D, Vector2D, Line, SubLine>, Serializable {

  37.     /** Serializable UID. */
  38.     private static final long serialVersionUID = 20140129L;

  39.     /** Vertices of the hull. */
  40.     private final Vector2D[] vertices;

  41.     /** Tolerance threshold used during creation of the hull vertices. */
  42.     private final double tolerance;

  43.     /**
  44.      * Line segments of the hull.
  45.      * The array is not serialized and will be created from the vertices on first access.
  46.      */
  47.     private transient Segment[] lineSegments;

  48.     /**
  49.      * Simple constructor.
  50.      * @param vertices the vertices of the convex hull, must be ordered
  51.      * @param tolerance tolerance below which points are considered identical
  52.      * @throws MathIllegalArgumentException if the vertices do not form a convex hull
  53.      */
  54.     public ConvexHull2D(final Vector2D[] vertices, final double tolerance)
  55.         throws MathIllegalArgumentException {

  56.         // assign tolerance as it will be used by the isConvex method
  57.         this.tolerance = tolerance;

  58.         if (!isConvex(vertices)) {
  59.             throw new MathIllegalArgumentException(LocalizedGeometryFormats.NOT_CONVEX);
  60.         }

  61.         this.vertices = vertices.clone();
  62.     }

  63.     /**
  64.      * Checks whether the given hull vertices form a convex hull.
  65.      * @param hullVertices the hull vertices
  66.      * @return {@code true} if the vertices form a convex hull, {@code false} otherwise
  67.      */
  68.     private boolean isConvex(final Vector2D[] hullVertices) {
  69.         if (hullVertices.length < 3) {
  70.             return true;
  71.         }

  72.         int sign = 0;
  73.         for (int i = 0; i < hullVertices.length; i++) {
  74.             final Vector2D p1 = hullVertices[i == 0 ? hullVertices.length - 1 : i - 1];
  75.             final Vector2D p2 = hullVertices[i];
  76.             final Vector2D p3 = hullVertices[i == hullVertices.length - 1 ? 0 : i + 1];

  77.             final Vector2D d1 = p2.subtract(p1);
  78.             final Vector2D d2 = p3.subtract(p2);

  79.             final double crossProduct = MathArrays.linearCombination(d1.getX(), d2.getY(), -d1.getY(), d2.getX());
  80.             final int cmp = Precision.compareTo(crossProduct, 0.0, tolerance);
  81.             // in case of collinear points the cross product will be zero
  82.             if (cmp != 0.0) {
  83.                 if (sign != 0.0 && cmp != sign) {
  84.                     return false;
  85.                 }
  86.                 sign = cmp;
  87.             }
  88.         }

  89.         return true;
  90.     }

  91.     /** {@inheritDoc} */
  92.     @Override
  93.     public Vector2D[] getVertices() {
  94.         return vertices.clone();
  95.     }

  96.     /**
  97.      * Get the line segments of the convex hull, ordered.
  98.      * @return the line segments of the convex hull
  99.      */
  100.     public Segment[] getLineSegments() {
  101.         return retrieveLineSegments().clone();
  102.     }

  103.     /**
  104.      * Retrieve the line segments from the cached array or create them if needed.
  105.      *
  106.      * @return the array of line segments
  107.      */
  108.     private Segment[] retrieveLineSegments() {
  109.         if (lineSegments == null) {
  110.             // construct the line segments - handle special cases of 1 or 2 points
  111.             final int size = vertices.length;
  112.             if (size <= 1) {
  113.                 this.lineSegments = new Segment[0];
  114.             } else if (size == 2) {
  115.                 this.lineSegments = new Segment[1];
  116.                 final Vector2D p1 = vertices[0];
  117.                 final Vector2D p2 = vertices[1];
  118.                 this.lineSegments[0] = new Segment(p1, p2, new Line(p1, p2, tolerance));
  119.             } else {
  120.                 this.lineSegments = new Segment[size];
  121.                 Vector2D firstPoint = null;
  122.                 Vector2D lastPoint = null;
  123.                 int index = 0;
  124.                 for (Vector2D point : vertices) {
  125.                     if (lastPoint == null) {
  126.                         firstPoint = point;
  127.                         lastPoint = point;
  128.                     } else {
  129.                         this.lineSegments[index++] =
  130.                                 new Segment(lastPoint, point, new Line(lastPoint, point, tolerance));
  131.                         lastPoint = point;
  132.                     }
  133.                 }
  134.                 this.lineSegments[index] =
  135.                         new Segment(lastPoint, firstPoint, new Line(lastPoint, firstPoint, tolerance));
  136.             }
  137.         }
  138.         return lineSegments;
  139.     }

  140.     /** {@inheritDoc} */
  141.     @Override
  142.     public Region<Euclidean2D, Vector2D, Line, SubLine> createRegion() throws MathIllegalArgumentException {
  143.         if (vertices.length < 3) {
  144.             throw new MathIllegalArgumentException(LocalizedCoreFormats.INSUFFICIENT_DATA);
  145.         }
  146.         final RegionFactory<Euclidean2D, Vector2D, Line, SubLine> factory = new RegionFactory<>();
  147.         final Segment[] segments = retrieveLineSegments();
  148.         final Line[] lineArray = new Line[segments.length];
  149.         for (int i = 0; i < segments.length; i++) {
  150.             lineArray[i] = segments[i].getLine();
  151.         }
  152.         return factory.buildConvex(lineArray);
  153.     }
  154. }