Skip to content

Commit

Permalink
Fix ST_AddPoint to be aligned with POSTGIS
Browse files Browse the repository at this point in the history
Add ST_InsertPoint method
Fix ST_IsClosed
  • Loading branch information
ebocher committed Oct 13, 2020
1 parent c756124 commit c70e5d4
Show file tree
Hide file tree
Showing 5 changed files with 475 additions and 185 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,8 @@ public static Function[] getBuiltInsFunctions() {
new ST_GeneratePointsInGrid(),
new AscRead(),
new FindGeometryMetadata(),
new UpdateGeometrySRID()
new UpdateGeometrySRID(),
new ST_InsertPoint()
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@
package org.h2gis.functions.spatial.edit;

import org.h2gis.api.DeterministicScalarFunction;
import org.h2gis.utilities.jts_utils.CoordinateUtils;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.operation.distance.DistanceOp;
import org.locationtech.jts.operation.distance.GeometryLocation;

import java.sql.SQLException;
import java.util.ArrayList;
Expand All @@ -39,7 +36,7 @@ public class ST_AddPoint extends DeterministicScalarFunction {

public ST_AddPoint() {
addProperty(PROP_REMARKS, "Adds a point to a geometry. \n"
+ "A tolerance could be set to snap the point to the geometry.");
+ "An index to set the position of the point (0-based index).");
}

@Override
Expand All @@ -57,220 +54,148 @@ public String getJavaStaticMethod() {
* @throws SQLException
*/
public static Geometry addPoint(Geometry geometry, Point point) throws SQLException {
return addPoint(geometry, point, PRECISION);
if(geometry == null || point == null){
return null;
}
if(geometry.getSRID()!=point.getSRID()){
throw new SQLException("Operation on mixed SRID geometries not supported");
}
int position = geometry.getNumPoints()+1;
GeometryFactory factory = geometry.getFactory();
if (geometry instanceof LineString) {
return insertVertexInLineString((LineString) geometry, point, position, factory);
}
throw new SQLException("First argument must be a LINESTRING");
}


/**
* Returns a new geometry based on an existing one, with a specific point as
* a new vertex.
*
* @param geometry
* @param point
* @param tolerance
* @return Null if the vertex cannot be inserted
* @param position
* @return same geometry if the vertex cannot be inserted
* @throws SQLException If the vertex can be inserted but it makes the
* geometry to be in an invalid shape
*/
public static Geometry addPoint(Geometry geometry, Point point, double tolerance) throws SQLException {
public static Geometry addPoint(Geometry geometry, Point point, int position) throws SQLException {
if(geometry == null || point == null){
return null;
}
if(geometry.getSRID()!=point.getSRID()){
throw new SQLException("Operation on mixed SRID geometries not supported");
}

GeometryFactory factory = geometry.getFactory();

if (geometry instanceof MultiPoint) {
return insertVertexInMultipoint(geometry, point, factory);
} else if (geometry instanceof LineString) {
return insertVertexInLineString((LineString) geometry, point, tolerance, factory);
} else if (geometry instanceof MultiLineString) {
LineString[] linestrings = new LineString[geometry.getNumGeometries()];
boolean any = false;
for (int i = 0; i < geometry.getNumGeometries(); i++) {
LineString line = (LineString) geometry.getGeometryN(i);

LineString inserted = insertVertexInLineString(line, point, tolerance, factory);
if (inserted != null) {
linestrings[i] = inserted;
any = true;
} else {
linestrings[i] = line;
}
}
if (any) {
return factory.createMultiLineString(linestrings);
} else {
return null;
}
} else if (geometry instanceof Polygon) {
return insertVertexInPolygon((Polygon) geometry, point, tolerance, factory);
} else if (geometry instanceof MultiPolygon) {
Polygon[] polygons = new Polygon[geometry.getNumGeometries()];
boolean any = false;
for (int i = 0; i < geometry.getNumGeometries(); i++) {
Polygon polygon = (Polygon) geometry.getGeometryN(i);
Polygon inserted = insertVertexInPolygon(polygon, point, tolerance, factory);
if (inserted != null) {
any = true;
polygons[i] = inserted;
} else {
polygons[i] = polygon;
}
}
if (any) {
return factory.createMultiPolygon(polygons);
} else {
return null;
}
} else if (geometry instanceof Point) {
return null;
if (position < 0){
throw new SQLException("Point index must start at 0");
}

int numGeom = geometry.getNumPoints();
if(position>numGeom){
throw new SQLException("The point index is out of range (0.." + numGeom
+ "): found " + position);
}
throw new SQLException("Unknown geometry type" + " : " + geometry.getGeometryType());
GeometryFactory factory = geometry.getFactory();
if (geometry instanceof LineString) {
return insertVertexInLineString((LineString) geometry, point, position, factory);
}
throw new SQLException("First argument must be a LINESTRING");
}

/**
* Adds a Point into a MultiPoint geometry.
*
* @param g
* @param vertexPoint
* @param factory
* @param position
* @param factory
* @return
*/
private static Geometry insertVertexInMultipoint(Geometry g, Point vertexPoint, GeometryFactory factory) {
private static Geometry insertVertexInMultipoint(MultiPoint g, Point vertexPoint, int position, GeometryFactory factory) throws SQLException {
ArrayList<Point> geoms = new ArrayList<Point>();
boolean added =false;
for (int i = 0; i < g.getNumGeometries(); i++) {
Point geom = (Point) g.getGeometryN(i);
geoms.add(geom);
Point geom ;
if(i==position){
geoms.add(vertexPoint);
added=true;
}else {
geoms.add((Point) g.getGeometryN(i));
}
}
geoms.add(factory.createPoint(new Coordinate(vertexPoint.getX(), vertexPoint.getY())));
return factory.createMultiPoint(GeometryFactory.toPointArray(geoms));
if(!added) {
geoms.add(vertexPoint);
}
return factory.createMultiPoint(geoms.toArray(new Point[0]));
}

/**
* Inserts a vertex into a LineString with a given tolerance.
*
* @param lineString
* @param vertexPoint
* @param tolerance
* @param factory
* @return
* @throws SQLException
*/
private static LineString insertVertexInLineString(LineString lineString, Point vertexPoint,
double tolerance, GeometryFactory factory) throws SQLException {
GeometryLocation geomLocation = EditUtilities.getVertexToSnap(lineString, vertexPoint, tolerance);
if (geomLocation != null) {
Coordinate[] coords = lineString.getCoordinates();
int index = geomLocation.getSegmentIndex();
Coordinate coord = geomLocation.getCoordinate();
if (!CoordinateUtils.contains2D(coords, coord)) {
Coordinate[] ret = new Coordinate[coords.length + 1];
System.arraycopy(coords, 0, ret, 0, index + 1);
ret[index + 1] = coord;
System.arraycopy(coords, index + 1, ret, index + 2, coords.length
- (index + 1));
return factory.createLineString(ret);
}
return null;
} else {
return lineString;
}
private static LineString insertVertexInLineString(LineString lineString, Point vertexPoint, GeometryFactory factory) throws SQLException {
CoordinateSequence coordSeq = lineString.getCoordinateSequence();
return factory.createLineString(addCoordinate(coordSeq, vertexPoint.getCoordinate(),coordSeq.size()+1 ));
}

/**
* Adds a vertex into a Polygon with a given tolerance.
* Inserts a vertex into a LineString with a given tolerance.
*
* @param polygon
* @param lineString
* @param vertexPoint
* @param tolerance
* @param factory
* @param factory
* @return
* @throws SQLException
*/
private static Polygon insertVertexInPolygon(Polygon polygon,
Point vertexPoint, double tolerance, GeometryFactory factory) throws SQLException {
Polygon geom =polygon;
LineString linearRing = polygon.getExteriorRing();
int index = -1;
for (int i = 0; i < polygon.getNumInteriorRing(); i++) {
double distCurr = computeDistance(polygon.getInteriorRingN(i),vertexPoint, tolerance);
if (distCurr<tolerance){
index = i;
}
}
if(index==-1){
//The point is a on the exterior ring.
LinearRing inserted = insertVertexInLinearRing(linearRing, vertexPoint, tolerance, factory);
if(inserted!=null){
LinearRing[] holes = new LinearRing[polygon.getNumInteriorRing()];
for (int i = 0; i < holes.length; i++) {
holes[i]= (LinearRing) polygon.getInteriorRingN(i);
}
geom = factory.createPolygon(inserted, holes);
}
}
else{
//We add the vertex on the first hole
LinearRing[] holes = new LinearRing[polygon.getNumInteriorRing()];
for (int i = 0; i < holes.length; i++) {
if (i == index) {
holes[i] = insertVertexInLinearRing(polygon.getInteriorRingN(i), vertexPoint, tolerance, factory);
} else {
holes[i] = (LinearRing) polygon.getInteriorRingN(i);
}
}
geom = factory.createPolygon((LinearRing) linearRing, holes);
}
if(geom!=null){
if (!geom.isValid()) {
throw new SQLException("Geometry not valid");
}
}
return geom;
private static LineString insertVertexInLineString(LineString lineString, Point vertexPoint, int position, GeometryFactory factory) throws SQLException {
return factory.createLineString(addCoordinate(lineString.getCoordinateSequence(), vertexPoint.getCoordinate(),position ));
}

/**
* Return minimum distance between a geometry and a point.
* Expand the coordinates array and add a coordinate at the given position
*
* @param geometry
* @param vertexPoint
* @param tolerance
* @return
*/
private static double computeDistance(Geometry geometry, Point vertexPoint, double tolerance) {
DistanceOp distanceOp = new DistanceOp(geometry, vertexPoint, tolerance);
return distanceOp.distance();
}

/**
* Adds a vertex into a LinearRing with a given tolerance.
*
* @param lineString
* @param vertexPoint
* @param tolerance
* @param factory
* @param coorseq
* @param position
* @param point
* @return
*/
private static LinearRing insertVertexInLinearRing(LineString lineString,
Point vertexPoint, double tolerance, GeometryFactory factory) {
GeometryLocation geomLocation = EditUtilities.getVertexToSnap(lineString, vertexPoint, tolerance);
if (geomLocation != null) {
Coordinate[] coords = lineString.getCoordinates();
int index = geomLocation.getSegmentIndex();
Coordinate coord = geomLocation.getCoordinate();
if (!CoordinateUtils.contains2D(coords, coord)) {
Coordinate[] ret = new Coordinate[coords.length + 1];
System.arraycopy(coords, 0, ret, 0, index + 1);
ret[index + 1] = coord;
System.arraycopy(coords, index + 1, ret, index + 2, coords.length
- (index + 1));
return factory.createLinearRing(ret);
public static Coordinate[] addCoordinate(CoordinateSequence coorseq, Coordinate point, int position) {
if(position<=coorseq.size() ){
Coordinate coord = new CoordinateXY(point);
if(coorseq.hasZ()&& coorseq.hasM()){
coord = new CoordinateXYZM(point);
}
return null;
} else {
return null;
else if(coorseq.hasZ()){
coord =new Coordinate(point.getX(), point.getY(), Double.isNaN(point.getZ())?0:point.getZ());
}
else if(coorseq.hasM()){
coord = new CoordinateXYM(point.getX(), point.getY(), Double.isNaN(point.getM())?0:point.getM());
}
Coordinate[] coordinates = coorseq.toCoordinateArray();
coordinates[position]=coord;
return coordinates;
}else {
Coordinate[] coordinates = coorseq.toCoordinateArray();
Coordinate[] destArray = new Coordinate[coordinates.length + 1];
Coordinate coord = new CoordinateXY(point);
if(coorseq.hasZ()&& coorseq.hasM()){
coord = new CoordinateXYZM(point);
}
else if(coorseq.hasZ()){
coord =new Coordinate(point.getX(), point.getY(), Double.isNaN(point.getZ())?0:point.getZ());
}
else if(coorseq.hasM()){
coord = new CoordinateXYM(point);
}
CoordinateArrays.copyDeep(coordinates, 0, destArray, 0, coordinates.length);
destArray[position-1] = coord;
return destArray;
}
}
}
Loading

0 comments on commit c70e5d4

Please sign in to comment.