Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SEDONA-607] Return geometry with ST functions with exceptions #1525

Merged
merged 12 commits into from
Jul 23, 2024
Merged
105 changes: 69 additions & 36 deletions common/src/main/java/org/apache/sedona/common/Functions.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.*;
import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.sedona.common.exception.IllegalGeometryException;
import org.apache.sedona.common.geometryObjects.Circle;
import org.apache.sedona.common.sphere.Spheroid;
import org.apache.sedona.common.subDivide.GeometrySubDivider;
Expand Down Expand Up @@ -100,7 +101,7 @@ public static Geometry buffer(Geometry geometry, double radius, boolean useSpher

public static Geometry buffer(
Geometry geometry, double radius, boolean useSpheroid, String params)
throws IllegalArgumentException {
throws IllegalGeometryException {
BufferParameters bufferParameters = new BufferParameters();

// Processing parameters
Expand All @@ -122,7 +123,8 @@ public static Geometry buffer(
try {
return bufferSpheroid(geometry, radius, bufferParameters);
} catch (RuntimeException e) {
throw new RuntimeException("Error processing spheroidal buffer", e);
throw new IllegalGeometryException(
"Error processing spheroidal buffer" + e.getMessage(), new Geometry[] {geometry});
}
} else {
// Existing planar buffer logic with params handling
Expand Down Expand Up @@ -252,8 +254,9 @@ public static int bestSRID(Geometry geometry) throws IllegalArgumentException {
Double xwidth = Spheroid.angularWidth(envelope);
Double ywidth = Spheroid.angularHeight(envelope);
if (xwidth.isNaN() | ywidth.isNaN()) {
throw new IllegalArgumentException(
"Only lon/lat coordinate systems are supported by ST_BestSRID");
throw new IllegalGeometryException(
"Only lon/lat coordinate systems are supported by ST_BestSRID",
new Geometry[] {geometry});
}

// Prioritize polar regions for Lambert Azimuthal Equal Area projection
Expand Down Expand Up @@ -597,8 +600,9 @@ public static String asHexEWKB(Geometry geom, String endian) {
} else if (endian.equalsIgnoreCase("XDR")) {
return GeomUtils.getHexEWKB(geom, ByteOrderValues.BIG_ENDIAN);
}
throw new IllegalArgumentException(
"You must select either NDR (little-endian) or XDR (big-endian) as the endian format.");
throw new IllegalGeometryException(
"You must select either NDR (little-endian) or XDR (big-endian) as the endian format.",
new Geometry[] {geom});
}

public static String asHexEWKB(Geometry geom) {
Expand Down Expand Up @@ -821,7 +825,8 @@ public static Geometry closestPoint(Geometry left, Geometry right) {
Coordinate[] closestPoints = distanceOp.nearestPoints();
return left.getFactory().createPoint(closestPoints[0]);
} catch (Exception e) {
throw new IllegalArgumentException("ST_ClosestPoint doesn't support empty geometry object.");
throw new IllegalGeometryException(
"ST_ClosestPoint doesn't support empty geometry object.", new Geometry[] {left, right});
}
}

Expand Down Expand Up @@ -1106,7 +1111,12 @@ public static double lineLocatePoint(Geometry geom, Geometry point) {
}

public static Geometry locateAlong(Geometry linear, double measure, double offset) {
return GeometryLocateAlongProcessor.processGeometry(linear, measure, offset);
try {
return GeometryLocateAlongProcessor.processGeometry(linear, measure, offset);
} catch (Exception e) {
throw new IllegalGeometryException(
"ST_LocateAlong failed to evaluate. " + e.getMessage(), new Geometry[] {linear});
}
}

public static Geometry locateAlong(Geometry linear, double measure) {
Expand Down Expand Up @@ -1536,8 +1546,9 @@ public static Geometry makeLine(Geometry[] geoms) {
coordinates.add(coord);
}
} else {
throw new IllegalArgumentException(
"ST_MakeLine only supports Point, MultiPoint and LineString geometries");
throw new IllegalGeometryException(
"ST_MakeLine only supports Point, MultiPoint and LineString geometries",
new Geometry[] {geom});
}
}

Expand Down Expand Up @@ -1622,7 +1633,8 @@ public static Geometry collectionExtract(Geometry geometry, Integer geomType) {
emptyResult = factory.createMultiPolygon();
break;
default:
throw new IllegalArgumentException("Invalid geometry type");
throw new IllegalGeometryException(
"Invalid geometry type: " + geomType, new Geometry[] {geometry});
}
List<Geometry> geometries = GeomUtils.extractGeometryCollection(geometry, geomClass);
if (geometries.isEmpty()) {
Expand Down Expand Up @@ -1768,10 +1780,9 @@ private static Coordinate[] extractCoordinates(Geometry geometry) {
public static int numPoints(Geometry geometry) throws Exception {
String geometryType = geometry.getGeometryType();
if (!(Geometry.TYPENAME_LINESTRING.equalsIgnoreCase(geometryType))) {
throw new IllegalArgumentException(
"Unsupported geometry type: "
+ geometryType
+ ", only LineString geometry is supported.");
throw new IllegalGeometryException(
"Unsupported geometry type: " + geometryType + ", only LineString geometry is supported.",
new Geometry[] {geometry});
}
return geometry.getNumPoints();
}
Expand Down Expand Up @@ -1833,10 +1844,11 @@ public static Geometry generatePoints(Geometry geom, int numPoints, Random rando
public static Integer nRings(Geometry geometry) throws Exception {
String geometryType = geometry.getGeometryType();
if (!(geometry instanceof Polygon || geometry instanceof MultiPolygon)) {
throw new IllegalArgumentException(
throw new IllegalGeometryException(
"Unsupported geometry type: "
+ geometryType
+ ", only Polygon or MultiPolygon geometries are supported.");
+ ", only Polygon or MultiPolygon geometries are supported.",
new Geometry[] {geometry});
}
int numRings = 0;
if (geometry instanceof Polygon) {
Expand Down Expand Up @@ -1902,7 +1914,8 @@ public static Geometry geometricMedian(
String geometryType = geometry.getGeometryType();
if (!(Geometry.TYPENAME_POINT.equals(geometryType)
|| Geometry.TYPENAME_MULTIPOINT.equals(geometryType))) {
throw new Exception("Unsupported geometry type: " + geometryType);
throw new IllegalGeometryException(
"Unsupported geometry type: " + geometryType, new Geometry[] {geometry});
}
Coordinate[] coordinates = extractCoordinates(geometry);
if (coordinates.length == 0) return new Point(null, factory);
Expand All @@ -1913,9 +1926,10 @@ public static Geometry geometricMedian(
for (int i = 0; i < maxIter && delta > tolerance; i++)
delta = iteratePoints(median, coordinates, distances);
if (failIfNotConverged && delta > tolerance)
throw new Exception(
throw new IllegalGeometryException(
String.format(
"Median failed to converge within %.1E after %d iterations.", tolerance, maxIter));
"Median failed to converge within %.1E after %d iterations.", tolerance, maxIter),
new Geometry[] {geometry});
boolean is3d = !Double.isNaN(geometry.getCoordinate().z);
if (!is3d) median.z = Double.NaN;
return factory.createPoint(median);
Expand Down Expand Up @@ -1979,17 +1993,21 @@ public static Geometry boundingDiagonal(Geometry geometry) {
}

public static double angle(Geometry point1, Geometry point2, Geometry point3, Geometry point4)
throws IllegalArgumentException {
throws IllegalGeometryException {
if (point3 == null && point4 == null) return Functions.angle(point1, point2);
else if (point4 == null) return Functions.angle(point1, point2, point3);
if (GeomUtils.isAnyGeomEmpty(point1, point2, point3, point4))
throw new IllegalArgumentException("ST_Angle cannot support empty geometries.");
throw new IllegalGeometryException(
"ST_Angle cannot support empty geometries, empty index = "
+ Arrays.toString(GeomUtils.emptyGeometries(point1, point2, point3, point4)),
new Geometry[] {point1, point2, point3, point4});
if (!(point1 instanceof Point
&& point2 instanceof Point
&& point3 instanceof Point
&& point4 instanceof Point))
throw new IllegalArgumentException(
"ST_Angle supports either only POINT or only LINESTRING geometries.");
throw new IllegalGeometryException(
"ST_Angle supports either only POINT or only LINESTRING geometries.",
new Geometry[] {point1, point2, point3, point4});
return GeomUtils.calcAngle(
point1.getCoordinate(),
point2.getCoordinate(),
Expand All @@ -1998,25 +2016,31 @@ public static double angle(Geometry point1, Geometry point2, Geometry point3, Ge
}

public static double angle(Geometry point1, Geometry point2, Geometry point3)
throws IllegalArgumentException {
throws IllegalGeometryException {
if (GeomUtils.isAnyGeomEmpty(point1, point2, point3))
throw new IllegalArgumentException("ST_Angle cannot support empty geometries.");
throw new IllegalGeometryException(
"ST_Angle cannot support empty geometries, empty index = "
+ Arrays.toString(GeomUtils.emptyGeometries(point1, point2, point3)),
new Geometry[] {point1, point2, point3});
if (!(point1 instanceof Point && point2 instanceof Point && point3 instanceof Point))
throw new IllegalArgumentException(
"ST_Angle supports either only POINT or only LINESTRING geometries.");
throw new IllegalGeometryException(
"ST_Angle supports either only POINT or only LINESTRING geometries.",
new Geometry[] {point1, point2, point3});
return GeomUtils.calcAngle(
point2.getCoordinate(),
point1.getCoordinate(),
point2.getCoordinate(),
point3.getCoordinate());
}

public static double angle(Geometry line1, Geometry line2) throws IllegalArgumentException {
public static double angle(Geometry line1, Geometry line2) throws IllegalGeometryException {
if (GeomUtils.isAnyGeomEmpty(line1, line2))
throw new IllegalArgumentException("ST_Angle cannot support empty geometries.");
throw new IllegalGeometryException(
"ST_Angle cannot support empty geometries.", new Geometry[] {line1, line2});
if (!(line1 instanceof LineString && line2 instanceof LineString))
throw new IllegalArgumentException(
"ST_Angle supports either only POINT or only LINESTRING geometries.");
throw new IllegalGeometryException(
"ST_Angle supports either only POINT or only LINESTRING geometries.",
new Geometry[] {line1, line2});
Coordinate[] startEndLine1 = GeomUtils.getStartEndCoordinates(line1);
Coordinate[] startEndLine2 = GeomUtils.getStartEndCoordinates(line2);
assert startEndLine1 != null;
Expand All @@ -2030,11 +2054,19 @@ public static double degrees(double angleInRadian) {
}

public static Double hausdorffDistance(Geometry g1, Geometry g2, double densityFrac) {
return GeomUtils.getHausdorffDistance(g1, g2, densityFrac);
try {
return GeomUtils.getHausdorffDistance(g1, g2, densityFrac);
} catch (IllegalArgumentException e) {
throw new IllegalGeometryException(e.getMessage(), new Geometry[] {g1, g2});
}
}

public static Double hausdorffDistance(Geometry g1, Geometry g2) {
return GeomUtils.getHausdorffDistance(g1, g2, -1);
try {
return GeomUtils.getHausdorffDistance(g1, g2, -1);
} catch (IllegalArgumentException e) {
throw new IllegalGeometryException(e.getMessage(), new Geometry[] {g1, g2});
}
}

private static IsValidOp getIsValidOpObject(Geometry geom, int flag) {
Expand Down Expand Up @@ -2168,14 +2200,15 @@ public static Geometry rotate(Geometry geometry, double angle, double originX, d
* @param angle The angle in radians to rotate the geometry.
* @param pointOrigin The origin point around which to rotate.
* @return The rotated geometry.
* @throws IllegalArgumentException if the pointOrigin is not a Point geometry.
* @throws IllegalGeometryException if the pointOrigin is not a Point geometry.
*/
public static Geometry rotate(Geometry geometry, double angle, Geometry pointOrigin) {
if (geometry == null || geometry.isEmpty()) {
return geometry;
}
if (pointOrigin == null || pointOrigin.isEmpty() || !(pointOrigin instanceof Point)) {
throw new IllegalArgumentException("The origin must be a non-empty Point geometry.");
throw new IllegalGeometryException(
"The origin must be a non-empty Point geometry.", new Geometry[] {geometry, pointOrigin});
}
Point origin = (Point) pointOrigin;
double originX = origin.getX();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.apache.sedona.common;

import java.util.Set;
import org.apache.sedona.common.exception.IllegalGeometryException;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.geotools.referencing.ReferencingFactoryFinder;
Expand All @@ -39,19 +40,31 @@ public class FunctionsGeoTools {

public static Geometry transform(Geometry geometry, String targetCRS)
throws FactoryException, TransformException {
return transform(geometry, null, targetCRS, true);
try {
return transform(geometry, null, targetCRS, true);
} catch (FactoryException e) {
throw new IllegalGeometryException(e.getMessage(), new Geometry[] {geometry});
}
}

public static Geometry transform(Geometry geometry, String sourceCRS, String targetCRS)
throws FactoryException, TransformException {
return transform(geometry, sourceCRS, targetCRS, true);
try {
return transform(geometry, sourceCRS, targetCRS, true);
} catch (FactoryException e) {
throw new IllegalGeometryException(e.getMessage(), new Geometry[] {geometry});
}
}

public static Geometry transform(
Geometry geometry, String sourceCRScode, String targetCRScode, boolean lenient)
throws FactoryException, TransformException {
CoordinateReferenceSystem targetCRS = parseCRSString(targetCRScode);
return transformToGivenTarget(geometry, sourceCRScode, targetCRS, lenient);
try {
return transformToGivenTarget(geometry, sourceCRScode, targetCRS, lenient);
} catch (FactoryException e) {
throw new IllegalGeometryException(e.getMessage(), new Geometry[] {geometry});
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sedona.common.exception;

import java.util.Arrays;
import org.locationtech.jts.geom.Geometry;

/** Exception for illegal geometries. */
public class IllegalGeometryException extends RuntimeException {
// The geometries that caused the exception
private Geometry[] geometries;

public IllegalGeometryException(String message, Geometry[] geometries) {
super(message + " [GEOM] " + Arrays.toString(geometries));
this.geometries = geometries;
}

public IllegalGeometryException(String message, Geometry[] geometries, Throwable cause) {
super(message + " " + Arrays.toString(geometries), cause);
this.geometries = geometries;
}

public Geometry[] getGeometries() {
return geometries;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.nio.ByteOrder;
import java.util.*;
import org.apache.sedona.common.Functions;
import org.apache.sedona.common.exception.IllegalGeometryException;
import org.locationtech.jts.algorithm.Angle;
import org.locationtech.jts.algorithm.distance.DiscreteFrechetDistance;
import org.locationtech.jts.algorithm.distance.DiscreteHausdorffDistance;
Expand Down Expand Up @@ -479,6 +480,15 @@ public static boolean isAnyGeomEmpty(Geometry... geometries) {
return false;
}

public static int[] emptyGeometries(Geometry... geometries) {
List<Integer> emptyGeometries = new ArrayList<>();
int i = 0;
for (Geometry geometry : geometries) {
if (geometry != null) if (geometry.isEmpty()) emptyGeometries.add(i);
}
return emptyGeometries.stream().mapToInt(Integer::intValue).toArray();
}

public static Coordinate[] getStartEndCoordinates(Geometry line) {
if (line.getNumPoints() < 2) return null;
Coordinate[] coordinates = line.getCoordinates();
Expand Down Expand Up @@ -554,7 +564,8 @@ public static Double getHausdorffDistance(Geometry g1, Geometry g2, double densi

public static Geometry addMeasure(Geometry geom, double measure_start, double measure_end) {
if (!(geom instanceof LineString) && !(geom instanceof MultiLineString)) {
throw new IllegalArgumentException("Geometry must be a LineString or MultiLineString.");
throw new IllegalGeometryException(
"Geometry must be a LineString or MultiLineString.", new Geometry[] {geom});
}

if (geom instanceof LineString) {
Expand Down
Loading
Loading