AklToussaintHeuristic.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.hull;

  22. import java.util.ArrayList;
  23. import java.util.Collection;
  24. import java.util.List;

  25. import org.hipparchus.geometry.euclidean.twod.Vector2D;

  26. /**
  27.  * A simple heuristic to improve the performance of convex hull algorithms.
  28.  * <p>
  29.  * The heuristic is based on the idea of a convex quadrilateral, which is formed by
  30.  * four points with the lowest and highest x / y coordinates. Any point that lies inside
  31.  * this quadrilateral can not be part of the convex hull and can thus be safely discarded
  32.  * before generating the convex hull itself.
  33.  * <p>
  34.  * The complexity of the operation is O(n), and may greatly improve the time it takes to
  35.  * construct the convex hull afterwards, depending on the point distribution.
  36.  *
  37.  * @see <a href="http://en.wikipedia.org/wiki/Convex_hull_algorithms#Akl-Toussaint_heuristic">
  38.  * Akl-Toussaint heuristic (Wikipedia)</a>
  39.  */
  40. public final class AklToussaintHeuristic {

  41.     /** Hide utility constructor. */
  42.     private AklToussaintHeuristic() {
  43.     }

  44.     /**
  45.      * Returns a point set that is reduced by all points for which it is safe to assume
  46.      * that they are not part of the convex hull.
  47.      *
  48.      * @param points the original point set
  49.      * @return a reduced point set, useful as input for convex hull algorithms
  50.      */
  51.     public static Collection<Vector2D> reducePoints(final Collection<Vector2D> points) {

  52.         // find the leftmost point
  53.         int size = 0;
  54.         Vector2D minX = null;
  55.         Vector2D maxX = null;
  56.         Vector2D minY = null;
  57.         Vector2D maxY = null;
  58.         for (Vector2D p : points) {
  59.             if (minX == null || p.getX() < minX.getX()) {
  60.                 minX = p;
  61.             }
  62.             if (maxX == null || p.getX() > maxX.getX()) {
  63.                 maxX = p;
  64.             }
  65.             if (minY == null || p.getY() < minY.getY()) {
  66.                 minY = p;
  67.             }
  68.             if (maxY == null || p.getY() > maxY.getY()) {
  69.                 maxY = p;
  70.             }
  71.             size++;
  72.         }

  73.         if (size < 4) {
  74.             return points;
  75.         }

  76.         final List<Vector2D> quadrilateral = buildQuadrilateral(minY, maxX, maxY, minX);
  77.         // if the quadrilateral is not well formed, e.g. only 2 points, do not attempt to reduce
  78.         if (quadrilateral.size() < 3) {
  79.             return points;
  80.         }

  81.         final List<Vector2D> reducedPoints = new ArrayList<>(quadrilateral);
  82.         for (final Vector2D p : points) {
  83.             // check all points if they are within the quadrilateral
  84.             // in which case they can not be part of the convex hull
  85.             if (!insideQuadrilateral(p, quadrilateral)) {
  86.                 reducedPoints.add(p);
  87.             }
  88.         }

  89.         return reducedPoints;
  90.     }

  91.     /**
  92.      * Build the convex quadrilateral with the found corner points (with min/max x/y coordinates).
  93.      *
  94.      * @param points the respective points with min/max x/y coordinate
  95.      * @return the quadrilateral
  96.      */
  97.     private static List<Vector2D> buildQuadrilateral(final Vector2D... points) {
  98.         List<Vector2D> quadrilateral = new ArrayList<>();
  99.         for (Vector2D p : points) {
  100.             if (!quadrilateral.contains(p)) {
  101.                 quadrilateral.add(p);
  102.             }
  103.         }
  104.         return quadrilateral;
  105.     }

  106.     /**
  107.      * Checks if the given point is located within the convex quadrilateral.
  108.      * @param point the point to check
  109.      * @param quadrilateralPoints the convex quadrilateral, represented by 4 points
  110.      * @return {@code true} if the point is inside the quadrilateral, {@code false} otherwise
  111.      */
  112.     private static boolean insideQuadrilateral(final Vector2D point,
  113.                                                final List<Vector2D> quadrilateralPoints) {

  114.         Vector2D p1 = quadrilateralPoints.get(0);
  115.         Vector2D p2 = quadrilateralPoints.get(1);

  116.         if (point.equals(p1) || point.equals(p2)) {
  117.             return true;
  118.         }

  119.         // get the location of the point relative to the first two vertices
  120.         final double last = point.crossProduct(p1, p2);
  121.         final int size = quadrilateralPoints.size();
  122.         // loop through the rest of the vertices
  123.         for (int i = 1; i < size; i++) {
  124.             p1 = p2;
  125.             p2 = quadrilateralPoints.get((i + 1) == size ? 0 : i + 1);

  126.             if (point.equals(p1) || point.equals(p2)) {
  127.                 return true;
  128.             }

  129.             // do side of line test: multiply the last location with this location
  130.             // if they are the same sign then the operation will yield a positive result
  131.             // -x * -y = +xy, x * y = +xy, -x * y = -xy, x * -y = -xy
  132.             if (last * point.crossProduct(p1, p2) < 0) {
  133.                 return false;
  134.             }
  135.         }
  136.         return true;
  137.     }

  138. }