Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.h2gis.functions.spatial.mesh.DelaunayData Maven / Gradle / Ivy
/**
* H2GIS is a library that brings spatial support to the H2 Database Engine
* . H2GIS is developed by CNRS
* .
*
* 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 .
*
*
* For more information, please consult:
* or contact directly: info_at_h2gis.org
*/
package org.h2gis.functions.spatial.mesh;
import java.math.BigDecimal;
import java.math.MathContext;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateFilter;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineSegment;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import org.h2gis.functions.spatial.aggregate.ST_Accum;
import org.h2gis.functions.spatial.convert.ST_ToMultiLine;
import org.h2gis.utilities.jts_utils.CoordinateSequenceDimensionFilter;
import org.poly2tri.Poly2Tri;
import org.poly2tri.geometry.polygon.PolygonPoint;
import org.poly2tri.triangulation.Triangulatable;
import org.poly2tri.triangulation.TriangulationAlgorithm;
import org.poly2tri.triangulation.TriangulationPoint;
import org.poly2tri.triangulation.delaunay.DelaunayTriangle;
import org.poly2tri.triangulation.point.TPoint;
import org.poly2tri.triangulation.sets.ConstrainedPointSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is used to collect all data used to compute a mesh based on a
* Delaunay triangulation
*
* @author Erwan Bocher
* @author Nicolas Fortin
*/
public class DelaunayData {
private static final Logger LOGGER = LoggerFactory.getLogger(DelaunayData.class);
public enum MODE {DELAUNAY, CONSTRAINED, TESSELLATION}
private boolean isInput2D;
private GeometryFactory gf;
private Triangulatable convertedInput = null;
// Precision
private MathContext mathContext = MathContext.DECIMAL64;
/**
* Create a mesh data structure to collect points and edges that will be
* used by the Delaunay Triangulation
*/
public DelaunayData() {
}
private double r(double v) {
return new BigDecimal(v).round(mathContext).doubleValue();
}
private org.poly2tri.geometry.polygon.Polygon makePolygon(LineString lineString) {
PolygonPoint[] points = new PolygonPoint[lineString.getNumPoints() - 1];
for(int idPoint=0; idPoint < points.length; idPoint++) {
Coordinate point = lineString.getCoordinateN(idPoint);
points[idPoint] = new PolygonPoint(r(point.x), r(point.y), Double.isNaN(point.z) ? 0 : r(point.z));
}
return new org.poly2tri.geometry.polygon.Polygon(points);
}
private org.poly2tri.geometry.polygon.Polygon makePolygon(Polygon polygon) {
org.poly2tri.geometry.polygon.Polygon poly = makePolygon(polygon.getExteriorRing());
// Add holes
for(int idHole = 0; idHole < polygon.getNumInteriorRing(); idHole++) {
poly.addHole(makePolygon(polygon.getInteriorRingN(idHole)));
}
return poly;
}
private static Coordinate toJts(boolean is2d, org.poly2tri.geometry.primitives.Point pt) {
if(is2d) {
return new Coordinate(pt.getX(), pt.getY());
} else {
return new Coordinate(pt.getX(), pt.getY(), pt.getZ());
}
}
private int getMinDimension(GeometryCollection geometries) {
int dimension = Integer.MAX_VALUE;
for (int i = 0; i < geometries.getNumGeometries(); i++) {
dimension = Math.min(dimension, geometries.getGeometryN(i).getDimension());
}
if(dimension == Integer.MAX_VALUE) {
dimension = -1;
}
return dimension;
}
/**
* Put a geometry into the data array. Set true to populate the list of
* points and edges, needed for the ContrainedDelaunayTriangulation. Set
* false to populate only the list of points. Note the z-value is forced to
* O when it's equal to NaN.
*
* @param geom Geometry
* @param mode Delaunay mode
* @throws IllegalArgumentException
*/
public void put(Geometry geom, MODE mode) throws IllegalArgumentException {
gf = geom.getFactory();
convertedInput = null;
// Does not use instanceof here as we must not match for overload of GeometryCollection
int dimension;
if(geom.getClass().getName().equals(GeometryCollection.class.getName())) {
dimension = getMinDimension((GeometryCollection)geom);
} else {
dimension = geom.getDimension();
}
// Workaround for issue 105 "Poly2Tri does not make a valid convexHull for points and linestrings delaunay
// https://code.google.com/p/poly2tri/issues/detail?id=105
if(mode != MODE.TESSELLATION) {
Geometry convexHull = new FullConvexHull(geom).getConvexHull();
if(convexHull instanceof Polygon && convexHull.isValid()) {
// Does not use instanceof here as we must not match for overload of GeometryCollection
if(geom.getClass().getName().equals(GeometryCollection.class.getName())) {
if(dimension > 0) {
// Mixed geometry, try to unify sub-types
try {
geom = ST_ToMultiLine.createMultiLineString(geom).union();
} catch (SQLException ex) {
throw new IllegalArgumentException(ex);
}
if(geom.getClass().getName().equals(GeometryCollection.class.getName())) {
throw new IllegalArgumentException("Delaunay does not support mixed geometry type");
}
}
}
if(dimension > 0) {
geom = ((Polygon) convexHull).getExteriorRing().union(geom);
} else {
ST_Accum accum = new ST_Accum();
try {
accum.add(geom);
accum.add(convexHull);
geom = accum.getResult();
} catch (SQLException ex) {
LOGGER.error(ex.getLocalizedMessage(), ex);
}
}
} else {
return;
}
}
// end workaround
CoordinateSequenceDimensionFilter info = CoordinateSequenceDimensionFilter.apply(geom);
isInput2D = info.is2D();
convertedInput = null;
if(mode == MODE.TESSELLATION) {
if(geom instanceof Polygon) {
convertedInput = makePolygon((Polygon) geom);
} else {
throw new IllegalArgumentException("Only Polygon are accepted for tessellation");
}
} else {
// Constraint delaunay of segments
addGeometry(geom);
}
}
public void triangulate() {
if(convertedInput != null) {
Poly2Tri.triangulate(TriangulationAlgorithm.DTSweep, convertedInput);
}
}
public MultiPolygon getTriangles() {
if(convertedInput != null) {
List delaunayTriangle = convertedInput.getTriangles();
// Convert into multi polygon
Polygon[] polygons = new Polygon[delaunayTriangle.size()];
for (int idTriangle = 0; idTriangle < polygons.length; idTriangle++) {
TriangulationPoint[] pts = delaunayTriangle.get(idTriangle).points;
polygons[idTriangle] = gf.createPolygon(new Coordinate[]{toJts(isInput2D, pts[0]), toJts(isInput2D, pts[1]), toJts(isInput2D, pts[2]), toJts(isInput2D, pts[0])});
}
return gf.createMultiPolygon(polygons);
} else {
return gf.createMultiPolygon(new Polygon[0]);
}
}
/**
* Return the 3D area of all triangles
* @return
*/
public double get3DArea(){
if(convertedInput != null) {
List delaunayTriangle = convertedInput.getTriangles();
double sum = 0;
for (DelaunayTriangle triangle : delaunayTriangle) {
sum += computeTriangleArea3D(triangle);
}
return sum;
} else {
return 0;
}
}
/**
* Computes the 3D area of a triangle.
*
* @param triangle
* @return
*/
private double computeTriangleArea3D(DelaunayTriangle triangle) {
TriangulationPoint[] points = triangle.points;
TriangulationPoint p1 = points[0];
TriangulationPoint p2 = points[1];
TriangulationPoint p3 = points[2];
/**
* Uses the formula 1/2 * | u x v | where u,v are the side vectors of
* the triangle x is the vector cross-product
*/
// side vectors u and v
double ux = p2.getX() - p1.getX();
double uy = p2.getY() - p1.getY();
double uz = p2.getZ() - p1.getZ();
double vx = p3.getX() - p1.getX();
double vy = p3.getY() - p1.getY();
double vz = p3.getZ() - p1.getZ();
if (Double.isNaN(uz) || Double.isNaN(vz)) {
uz=1;
vz=1;
}
// cross-product = u x v
double crossx = uy * vz - uz * vy;
double crossy = uz * vx - ux * vz;
double crossz = ux * vy - uy * vx;
// tri area = 1/2 * | u x v |
double absSq = crossx * crossx + crossy * crossy + crossz * crossz;
return Math.sqrt(absSq) / 2;
}
private void addSegment(Set segmentHashMap, TriangulationPoint a, TriangulationPoint b) {
LineSegment lineSegment = new LineSegment(toJts(isInput2D, a), toJts(isInput2D, b));
lineSegment.normalize();
segmentHashMap.add(lineSegment);
}
/**
* @return Unique triangles edges
*/
public MultiLineString getTrianglesSides() {
List delaunayTriangle = convertedInput.getTriangles();
// Remove duplicates edges thanks to this hash map of normalized line segments
Set segmentHashMap = new HashSet(delaunayTriangle.size());
for(DelaunayTriangle triangle : delaunayTriangle) {
TriangulationPoint[] pts = triangle.points;
addSegment(segmentHashMap, pts[0], pts[1]);
addSegment(segmentHashMap, pts[1], pts[2]);
addSegment(segmentHashMap, pts[2], pts[0]);
}
LineString[] lineStrings = new LineString[segmentHashMap.size()];
int i = 0;
for(LineSegment lineSegment : segmentHashMap) {
lineStrings[i++] = lineSegment.toGeometry(gf);
}
return gf.createMultiLineString(lineStrings);
}
/**
* Add a geometry to the list of points and edges used by the triangulation.
* @param geom Any geometry
* @throws IllegalArgumentException
*/
private void addGeometry(Geometry geom) throws IllegalArgumentException {
if(!geom.isValid()) {
throw new IllegalArgumentException("Provided geometry is not valid !");
}
if(geom instanceof GeometryCollection) {
Map pts = new HashMap(geom.getNumPoints());
List segments = new ArrayList(pts.size());
AtomicInteger pointsCount = new AtomicInteger(0);
PointHandler pointHandler = new PointHandler(this, pts, pointsCount);
LineStringHandler lineStringHandler = new LineStringHandler(this, pts, pointsCount, segments);
for(int geomId = 0; geomId < geom.getNumGeometries(); geomId++) {
addSimpleGeometry(geom.getGeometryN(geomId), pointHandler, lineStringHandler);
}
int[] index = new int[segments.size()];
for(int i = 0; i < index.length; i++) {
index[i] = segments.get(i);
}
// Construct final points array by reversing key,value of hash map
TriangulationPoint[] ptsArray = new TriangulationPoint[pointsCount.get()];
for(Map.Entry entry : pts.entrySet()) {
ptsArray[entry.getValue()] = entry.getKey();
}
pts.clear();
convertedInput = new ConstrainedPointSet(Arrays.asList(ptsArray), index);
} else {
addGeometry(geom.getFactory().createGeometryCollection(new Geometry[]{geom}));
}
}
private void addSimpleGeometry(Geometry geom, PointHandler pointHandler, LineStringHandler lineStringHandler) throws IllegalArgumentException {
if(geom instanceof Point) {
geom.apply(pointHandler);
} else if(geom instanceof LineString) {
lineStringHandler.reset();
geom.apply(lineStringHandler);
} else if(geom instanceof Polygon) {
Polygon polygon = (Polygon) geom;
lineStringHandler.reset();
polygon.getExteriorRing().apply(lineStringHandler);
for(int idHole = 0; idHole < polygon.getNumInteriorRing(); idHole++) {
lineStringHandler.reset();
polygon.getInteriorRingN(idHole).apply(lineStringHandler);
}
}
}
private static class PointHandler implements CoordinateFilter {
private DelaunayData delaunayData;
private Map pts;
private AtomicInteger maxIndex;
public PointHandler(DelaunayData delaunayData, Map pts, AtomicInteger maxIndex) {
this.delaunayData = delaunayData;
this.pts = pts;
this.maxIndex = maxIndex;
}
protected int addPt(Coordinate coordinate) {
TPoint pt = new TPoint(delaunayData.r(coordinate.x), delaunayData.r(coordinate.y),
Double.isNaN(coordinate.z) ? 0 : delaunayData.r(coordinate.z));
Integer index = pts.get(pt);
if(index == null) {
index = maxIndex.getAndAdd(1);
pts.put(pt, index);
}
return index;
}
@Override
public void filter(Coordinate pt) {
addPt(pt);
}
}
private static class LineStringHandler extends PointHandler {
private List segments;
private int firstPtIndex = -1;
public LineStringHandler(DelaunayData delaunayData, Map pts,
AtomicInteger maxIndex, List segments) {
super(delaunayData, pts, maxIndex);
this.segments = segments;
}
/**
* New line string
*/
public void reset() {
firstPtIndex = -1;
}
@Override
public void filter(Coordinate pt) {
if (firstPtIndex == -1) {
firstPtIndex = addPt(pt);
} else {
int secondPt = addPt(pt);
if (secondPt != firstPtIndex) {
segments.add(firstPtIndex);
segments.add(secondPt);
firstPtIndex = secondPt;
}
}
}
}
}