public Geometry createHullFromGeometry( Geometry clusterGeometry, Collection<Coordinate> additionalPoints, boolean fast) { if (additionalPoints.isEmpty()) return clusterGeometry; final Set<Coordinate> batchCoords = new HashSet<Coordinate>(); for (final Coordinate coordinate : clusterGeometry.getCoordinates()) { batchCoords.add(coordinate); } for (final Coordinate coordinate : additionalPoints) { batchCoords.add(coordinate); } final Coordinate[] actualCoords = batchCoords.toArray(new Coordinate[batchCoords.size()]); if (batchCoords.size() == 2) { return clusterGeometry.getFactory().createLineString(actualCoords); } final ConvexHull convexHull = new ConvexHull(actualCoords, clusterGeometry.getFactory()); final Geometry convexHullGeo = convexHull.getConvexHull(); try { // does this shape benefit from concave hulling? // it cannot be a line string if (batchCoords.size() > 5 && convexHullGeo.getArea() > 0.0) { final Geometry concaveHull = fast ? concaveHull(convexHullGeo, batchCoords) : this.concaveHullParkOhMethod(convexHullGeo, batchCoords); if (!concaveHull.isSimple()) { LOGGER.warn("Produced non simple hull", concaveHull.toText()); return convexHullGeo; } return concaveHull; } else { return convexHullGeo; } } catch (final Exception ex) { /* * Geometry[] points = new Geometry[actualCoords.length + 1]; for * (int i = 0; i < actualCoords.length; i++) points[i] = * hull.getFactory().createPoint( actualCoords[i]); * points[points.length - 1] = hull; try { ShapefileTool.writeShape( * "test_perf_xh", new File( "./targettest_perf_xh"), points); } * catch (IOException e) { e.printStackTrace(); } */ LOGGER.error("Failed to compute hull", ex); return convexHullGeo; } }
/** * Finds the geometric attribute requested by the symbolizer. * * @param feature The victim * @param symbolizer The symbolizer * @param style the resolved style for the specified victim * @return The geometry requested in the symbolizer, or the default geometry if none is specified */ private com.vividsolutions.jts.geom.Geometry findGeometry( SimpleFeature feature, Symbolizer symbolizer) { String geomName = getGeometryPropertyName(symbolizer); // get the geometry com.vividsolutions.jts.geom.Geometry geometry; if (geomName == null || feature.getType().getDescriptor(geomName) == null) { geometry = (Geometry) feature.getDefaultGeometry(); } else { geometry = (com.vividsolutions.jts.geom.Geometry) feature.getAttribute(geomName); } if (geometry == null) { return null; // nothing to see here } // if the symbolizer is a point or text symbolizer generate a suitable // location to place the // point in order to avoid recomputing that location at each rendering // step if ((symbolizer instanceof PointSymbolizer || symbolizer instanceof TextSymbolizer) && !(geometry instanceof Point)) { if (geometry instanceof LineString && !(geometry instanceof LinearRing)) { // use the mid point to represent the point/text symbolizer // anchor Coordinate[] coordinates = geometry.getCoordinates(); Coordinate start = coordinates[0]; Coordinate end = coordinates[1]; Coordinate mid = new Coordinate((start.x + end.x) / 2, (start.y + end.y) / 2); geometry = geometry.getFactory().createPoint(mid); } else { // otherwise use the centroid of the polygon geometry = geometry.getCentroid(); } } return geometry; }
/** * split intersection segments have interior location at both left and right * * @param utilSplitLine * @param polygon * @param holesList */ private void addSplitLineIntoGraph( final Geometry utilSplitLine, final Polygon polygon, List<LineString> holesList) { // split intersection segments have interior location at both left // and right Geometry intersectingLineStrings = utilSplitLine.intersection(polygon); if (intersectingLineStrings.getNumGeometries() > 1) { // If points exist, then remove them. intersectingLineStrings = filterLineString(intersectingLineStrings); } // use the same input used to create hole edges Geometry holeCollection = intersectingLineStrings .getFactory() .createMultiLineString(holesList.toArray(new LineString[holesList.size()])); Geometry holeGeometries = holeCollection.difference(utilSplitLine); insertEdge( intersectingLineStrings, holeGeometries, Location.BOUNDARY, Location.INTERIOR, Location.INTERIOR); }
private FeatureCollection convexHhull(TaskMonitor monitor, FeatureCollection fc) { monitor.allowCancellationRequests(); monitor.report(I18N.get("ui.plugin.analysis.ConvexHullPlugIn.Computing-Convex-Hull") + "..."); int size = fc.size(); GeometryFactory geomFact = null; if (size == 0) { return null; } int count = 0; Geometry[] geoms = new Geometry[size]; for (Iterator i = fc.iterator(); i.hasNext(); ) { Feature f = (Feature) i.next(); Geometry geom = f.getGeometry(); if (geom == null) { continue; } if (geomFact == null) { geomFact = geom.getFactory(); } geoms[count++] = geom; } GeometryCollection gc = geomFact.createGeometryCollection(geoms); Geometry hull = gc.convexHull(); List hullList = new ArrayList(); hullList.add(hull); return FeatureDatasetFactory.createFromGeometry(hullList); }
private Geometry polygonize(Geometry geometry) { List lines = LineStringExtracter.getLines(geometry); Polygonizer polygonizer = new Polygonizer(); polygonizer.add(lines); Collection polys = polygonizer.getPolygons(); Polygon[] polyArray = GeometryFactory.toPolygonArray(polys); return geometry.getFactory().createGeometryCollection(polyArray); }
private Geometry intersectionWithSegment( Coordinate[] holeCoords, int i, Geometry intersectingSegment) { Coordinate[] holeSegmentCoord = new Coordinate[] {holeCoords[i], holeCoords[i + 1]}; LineString holeSegment; GeometryFactory geomFact = intersectingSegment.getFactory(); holeSegment = geomFact.createLineString(holeSegmentCoord); Geometry intersection = holeSegment.intersection(intersectingSegment); return intersection; }
@Override public void encodeGeometryValue(Geometry value, int srid, StringBuffer sql) throws IOException { if (value == null || value.isEmpty()) { sql.append("NULL"); } else { if (value instanceof LinearRing) { // postgis does not handle linear rings, convert to just a line string value = value.getFactory().createLineString(((LinearRing) value).getCoordinateSequence()); } sql.append("ST_GeomFromText('" + value.toText() + "', " + srid + ")"); } }
protected Geometry distillSameTypeGeometries(GeometryCollection coll, Geometry original) { if (original instanceof Polygon || original instanceof MultiPolygon) { List<Polygon> polys = new ArrayList<Polygon>(); accumulateGeometries(polys, coll, Polygon.class); return original .getFactory() .createMultiPolygon(((Polygon[]) polys.toArray(new Polygon[polys.size()]))); } else if (original instanceof LineString || original instanceof MultiLineString) { List<LineString> ls = new ArrayList<LineString>(); accumulateGeometries(ls, coll, LineString.class); return original .getFactory() .createMultiLineString((LineString[]) ls.toArray(new LineString[ls.size()])); } else if (original instanceof Point || original instanceof MultiPoint) { List<LineString> points = new ArrayList<LineString>(); accumulateGeometries(points, coll, LineString.class); return original .getFactory() .createMultiPoint((Point[]) points.toArray(new Point[points.size()])); } else { return original; } }
@Override public void setGeometryValue( Geometry g, int srid, Class binding, PreparedStatement ps, int column) throws SQLException { if (g != null) { if (g instanceof LinearRing) { // ingres does not handle linear rings, convert to just a line string g = g.getFactory().createLineString(((LinearRing) g).getCoordinateSequence()); } byte[] bytes = new WKBWriter().write(g); ps.setBytes(column, bytes); } else { ps.setBytes(column, null); } }
@Override @SuppressWarnings("rawtypes") public void setGeometryValue( Geometry g, int srid, Class binding, PreparedStatement ps, int column) throws SQLException { if (g != null) { if (g instanceof LinearRing) { // WKT does not support linear rings g = g.getFactory().createLineString(((LinearRing) g).getCoordinateSequence()); } byte[] bytes = new WKBWriter().write(g); ps.setBytes(column, bytes); } else { ps.setNull(column, Types.OTHER, "Geometry"); } }
public Geometry buffer(Geometry g, double distance) { PrecisionModel precisionModel = workingPrecisionModel; if (precisionModel == null) precisionModel = g.getPrecisionModel(); // factory must be the same as the one used by the input geomFact = g.getFactory(); OffsetCurveBuilder curveBuilder = new OffsetCurveBuilder(precisionModel, bufParams); OffsetCurveSetBuilder curveSetBuilder = new OffsetCurveSetBuilder(g, distance, curveBuilder); List bufferSegStrList = curveSetBuilder.getCurves(); // short-circuit test if (bufferSegStrList.size() <= 0) { return createEmptyResultGeometry(); } // BufferDebug.runCount++; // String filename = "run" + BufferDebug.runCount + "_curves"; // System.out.println("saving " + filename); // BufferDebug.saveEdges(bufferEdgeList, filename); // DEBUGGING ONLY // WKTWriter wktWriter = new WKTWriter(); // Debug.println("Rings: " + wktWriter.write(convertSegStrings(bufferSegStrList.iterator()))); // wktWriter.setMaxCoordinatesPerLine(10); // System.out.println(wktWriter.writeFormatted(convertSegStrings(bufferSegStrList.iterator()))); computeNodedEdges(bufferSegStrList, precisionModel); graph = new PlanarGraph(new OverlayNodeFactory()); graph.addEdges(edgeList.getEdges()); List subgraphList = createSubgraphs(graph); PolygonBuilder polyBuilder = new PolygonBuilder(geomFact); buildSubgraphs(subgraphList, polyBuilder); List resultPolyList = polyBuilder.getPolygons(); // just in case... if (resultPolyList.size() <= 0) { return createEmptyResultGeometry(); } Geometry resultGeom = geomFact.buildGeometry(resultPolyList); return resultGeom; }
/** * Gets the AWT {@link Shape} we'll use to represent {@code geom} on the map. * * @param geom The geometry we want to draw. * @param generalize If true we'll perform generalization * @return An AWT Shape instance. */ public Shape getShape(Geometry geom, boolean generalize) { if (generalize) { Rectangle2DDouble rectangle2dDouble = toPixel(geom.getEnvelopeInternal()); if ((rectangle2dDouble.getHeight() <= MAXPIXEL_DISPLAY) && (rectangle2dDouble.getWidth() <= MAXPIXEL_DISPLAY)) { if (geom.getDimension() == 1) { Coordinate[] coords = geom.getCoordinates(); return getShapeWriter() .toShape( geom.getFactory() .createLineString(new Coordinate[] {coords[0], coords[coords.length - 1]})); } else { return rectangle2dDouble; } } } return getShapeWriter().toShape(geom); }
public final Geometry transform(Geometry inputGeom) { this.inputGeom = inputGeom; this.factory = inputGeom.getFactory(); if (inputGeom instanceof Point) return transformPoint((Point) inputGeom, null); if (inputGeom instanceof MultiPoint) return transformMultiPoint((MultiPoint) inputGeom, null); if (inputGeom instanceof LinearRing) return transformLinearRing((LinearRing) inputGeom, null); if (inputGeom instanceof LineString) return transformLineString((LineString) inputGeom, null); if (inputGeom instanceof MultiLineString) return transformMultiLineString((MultiLineString) inputGeom, null); if (inputGeom instanceof Polygon) return transformPolygon((Polygon) inputGeom, null); if (inputGeom instanceof MultiPolygon) return transformMultiPolygon((MultiPolygon) inputGeom, null); if (inputGeom instanceof GeometryCollection) return transformGeometryCollection((GeometryCollection) inputGeom, null); throw new IllegalArgumentException( "Unknown Geometry subtype: " + inputGeom.getClass().getName()); }
/** * Only return the lines contained on the given geometry, the non lines geometry are rejected. * * @param geometry Intersection geometry between split line and source geometry. * @return The valid geometries needed for the graph, those are lines and multiLines. */ private Geometry filterLineString(Geometry geometry) { List<Geometry> filteredLines = new ArrayList<Geometry>(); for (int i = 0; i < geometry.getNumGeometries(); i++) { Geometry possibleLine = geometry.getGeometryN(i); // if there are point geometries, discard it. if (possibleLine instanceof LineString || possibleLine instanceof MultiLineString) { // also remove very very short liens. if (possibleLine.getLength() > UsefulSplitLineBuilder.DEPRECIATE_VALUE) { filteredLines.add(possibleLine); } } } GeometryFactory gf = geometry.getFactory(); return gf.buildGeometry(filteredLines); }
protected Geometry connect( final Geometry shape1, final Geometry shape2, final Pair<Integer, Integer> closestCoordinates) { Coordinate[] leftCoords = shape1.getCoordinates(), rightCoords = shape2.getCoordinates(); int startLeft, startRight; if ((leftCoords[closestCoordinates.getLeft()].x < rightCoords[closestCoordinates.getRight()].x)) { startLeft = closestCoordinates.getLeft(); startRight = closestCoordinates.getRight(); } else { leftCoords = shape2.getCoordinates(); rightCoords = shape1.getCoordinates(); startLeft = closestCoordinates.getRight(); startRight = closestCoordinates.getLeft(); } final HashSet<Coordinate> visitedSet = new HashSet<Coordinate>(); visitedSet.add(leftCoords[startLeft]); visitedSet.add(rightCoords[startRight]); final boolean leftClockwise = clockwise(leftCoords); final boolean rightClockwise = clockwise(rightCoords); final Pair<Integer, Integer> upperCoords = walk( visitedSet, leftCoords, rightCoords, startLeft, startRight, new DirectionFactory() { @Override public Direction createLeftFootDirection(final int start, final int max) { return leftClockwise ? new IncreaseDirection(start, max, true) : new DecreaseDirection(start, max, true); } @Override public Direction createRightFootDirection(final int start, final int max) { return rightClockwise ? new DecreaseDirection(start, max, false) : new IncreaseDirection(start, max, false); } }); final Pair<Integer, Integer> lowerCoords = walk( visitedSet, leftCoords, rightCoords, startLeft, startRight, new DirectionFactory() { @Override public Direction createLeftFootDirection(final int start, final int max) { return leftClockwise ? new DecreaseDirection(start, max, false) : new IncreaseDirection(start, max, false); } @Override public Direction createRightFootDirection(final int start, final int max) { return rightClockwise ? new IncreaseDirection(start, max, true) : new DecreaseDirection(start, max, true); } }); final List<Coordinate> newCoordinateSet = new ArrayList<Coordinate>(); final Direction leftSet = leftClockwise ? new IncreaseDirection( upperCoords.getLeft(), lowerCoords.getLeft() + 1, leftCoords.length) : new DecreaseDirection( upperCoords.getLeft(), lowerCoords.getLeft() - 1, leftCoords.length); newCoordinateSet.add(leftCoords[upperCoords.getLeft()]); while (leftSet.hasNext()) { newCoordinateSet.add(leftCoords[leftSet.next()]); } final Direction rightSet = rightClockwise ? new IncreaseDirection( lowerCoords.getRight(), upperCoords.getRight() + 1, rightCoords.length) : new DecreaseDirection( lowerCoords.getRight(), upperCoords.getRight() - 1, rightCoords.length); newCoordinateSet.add(rightCoords[lowerCoords.getRight()]); while (rightSet.hasNext()) { newCoordinateSet.add(rightCoords[rightSet.next()]); } newCoordinateSet.add(leftCoords[upperCoords.getLeft()]); return shape1 .getFactory() .createPolygon(newCoordinateSet.toArray(new Coordinate[newCoordinateSet.size()])); }
/** * Checks if the intersecting segment intersects with a hole in two points. In that case, the * segment might be adjusted following the orientation of the intersected hole. * * @param intersectingSegment this segment that could intersect with a hole * @param holeGeometries the polygon hole list */ private LineString adjustSegmentToHoleDirection( final Geometry intersectingSegment, final Geometry holeGeometries) { LineString intersectedHole = intersectionHole(intersectingSegment, holeGeometries); if (intersectedHole == null) { return (LineString) intersectingSegment; // it does not require // adjust orientation } // Traverses the hole-segments until the second intersection with the // intersectingSegment is found // a ring will be created with those segments between first intersection // and second intersection. Coordinate secondIntersection = null; Coordinate firstIntersection = null; int j = -1; Coordinate[] holeCoords = intersectedHole.getCoordinates(); List<Coordinate> ring = new LinkedList<Coordinate>(); for (int i = 0; i < holeCoords.length - 1; i++) { Geometry intersection = intersectionWithSegment(holeCoords, i, intersectingSegment); if (intersection instanceof Point) { // store first and second coordinates if (firstIntersection == null) { firstIntersection = intersection.getCoordinate(); ring.add(firstIntersection); ring.add(holeCoords[i + 1]); j = i + 1; break; } // Adds the rest of segments in the ring until found a second // intersection } } assert firstIntersection != null && j != -1; while (true) { Geometry intersection = intersectionWithSegment(holeCoords, j, intersectingSegment); if (intersection instanceof Point && !intersection.getCoordinate().equals2D(firstIntersection)) { secondIntersection = intersection.getCoordinate(); ring.add(secondIntersection); // close the ring ring.add(firstIntersection); break; } else { ring.add(holeCoords[j + 1]); } j++; } assert secondIntersection != null; // Creates the adjusted line following this rules: // - if the ring is CW then the result line must be this: first // intersection coordinate--> second intersection coordinate. // - if the ring is CCW the the result line must be this: second // intersection coordinate --> first intersection coordinate. GeometryFactory factory = intersectingSegment.getFactory(); LinearRing linearRing = factory.createLinearRing(ring.toArray(new Coordinate[ring.size()])); LineString adjustedSegment = null; if (isCW(linearRing)) { adjustedSegment = createAdjustedSegment(firstIntersection, secondIntersection, factory); } else { adjustedSegment = createAdjustedSegment(secondIntersection, firstIntersection, factory); } return adjustedSegment; }
public static Geometry obtenerGeometriaParcela(String dxf, WorkbenchContext context) { Geometry geometryParcela = null; GeopistaLoadDxfQueryChooser dxfLoad = new GeopistaLoadDxfQueryChooser(Dxf.class, "GEOPISTA dxf", extensions(Dxf.class), context); InputStream fileDXF = ImportarUtils_LCGIII.parseStringToIS(dxf); try { Assert.isTrue(!dxfLoad.getDataSourceQueries(fileDXF).isEmpty()); } catch (AssertionFailedException e) { throw new AssertionFailedException(I18N.get("FileEmpty")); } fileDXF = ImportarUtils_LCGIII.parseStringToIS(dxf); boolean exceptionsEncountered = false; for (Iterator i = dxfLoad.getDataSourceQueries(fileDXF).iterator(); i.hasNext(); ) { DataSourceQuery dataSourceQuery = (DataSourceQuery) i.next(); ArrayList exceptions = new ArrayList(); Assert.isTrue(dataSourceQuery.getDataSource().isReadable()); Connection connection = dataSourceQuery.getDataSource().getConnection(); try { FeatureCollection dataset = dataSourceQuery .getDataSource() .installCoordinateSystem( connection.executeQuery(dataSourceQuery.getQuery(), exceptions, null), null); if (dataset != null) { String layerName = dataSourceQuery.toString(); Geometry geometriaInicial = null; GeopistaFeature featureInicial = null; if (layerName.startsWith("PG-LP")) { // Obtener el borde con las features de la capa ArrayList lstFeatures = new ArrayList(); for (Iterator features = dataset.getFeatures().iterator(); features.hasNext(); ) { GeopistaFeature feature = (GeopistaFeature) features.next(); lstFeatures.add(feature); } ArrayList coordenadas = new ArrayList(); if (lstFeatures != null && lstFeatures.size() > 0) { featureInicial = (GeopistaFeature) lstFeatures.iterator().next(); lstFeatures.remove(featureInicial); geometriaInicial = featureInicial.getGeometry(); for (int indice = 0; indice < geometriaInicial.getCoordinates().length; indice++) coordenadas.add(geometriaInicial.getCoordinates()[indice]); if (geometriaInicial instanceof LineString) { Point puntoFinal = ((LineString) geometriaInicial).getEndPoint(); GeopistaFeature feature = null; Geometry geometria = null; int indice; while (lstFeatures.size() > 0) { boolean encontrado = false; Iterator features = lstFeatures.iterator(); while (features.hasNext() && !encontrado) { feature = (GeopistaFeature) features.next(); geometria = feature.getGeometry(); if (geometria instanceof LineString) { if (puntoFinal.distance(((LineString) geometria).getStartPoint()) == 0) { for (indice = 1; indice < geometria.getCoordinates().length; indice++) coordenadas.add(geometria.getCoordinates()[indice]); puntoFinal = ((LineString) geometria).getEndPoint(); encontrado = true; } else if (puntoFinal.distance(((LineString) geometria).getEndPoint()) == 0) { for (indice = geometria.getCoordinates().length - 2; indice >= 0; indice--) coordenadas.add(geometria.getCoordinates()[indice]); puntoFinal = ((LineString) geometria).getStartPoint(); encontrado = true; } } } if (encontrado) { lstFeatures.remove(feature); } } Coordinate[] coordenadasParcela = new Coordinate[coordenadas.size()]; indice = 0; for (Iterator coordenada = coordenadas.iterator(); coordenada.hasNext(); ) { coordenadasParcela[indice] = (Coordinate) coordenada.next(); indice++; } if (coordenadasParcela[0].equals3D( coordenadasParcela[coordenadasParcela.length - 1])) { LinearRing lineaParcela = geometriaInicial.getFactory().createLinearRing(coordenadasParcela); Polygon poligonoParcela = null; poligonoParcela = geometriaInicial.getFactory().createPolygon(lineaParcela, null); geometryParcela = poligonoParcela; } } } } } } finally { connection.close(); } if (!exceptions.isEmpty()) { if (!exceptionsEncountered) { context.getIWorkbench().getFrame().getOutputFrame().createNewDocument(); exceptionsEncountered = true; } reportExceptions(exceptions, dataSourceQuery, context); } } if (exceptionsEncountered) { context .getIWorkbench() .getGuiComponent() .warnUser("Problems were encountered. See Output Window for details."); } return geometryParcela; }
/** * Gift unwrapping (e.g. dig) concept, taking a convex hull and a set of inner points, add inner * points to the hull without violating hull invariants--all points must reside on the hull or * inside the hull. Based on: Jin-Seo Park and Se-Jong Oh. "A New Concave Algorithm and * Concaveness Measure for n-dimensional Datasets" . Department of Nanobiomedical Science. Dankook * University". 2010. * * <p>Per the paper, N = concaveThreshold * * @param geometry * @param providedInnerPoints * @return */ public Geometry concaveHullParkOhMethod( final Geometry geometry, final Collection<Coordinate> providedInnerPoints) { final Set<Coordinate> innerPoints = new HashSet<Coordinate>(providedInnerPoints); final TreeSet<Edge> edges = new TreeSet<Edge>(); final Coordinate[] geoCoordinateList = geometry.getCoordinates(); final int s = geoCoordinateList.length - 1; final Edge firstEdge = createEdgeWithSideEffects(geoCoordinateList[0], geoCoordinateList[1], innerPoints, edges); Edge lastEdge = firstEdge; for (int i = 1; i < s; i++) { final Edge newEdge = createEdgeWithSideEffects( geoCoordinateList[i], geoCoordinateList[i + 1], innerPoints, edges); newEdge.connectLast(lastEdge); lastEdge = newEdge; } firstEdge.connectLast(lastEdge); while (!edges.isEmpty() && !innerPoints.isEmpty()) { final Edge edge = edges.pollLast(); lastEdge = edge; double score = Double.MAX_VALUE; Coordinate selectedCandidate = null; for (final Coordinate candidate : innerPoints) { final double dist = calcDistance(edge.start, edge.end, candidate); // on the hull if (MathUtils.equals(dist, 0.0, 0.000000001)) { score = 0.0; selectedCandidate = candidate; break; } if ((dist > 0) && (dist < score)) { score = dist; selectedCandidate = candidate; } } if (selectedCandidate == null) { continue; } // if one a line segment of the hull, then remove candidate if (score == 0.0) { innerPoints.remove(selectedCandidate); edges.add(edge); continue; } // Park and Oh look only at the neighbor edges // but this fails in some cases. if (isCandidateCloserToAnotherEdge(score, edge, edges, selectedCandidate)) { continue; } innerPoints.remove(selectedCandidate); final double eh = edge.distance; final double startToCandidate = distanceFnForCoordinate.measure(edge.start, selectedCandidate); final double endToCandidate = distanceFnForCoordinate.measure(edge.end, selectedCandidate); final double min = Math.min(startToCandidate, endToCandidate); // protected against duplicates if ((eh / min) > concaveThreshold) { final Edge newEdge1 = new Edge(edge.start, selectedCandidate, startToCandidate); final Edge newEdge2 = new Edge(selectedCandidate, edge.end, endToCandidate); // need to replace this with something more intelligent. This // occurs in cases of sharp angles. An angular approach may also // work // look for an angle to flip in the reverse direction. if (!intersectAnotherEdge(newEdge1, edge) && !intersectAnotherEdge(newEdge2, edge) && !intersectAnotherEdge(newEdge1, edge.last) && !intersectAnotherEdge(newEdge2, edge.next)) { edges.add(newEdge2); edges.add(newEdge1); newEdge1.connectLast(edge.last); newEdge2.connectLast(newEdge1); edge.next.connectLast(newEdge2); lastEdge = newEdge1; } } } return geometry.getFactory().createPolygon(reassemble(lastEdge)); }
/** * Gift unwrapping (e.g. dig) concept, taking a convex hull and a set of inner points, add inner * points to the hull without violating hull invariants--all points must reside on the hull or * inside the hull. Based on: Jin-Seo Park and Se-Jong Oh. "A New Concave Algorithm and * Concaveness Measure for n-dimensional Datasets" . Department of Nanobiomedical Science. Dankook * University". 2010. * * <p>Per the paper, N = concaveThreshold. * * <p>This algorithm evaluates remarkably faster than Park and Oh, but the quality of the result * is marginally less. If it is acceptable to have some small number of points fall outside of the * hull and speed is critical, use this method. The measure of error is difficult to calculate * since it is not directly calculated based on the number of inner points. Rather, the measure is * based on some number of points in proximity the optimal concave hull. * * @param geometry * @param providedInnerPoints * @return */ public Geometry concaveHull( final Geometry geometry, final Collection<Coordinate> providedInnerPoints) { final Set<Coordinate> innerPoints = (providedInnerPoints instanceof Set) ? (Set<Coordinate>) providedInnerPoints : new HashSet<Coordinate>(providedInnerPoints); final TreeSet<Edge> edges = new TreeSet<Edge>(); final Coordinate[] geoCoordinateList = geometry.getCoordinates(); final int s = geoCoordinateList.length - 1; final Edge firstEdge = createEdgeWithSideEffects(geoCoordinateList[0], geoCoordinateList[1], innerPoints, edges); Edge lastEdge = firstEdge; for (int i = 1; i < s; i++) { final Edge newEdge = createEdgeWithSideEffects( geoCoordinateList[i], geoCoordinateList[i + 1], innerPoints, edges); newEdge.connectLast(lastEdge); lastEdge = newEdge; } firstEdge.connectLast(lastEdge); for (final Coordinate candidate : innerPoints) { double min = Double.MAX_VALUE; Edge bestEdge = null; for (final Edge edge : edges) { final double dist = calcDistance(edge.start, edge.end, candidate); if ((dist > 0) && (dist < min)) { min = dist; bestEdge = edge; } } if (bestEdge != null) { bestEdge.getPoints().add(new NeighborData<Coordinate>(candidate, null, min)); } } while (!edges.isEmpty()) { final Edge edge = edges.pollLast(); lastEdge = edge; NeighborData<Coordinate> candidate = edge.getPoints().pollFirst(); while (candidate != null) { if (!MathUtils.equals(candidate.getDistance(), 0.0, 0.000000001)) { final Coordinate selectedCandidate = candidate.getElement(); final double eh = edge.distance; final double startToCandidate = distanceFnForCoordinate.measure(edge.start, selectedCandidate); final double endToCandidate = distanceFnForCoordinate.measure(edge.end, selectedCandidate); final double min = Math.min(startToCandidate, endToCandidate); // protected against duplicates if ((eh / min) > concaveThreshold) { final Edge newEdge1 = new Edge(edge.start, selectedCandidate, startToCandidate); final Edge newEdge2 = new Edge(selectedCandidate, edge.end, endToCandidate); edges.add(newEdge2); edges.add(newEdge1); newEdge1.connectLast(edge.last); newEdge2.connectLast(newEdge1); edge.next.connectLast(newEdge2); lastEdge = newEdge1; for (final NeighborData<Coordinate> otherPoint : edge.getPoints()) { final double[] distProfile1 = calcDistanceSegment(newEdge1.start, newEdge1.end, otherPoint.getElement()); final double[] distProfile2 = calcDistanceSegment(newEdge2.start, newEdge2.end, otherPoint.getElement()); if (distProfile1[0] >= 0.0 && distProfile1[0] <= 1.0) { if (distProfile1[0] < 0.0 || distProfile1[0] > 1.0 || distProfile2[1] > distProfile1[1]) { otherPoint.setDistance(distProfile1[1]); newEdge1.getPoints().add(otherPoint); } else { otherPoint.setDistance(distProfile2[1]); newEdge2.getPoints().add(otherPoint); } } else if (distProfile2[0] >= 0.0 && distProfile2[0] <= 1.0) { otherPoint.setDistance(distProfile2[1]); newEdge2.getPoints().add(otherPoint); } } edge.getPoints().clear(); // forces this loop to end } } candidate = edge.getPoints().pollFirst(); } } return geometry.getFactory().createPolygon(reassemble(lastEdge)); }