/**
* 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.split;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
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 com.vividsolutions.jts.operation.distance.GeometryLocation;
import com.vividsolutions.jts.operation.polygonize.Polygonizer;
import com.vividsolutions.jts.operation.union.UnaryUnionOp;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.h2gis.api.DeterministicScalarFunction;
import org.h2gis.functions.spatial.convert.ST_ToMultiSegments;
import org.h2gis.functions.spatial.edit.EditUtilities;
import org.h2gis.utilities.jts_utils.CoordinateUtils;
/**
* This function split a line by a line a line by a point a polygon by a line
*
* @author Erwan Bocher
*/
public class ST_Split extends DeterministicScalarFunction {
private static final GeometryFactory FACTORY = new GeometryFactory();
public static final double PRECISION = 10E-6;
public ST_Split() {
addProperty(PROP_REMARKS, "Returns a collection of geometries resulting by splitting a geometry.\n"
+ "Supported operations are : "
+ "- split a polygon or a multipolygon by a linestring,\n"
+ "- split a linestring or a multilinestring by a linestring,\n"
+ "- split a linestring or a multilinestring by a point. At this stage a double tolerance\n"
+ "can be used to snap the point.");
}
@Override
public String getJavaStaticMethod() {
return "split";
}
/**
* Split a geometry a according a geometry b. Supported operations are :
* split a line by a line a line by a point a polygon by a line.
*
* A default tolerance of 10E-6 is used to snap the cutter point.
*
* @param geomA
* @param geomB
* @return
* @throws SQLException
*/
public static Geometry split(Geometry geomA, Geometry geomB) throws SQLException {
if(geomA == null||geomB == null){
return null;
}
if (geomA instanceof Polygon) {
return splitPolygonWithLine((Polygon) geomA, (LineString) geomB);
}
else if(geomA instanceof MultiPolygon){
return splitMultiPolygonWithLine((MultiPolygon)geomA, (LineString) geomB);
}
else if (geomA instanceof LineString) {
if (geomB instanceof LineString) {
return splitLineStringWithLine((LineString) geomA, (LineString) geomB);
} else if (geomB instanceof Point) {
return splitLineWithPoint((LineString) geomA, (Point) geomB, PRECISION);
}
} else if (geomA instanceof MultiLineString) {
if (geomB instanceof LineString) {
return splitMultiLineStringWithLine((MultiLineString) geomA, (LineString) geomB);
} else if (geomB instanceof Point) {
return splitMultiLineStringWithPoint((MultiLineString) geomA, (Point) geomB, PRECISION);
}
}
throw new SQLException("Split a " + geomA.getGeometryType() + " by a " + geomB.getGeometryType() + " is not supported.");
}
/**
* Split a geometry a according a geometry b using a snapping tolerance.
*
* This function support only the operations :
*
* - split a line or a multiline with a point.
*
* @param geomA the geometry to be splited
* @param geomB the geometry used to split
* @param tolerance a distance tolerance to snap the split geometry
* @return
* @throws java.sql.SQLException
*/
public static Geometry split(Geometry geomA, Geometry geomB, double tolerance) throws SQLException {
if (geomA instanceof Polygon) {
throw new SQLException("Split a Polygon by a line is not supported using a tolerance. \n"
+ "Please used ST_Split(geom1, geom2)");
} else if (geomA instanceof LineString) {
if (geomB instanceof LineString) {
throw new SQLException("Split a line by a line is not supported using a tolerance. \n"
+ "Please used ST_Split(geom1, geom2)");
} else if (geomB instanceof Point) {
return splitLineWithPoint((LineString) geomA, (Point) geomB, tolerance);
}
} else if (geomA instanceof MultiLineString) {
if (geomB instanceof LineString) {
throw new SQLException("Split a multiline by a line is not supported using a tolerance. \n"
+ "Please used ST_Split(geom1, geom2)");
} else if (geomB instanceof Point) {
return splitMultiLineStringWithPoint((MultiLineString) geomA, (Point) geomB, tolerance);
}
}
throw new SQLException("Split a " + geomA.getGeometryType() + " by a " + geomB.getGeometryType() + " is not supported.");
}
/**
* Split a linestring with a point The point must be on the linestring
*
* @param line
* @param pointToSplit
* @return
*/
private static MultiLineString splitLineWithPoint(LineString line, Point pointToSplit, double tolerance) {
return FACTORY.createMultiLineString(splitLineStringWithPoint(line, pointToSplit, tolerance));
}
/**
* Splits a LineString using a Point, with a distance tolerance.
*
* @param line
* @param pointToSplit
* @param tolerance
* @return
*/
private static LineString[] splitLineStringWithPoint(LineString line, Point pointToSplit, double tolerance) {
Coordinate[] coords = line.getCoordinates();
Coordinate firstCoord = coords[0];
Coordinate lastCoord = coords[coords.length - 1];
Coordinate coordToSplit = pointToSplit.getCoordinate();
if ((coordToSplit.distance(firstCoord) <= PRECISION) || (coordToSplit.distance(lastCoord) <= PRECISION)) {
return new LineString[]{line};
} else {
ArrayList<Coordinate> firstLine = new ArrayList<Coordinate>();
firstLine.add(coords[0]);
ArrayList<Coordinate> secondLine = new ArrayList<Coordinate>();
GeometryLocation geometryLocation = EditUtilities.getVertexToSnap(line, pointToSplit, tolerance);
if (geometryLocation != null) {
int segmentIndex = geometryLocation.getSegmentIndex();
Coordinate coord = geometryLocation.getCoordinate();
int index = -1;
for (int i = 1; i < coords.length; i++) {
index = i - 1;
if (index < segmentIndex) {
firstLine.add(coords[i]);
} else if (index == segmentIndex) {
coord.z = CoordinateUtils.interpolate(coords[i - 1], coords[i], coord);
firstLine.add(coord);
secondLine.add(coord);
if (!coord.equals2D(coords[i])) {
secondLine.add(coords[i]);
}
} else {
secondLine.add(coords[i]);
}
}
LineString lineString1 = FACTORY.createLineString(firstLine.toArray(new Coordinate[firstLine.size()]));
LineString lineString2 = FACTORY.createLineString(secondLine.toArray(new Coordinate[secondLine.size()]));
return new LineString[]{lineString1, lineString2};
}
}
return null;
}
/**
* Splits a MultilineString using a point.
*
* @param multiLineString
* @param pointToSplit
* @param tolerance
* @return
*/
private static MultiLineString splitMultiLineStringWithPoint(MultiLineString multiLineString, Point pointToSplit, double tolerance) {
ArrayList<LineString> linestrings = new ArrayList<LineString>();
boolean notChanged = true;
int nb = multiLineString.getNumGeometries();
for (int i = 0; i < nb; i++) {
LineString subGeom = (LineString) multiLineString.getGeometryN(i);
LineString[] result = splitLineStringWithPoint(subGeom, pointToSplit, tolerance);
if (result != null) {
Collections.addAll(linestrings, result);
notChanged = false;
} else {
linestrings.add(subGeom);
}
}
if (!notChanged) {
return FACTORY.createMultiLineString(linestrings.toArray(new LineString[linestrings.size()]));
}
return null;
}
/**
* Splits a Polygon with a LineString.
*
* @param polygon
* @param lineString
* @return
*/
private static Collection<Polygon> splitPolygonizer(Polygon polygon, LineString lineString) throws SQLException {
LinkedList<LineString> result = new LinkedList<LineString>();
ST_ToMultiSegments.createSegments(polygon.getExteriorRing(), result);
result.add(lineString);
int holes = polygon.getNumInteriorRing();
for (int i = 0; i < holes; i++) {
ST_ToMultiSegments.createSegments(polygon.getInteriorRingN(i), result);
}
// Perform union of all extracted LineStrings (the edge-noding process)
UnaryUnionOp uOp = new UnaryUnionOp(result);
Geometry union = uOp.union();
// Create polygons from unioned LineStrings
Polygonizer polygonizer = new Polygonizer();
polygonizer.add(union);
Collection<Polygon> polygons = polygonizer.getPolygons();
if (polygons.size() > 1) {
return polygons;
}
return null;
}
/**
* Splits a Polygon using a LineString.
*
* @param polygon
* @param lineString
* @return
*/
private static Geometry splitPolygonWithLine(Polygon polygon, LineString lineString) throws SQLException {
Collection<Polygon> pols = polygonWithLineSplitter(polygon, lineString);
if (pols != null) {
return FACTORY.buildGeometry(polygonWithLineSplitter(polygon, lineString));
}
return null;
}
/**
* Splits a Polygon using a LineString.
*
* @param polygon
* @param lineString
* @return
*/
private static Collection<Polygon> polygonWithLineSplitter(Polygon polygon, LineString lineString) throws SQLException {
Collection<Polygon> polygons = splitPolygonizer(polygon, lineString);
if (polygons != null && polygons.size() > 1) {
List<Polygon> pols = new ArrayList<Polygon>();
for (Polygon pol : polygons) {
if (polygon.contains(pol.getInteriorPoint())) {
pols.add(pol);
}
}
return pols;
}
return null;
}
/**
* Splits a MultiPolygon using a LineString.
*
* @param multiPolygon
* @param lineString
* @return
*/
private static Geometry splitMultiPolygonWithLine(MultiPolygon multiPolygon, LineString lineString) throws SQLException {
ArrayList<Polygon> allPolygons = new ArrayList<Polygon>();
for (int i = 0; i < multiPolygon.getNumGeometries(); i++) {
Collection<Polygon> polygons = splitPolygonizer((Polygon) multiPolygon.getGeometryN(i), lineString);
if (polygons != null) {
allPolygons.addAll(polygons);
}
}
if (!allPolygons.isEmpty()) {
return FACTORY.buildGeometry(allPolygons);
}
return null;
}
/**
* Splits the specified lineString with another lineString.
*
* @param lineString
* @param lineString
*
*/
private static Geometry splitLineStringWithLine(LineString input, LineString cut) {
return input.difference(cut);
}
/**
* Splits the specified MultiLineString with another lineString.
*
* @param MultiLineString
* @param lineString
*
*/
private static Geometry splitMultiLineStringWithLine(MultiLineString input, LineString cut) {
Geometry lines = input.difference(cut);
//Only to preserve SQL constrains
if (lines instanceof LineString) {
return FACTORY.createMultiLineString(new LineString[]{(LineString) lines.getGeometryN(0)});
}
return lines;
}
}