/**
* H2GIS is a library that brings spatial support to the H2 Database Engine
* <http://www.h2database.com>. H2GIS is developed by CNRS
* <http://www.cnrs.fr/>.
*
* This code is part of the H2GIS project. H2GIS is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation;
* version 3.0 of the License.
*
* H2GIS is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details <http://www.gnu.org/licenses/>.
*
*
* For more information, please consult: <http://www.h2gis.org/>
* or contact directly: info_at_h2gis.org
*/
package org.h2gis.functions.spatial.mesh;
import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.triangulate.VoronoiDiagramBuilder;
import com.vividsolutions.jts.triangulate.quadedge.QuadEdge;
import com.vividsolutions.jts.triangulate.quadedge.QuadEdgeSubdivision;
import com.vividsolutions.jts.triangulate.quadedge.TriangleVisitor;
import org.h2gis.api.DeterministicScalarFunction;
import org.h2gis.utilities.jts_utils.Voronoi;
import java.sql.SQLException;
import java.util.*;
/**
* @author Nicolas Fortin
*/
public class ST_Voronoi extends DeterministicScalarFunction {
private static final int DEFAULT_DIMENSION = 2;
public ST_Voronoi() {
addProperty(PROP_REMARKS, "Construct a voronoi diagram from a delaunay triangulation or a set of points.\n" +
"ST_VORONOI(THE_GEOM MULTIPOLYGON)\n" +
"ST_VORONOI(THE_GEOM MULTIPOLYGON,OUT_DIMENSION INTEGER)\n" +
"ST_VORONOI(THE_GEOM MULTIPOLYGON,OUT_DIMENSION INTEGER,ENVELOPE POLYGON)\n" +
"ST_VORONOI(THE_GEOM MULTIPOINTS)\n" +
"ST_VORONOI(THE_GEOM MULTIPOINTS,OUT_DIMENSION INTEGER)\n" +
"ST_VORONOI(THE_GEOM MULTIPOINTS,OUT_DIMENSION INTEGER,ENVELOPE POLYGON)\n" +
"Ex:\n" +
"SELECT ST_VORONOI(ST_DELAUNAY('MULTIPOINT(2 2 0,6 3 0,4 7 0,2 8 0,1 6 0,3 5 0)')) the_geom;\n" +
"SELECT ST_VORONOI(ST_DELAUNAY('MULTIPOINT(2 2 0,6 3 0,4 7 0,2 8 0,1 6 0,3 5 0)'), 1)\n" +
"SELECT ST_VORONOI(ST_DELAUNAY('MULTIPOINT(2 2 0,6 3 0,4 7 0,2 8 0,1 6 0,3 5 0)'), 1, ST_EXPAND('POINT(3 5)', 10, 10))");
}
@Override
public String getJavaStaticMethod() {
return "voronoi";
}
public static GeometryCollection voronoi(Geometry geomCollection) throws SQLException {
return voronoi(geomCollection, DEFAULT_DIMENSION);
}
public static GeometryCollection voronoi(Geometry geomCollection, int outputDimension) throws SQLException {
return voronoi(geomCollection, outputDimension, null);
}
private static GeometryCollection returnEmptyCollection(int outputDimension) {
switch (outputDimension) {
case 2:
return new GeometryFactory().createMultiPolygon(new Polygon[0]);
case 1:
return new GeometryFactory().createMultiLineString(new LineString[0]);
default:
return new GeometryFactory().createMultiPoint(new Point[0]);
}
}
public static GeometryCollection voronoi(Geometry geomCollection, int outputDimension, Geometry envelope) throws SQLException {
if(geomCollection == null) {
return returnEmptyCollection(outputDimension);
}
if(geomCollection instanceof MultiPoint || (geomCollection instanceof GeometryCollection &&
geomCollection.getNumGeometries() > 0 && geomCollection.getGeometryN(0) instanceof Point) ) {
// From point set use JTS
VoronoiDiagramBuilder diagramBuilder = new VoronoiDiagramBuilder();
diagramBuilder.setSites(geomCollection);
if(envelope != null) {
diagramBuilder.setClipEnvelope(envelope.getEnvelopeInternal());
}
if(outputDimension == 2) {
// Output directly the polygons
return (GeometryCollection) diagramBuilder.getDiagram(geomCollection.getFactory());
} else if (outputDimension == 1) {
// Convert into lineStrings.
return mergeTrianglesEdges((GeometryCollection)diagramBuilder.getDiagram(geomCollection.getFactory()));
} else {
// Extract triangles Circumcenter
QuadEdgeSubdivision subdivision = diagramBuilder.getSubdivision();
List<Coordinate> circumcenter = new ArrayList<Coordinate>(geomCollection.getNumGeometries());
subdivision.visitTriangles(new TriangleVisitorCircumCenter(circumcenter), false);
return geomCollection.getFactory().createMultiPoint(circumcenter.toArray(new Coordinate[circumcenter.size()]));
}
} else {
if(Double.compare(geomCollection.getEnvelopeInternal().getArea(), 0d) == 0) {
return returnEmptyCollection(outputDimension);
}
// Triangle input use internal method
Voronoi voronoi = new Voronoi();
if (envelope != null) {
voronoi.setEnvelope(envelope.getEnvelopeInternal());
}
voronoi.generateTriangleNeighbors(geomCollection);
return voronoi.generateVoronoi(outputDimension);
}
}
private static class TriangleVisitorCircumCenter implements TriangleVisitor {
List<Coordinate> circumCenters;
public TriangleVisitorCircumCenter(List<Coordinate> circumCenters) {
this.circumCenters = circumCenters;
}
@Override
public void visit(QuadEdge[] triEdges) {
Coordinate a = triEdges[0].orig().getCoordinate();
Coordinate b = triEdges[1].orig().getCoordinate();
Coordinate c = triEdges[2].orig().getCoordinate();
circumCenters.add(Triangle.circumcentre(a, b, c));
}
}
private static MultiLineString mergeTrianglesEdges(GeometryCollection polygons) {
GeometryFactory factory = polygons.getFactory();
Set<LineSegment> segments = new HashSet<LineSegment>(polygons.getNumGeometries());
SegmentMerge segmentMerge = new SegmentMerge(segments);
for(int idGeom = 0; idGeom < polygons.getNumGeometries(); idGeom++) {
Geometry polygonGeom = polygons.getGeometryN(idGeom);
if(polygonGeom instanceof Polygon) {
Polygon polygon = (Polygon)polygonGeom;
segmentMerge.reset();
polygon.getExteriorRing().apply(segmentMerge);
}
}
// Convert segments into multilinestring
LineString[] lineStrings = new LineString[segments.size()];
int idLine = 0;
for(LineSegment lineSegment : segments) {
lineStrings[idLine++] = factory.createLineString(new Coordinate[] {lineSegment.p0, lineSegment.p1});
}
segments.clear();
return factory.createMultiLineString(lineStrings);
}
private static class SegmentMerge implements CoordinateFilter {
private Set<LineSegment> segments;
private Coordinate firstPt = null;
public SegmentMerge(Set<LineSegment> segments) {
this.segments = segments;
}
@Override
public void filter(Coordinate coord) {
if(firstPt != null) {
LineSegment segment = new LineSegment(firstPt, coord);
segment.normalize();
segments.add(segment);
}
firstPt = coord;
}
public void reset() {
firstPt = null;
}
}
}