GeometryHullTool { public Geometry createHullFromGeometry( final Geometry clusterGeometry, final Collection<Coordinate> additionalPoints, final boolean fast) { if (additionalPoints.isEmpty()) { return clusterGeometry; } final Set<Coordinate> batchCoords = new HashSet<>(); if (clusterGeometry != null) { for (final Coordinate coordinate : clusterGeometry.getCoordinates()) { batchCoords.add(coordinate); } } for (final Coordinate coordinate : additionalPoints) { batchCoords.add(coordinate); } final GeometryFactory factory = clusterGeometry == null ? new GeometryFactory() : clusterGeometry.getFactory(); final Coordinate[] actualCoords = batchCoords.toArray(new Coordinate[batchCoords.size()]); if (batchCoords.size() == 2) { return factory.createLineString(actualCoords); } final ConvexHull convexHull = new ConvexHull(actualCoords, factory); final Geometry convexHullGeo = convexHull.getConvexHull(); try { if ((batchCoords.size() > 5) && (convexHullGeo.getArea() > 0.0)) { final Geometry concaveHull = fast ? concaveHull(convexHullGeo, batchCoords) : concaveHullParkOhMethod(convexHullGeo, batchCoords); if (fast && !concaveHull.isSimple()) { LOGGER.warn("Produced non simple hull", concaveHull.toText()); return concaveHullParkOhMethod(convexHullGeo, batchCoords); } return concaveHull; } else { return convexHullGeo; } } catch (final Exception ex) { LOGGER.error("Failed to compute hull", ex); return convexHullGeo; } }  void connect(final List<Geometry> geometries); DistanceFn<Coordinate> getDistanceFnForCoordinate(); void setDistanceFnForCoordinate(final DistanceFn<Coordinate> distanceFnForCoordinate); Geometry createHullFromGeometry(
      final Geometry clusterGeometry,
      final Collection<Coordinate> additionalPoints,
      final boolean fast); Geometry concaveHullParkOhMethod(
      final Geometry geometry,
      final Collection<Coordinate> providedInnerPoints); Geometry concaveHull(
      final Geometry geometry,
      final Collection<Coordinate> providedInnerPoints); static boolean intersectAnotherEdge(final Edge newEdge, final Edge edgeToReplace); static boolean edgesIntersect(final Edge e1, final Edge e2); Geometry connect(final Geometry shape1, final Geometry shape2); static boolean clockwise(final Coordinate[] set); static double calcSmallestAngle(
      final Coordinate one,
      final Coordinate vertex,
      final Coordinate two); static double calcAngle(
      final Coordinate one,
      final Coordinate vertex,
      final Coordinate two); static double[] calcDistanceSegment(
      final Coordinate start,
      final Coordinate end,
      final Coordinate point); static double calcDistance(
      final Coordinate start,
      final Coordinate end,
      final Coordinate point); static Pair<Integer, Integer> getClosestPoints(
      final Geometry shape1,
      final Geometry shape2,
      final DistanceFn<Coordinate> distanceFnForCoordinate);  }
@Test public void testCreateHullFromGeometry() { final GeometryHullTool cg = new GeometryHullTool(); cg.setDistanceFnForCoordinate(new CoordinateCircleDistanceFn()); for (int i = 2; i < 10; i++) { final Coordinate[] coords = new Coordinate[i]; for (int p = 0; p < i; p++) { coords[p] = new Coordinate(p, p); } final Geometry lineString1 = factory.createLineString(coords); final Geometry concaveHull1 = cg.createHullFromGeometry(lineString1, Arrays.asList(coords[0]), true); assertEquals( "straigh line size=" + i + " geo=" + lineString1.toText(), 2, concaveHull1.getCoordinates().length); } final Geometry lineString3 = factory.createLineString( new Coordinate[] {new Coordinate(1, 1), new Coordinate(2, 2), new Coordinate(3, 1)}); final Geometry concaveHull3 = cg.createHullFromGeometry( lineString3, Arrays.asList(lineString3.getCoordinates()[0]), true); assertEquals( "expecting a triangle " + concaveHull3.toText(), 4, concaveHull3.getCoordinates().length); assertTrue("expecting a triangle " + concaveHull3.toText(), concaveHull3.getArea() > 0.0); assertTrue(concaveHull3.isSimple()); assertEquals( "expecting identical result", lineString3, cg.createHullFromGeometry(lineString3, Collections.<Coordinate>emptyList(), true)); final Geometry[] newPoints = new Geometry[900]; for (int j = 0; j < 10; j++) { final Coordinate[] newCoords = new Coordinate[900]; final Coordinate[] geoCoords = new Coordinate[100]; final Random rand = new Random(73634 + j); for (int i = 0; i < 100; i++) { geoCoords[i] = new Coordinate(rand.nextGaussian() * 0.001, rand.nextGaussian() * 0.001); } for (int i = 0; i < 900; i++) { newCoords[i] = new Coordinate(rand.nextGaussian() * 0.01, rand.nextGaussian() * 0.01); newPoints[i] = factory.createPoint(newCoords[i]); } final ConvexHull hull = new ConvexHull(geoCoords, factory); final Geometry concaveHull = cg.createHullFromGeometry(hull.getConvexHull(), Arrays.asList(newCoords), true); assertTrue(concaveHull.isSimple()); int error = 0; for (final Geometry newPoint : newPoints) { error += concaveHull.intersects(newPoint) ? 0 : 1; } assertTrue(error < 3); final Geometry concaveHull2 = cg.createHullFromGeometry(hull.getConvexHull(), Arrays.asList(newCoords), false); assertTrue(concaveHull2.isSimple()); error = 0; for (final Geometry newPoint : newPoints) { error += concaveHull2.intersects(newPoint) ? 0 : 1; } assertTrue(error < 1); } }