Skip to content

Commit

Permalink
[SEDONA-294 | SEDONA-310] Add ST_Angle and ST_Degrees to sedona (apac…
Browse files Browse the repository at this point in the history
  • Loading branch information
iGN5117 authored and Kontinuation committed Jul 7, 2023
1 parent 24c518f commit da0e275
Show file tree
Hide file tree
Showing 17 changed files with 581 additions and 51 deletions.
30 changes: 28 additions & 2 deletions common/src/main/java/org/apache/sedona/common/Functions.java
Original file line number Diff line number Diff line change
Expand Up @@ -1061,8 +1061,6 @@ public static Geometry boundingDiagonal(Geometry geometry) {
if (geometry.isEmpty()) {
return GEOMETRY_FACTORY.createLineString();
}else {
//Envelope envelope = geometry.getEnvelopeInternal();
// if (envelope.isNull()) return GEOMETRY_FACTORY.createLineString();
Double startX = null, startY = null, startZ = null,
endX = null, endY = null, endZ = null;
boolean is3d = !Double.isNaN(geometry.getCoordinate().z);
Expand Down Expand Up @@ -1091,6 +1089,34 @@ public static Geometry boundingDiagonal(Geometry geometry) {
}
}

public static double angle(Geometry point1, Geometry point2, Geometry point3, Geometry point4) throws IllegalArgumentException {
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.");
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.");
return GeomUtils.calcAngle(point1.getCoordinate(), point2.getCoordinate(), point3.getCoordinate(), point4.getCoordinate());
}

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

public static double angle(Geometry line1, Geometry line2) throws IllegalArgumentException {
if (GeomUtils.isAnyGeomEmpty(line1, line2)) throw new IllegalArgumentException("ST_Angle cannot support empty geometries.");
if (!(line1 instanceof LineString && line2 instanceof LineString)) throw new IllegalArgumentException("ST_Angle supports either only POINT or only LINESTRING geometries.");
Coordinate[] startEndLine1 = GeomUtils.getStartEndCoordinates(line1);
Coordinate[] startEndLine2 = GeomUtils.getStartEndCoordinates(line2);
assert startEndLine1 != null;
assert startEndLine2 != null;
return GeomUtils.calcAngle(startEndLine1[0], startEndLine1[1], startEndLine2[0], startEndLine2[1]);
}

public static double degrees(double angleInRadian) {
return GeomUtils.toDegrees(angleInRadian);
}

public static Double hausdorffDistance(Geometry g1, Geometry g2, double densityFrac) throws Exception {
return GeomUtils.getHausdorffDistance(g1, g2, densityFrac);
}
Expand Down
36 changes: 36 additions & 0 deletions common/src/main/java/org/apache/sedona/common/utils/GeomUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.locationtech.jts.io.WKTWriter;
import org.locationtech.jts.operation.polygonize.Polygonizer;
import org.locationtech.jts.operation.union.UnaryUnionOp;
import org.locationtech.jts.algorithm.Angle;
import org.locationtech.jts.algorithm.distance.DiscreteFrechetDistance;
import org.locationtech.jts.algorithm.distance.DiscreteHausdorffDistance;

Expand Down Expand Up @@ -454,8 +455,43 @@ public static void translateGeom(Geometry geometry, double deltaX, double deltaY
geometry.geometryChanged();
}
}

public static boolean isAnyGeomEmpty(Geometry... geometries) {
for (Geometry geometry: geometries) {
if (geometry != null)
if (geometry.isEmpty())
return true;
}
return false;
}

public static Coordinate[] getStartEndCoordinates(Geometry line) {
if (line.getNumPoints() < 2) return null;
Coordinate[] coordinates = line.getCoordinates();
return new Coordinate[] {coordinates[0], coordinates[coordinates.length - 1]};
}

public static double calcAngle(Coordinate start1, Coordinate end1, Coordinate start2, Coordinate end2) {
double angle1 = normalizeAngle(Angle.angle(start1, end1));
double angle2 = normalizeAngle(Angle.angle(start2, end2));
return normalizeAngle(angle1 - angle2);
}

private static double normalizeAngle(double angle) {
if (angle < 0) {
return 2 * Math.PI - Math.abs(angle);
}
return angle;
}

public static double toDegrees(double angleInRadian) {
return Angle.toDegrees(angleInRadian);
}


public static void affineGeom(Geometry geometry, Double a, Double b, Double c, Double d, Double e, Double f, Double g, Double h, Double i, Double xOff, Double yOff,
Double zOff) {

Coordinate[] coordinates = geometry.getCoordinates();
for (Coordinate currCoordinate : coordinates) {
double x = currCoordinate.getX(), y = currCoordinate.getY(), z = Double.isNaN(currCoordinate.getZ()) ? 0 : currCoordinate.getZ();
Expand Down
107 changes: 107 additions & 0 deletions common/src/test/java/org/apache/sedona/common/FunctionsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1139,6 +1139,113 @@ public void boundingDiagonalSingleVertex() {
}

@Test
public void angleFourPoints() {
Point start1 = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 0));
Point end1 = GEOMETRY_FACTORY.createPoint(new Coordinate(1, 1));
Point start2 = GEOMETRY_FACTORY.createPoint(new Coordinate(1, 0));
Point end2 = GEOMETRY_FACTORY.createPoint(new Coordinate(6, 2));

double expected = 0.4048917862850834;
double expectedDegrees = 23.198590513648185;
double reverseExpectedDegrees = 336.8014094863518;
double reverseExpected = 5.878293520894503;

double actualPointsFour = Functions.angle(start1, end1, start2, end2);
double actualPointsFourDegrees = Functions.degrees(actualPointsFour);
double actualPointsFourReverse = Functions.angle(start2, end2, start1, end1);
double actualPointsFourReverseDegrees = Functions.degrees(actualPointsFourReverse);

assertEquals(expected, actualPointsFour, 1e-9);
assertEquals(expectedDegrees, actualPointsFourDegrees, 1e-9);
assertEquals(reverseExpected, actualPointsFourReverse, 1e-9);
assertEquals(reverseExpectedDegrees, actualPointsFourReverseDegrees, 1e-9);
}

@Test
public void angleFourPoints3D() {
Point start1 = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 0, 4));
Point end1 = GEOMETRY_FACTORY.createPoint(new Coordinate(1, 1, 5));
Point start2 = GEOMETRY_FACTORY.createPoint(new Coordinate(1, 0, 9));
Point end2 = GEOMETRY_FACTORY.createPoint(new Coordinate(6, 2, 2));

double expected = 0.4048917862850834;
double expectedDegrees = 23.198590513648185;
double reverseExpectedDegrees = 336.8014094863518;
double reverseExpected = 5.878293520894503;

double actualPointsFour = Functions.angle(start1, end1, start2, end2);
double actualPointsFourDegrees = Functions.degrees(actualPointsFour);
double actualPointsFourReverse = Functions.angle(start2, end2, start1, end1);
double actualPointsFourReverseDegrees = Functions.degrees(actualPointsFourReverse);

assertEquals(expected, actualPointsFour, 1e-9);
assertEquals(expectedDegrees, actualPointsFourDegrees, 1e-9);
assertEquals(reverseExpected, actualPointsFourReverse, 1e-9);
assertEquals(reverseExpectedDegrees, actualPointsFourReverseDegrees, 1e-9);
}



@Test
public void angleThreePoints() {
Point point1 = GEOMETRY_FACTORY.createPoint(new Coordinate(1, 1));
Point point2 = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 0));
Point point3 = GEOMETRY_FACTORY.createPoint(new Coordinate(3, 2));

double expected = 0.19739555984988044;
double expectedDegrees = 11.309932474020195;


double actualPointsThree = Functions.angle(point1, point2, point3);
double actualPointsThreeDegrees = Functions.degrees(actualPointsThree);

assertEquals(expected, actualPointsThree, 1e-9);
assertEquals(expectedDegrees, actualPointsThreeDegrees, 1e-9);

}

@Test
public void angleTwoLineStrings() {
LineString lineString1 = GEOMETRY_FACTORY.createLineString(coordArray(0, 0, 1, 1));
LineString lineString2 = GEOMETRY_FACTORY.createLineString(coordArray(0, 0, 3, 2));

double expected = 0.19739555984988044;
double expectedDegrees = 11.309932474020195;
double reverseExpected = 6.085789747329706;
double reverseExpectedDegrees = 348.69006752597977;

double actualLineString = Functions.angle(lineString1, lineString2);
double actualLineStringReverse = Functions.angle(lineString2, lineString1);
double actualLineStringDegrees = Functions.degrees(actualLineString);
double actualLineStringReverseDegrees = Functions.degrees(actualLineStringReverse);

assertEquals(expected, actualLineString, 1e-9);
assertEquals(reverseExpected, actualLineStringReverse, 1e-9);
assertEquals(expectedDegrees, actualLineStringDegrees, 1e-9);
assertEquals(reverseExpectedDegrees, actualLineStringReverseDegrees, 1e-9);
}

@Test
public void angleInvalidEmptyGeom() {
Point point1 = GEOMETRY_FACTORY.createPoint(new Coordinate(3, 5));
Point point2 = GEOMETRY_FACTORY.createPoint();
Point point3 = GEOMETRY_FACTORY.createPoint(new Coordinate(1, 1));

Exception e = assertThrows(IllegalArgumentException.class, () -> Functions.angle(point1, point2, point3));
assertEquals("ST_Angle cannot support empty geometries.", e.getMessage());
}

@Test
public void angleInvalidUnsupportedGeom() {
Point point1 = GEOMETRY_FACTORY.createPoint(new Coordinate(3, 5));
Polygon polygon = GEOMETRY_FACTORY.createPolygon(coordArray(1, 0, 1, 1, 2, 1, 2, 0, 1, 0));
Point point3 = GEOMETRY_FACTORY.createPoint(new Coordinate(1, 1));

Exception e = assertThrows(IllegalArgumentException.class, () -> Functions.angle(point1, polygon, point3));
assertEquals("ST_Angle supports either only POINT or only LINESTRING geometries.", e.getMessage());
}


public void affineEmpty3D() {
LineString emptyLineString = GEOMETRY_FACTORY.createLineString();
String expected = emptyLineString.toText();
Expand Down
84 changes: 84 additions & 0 deletions docs/api/flink/Function.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,74 @@ Input: `POLYGON ((1 0 1, 1 1 1, 2 2 2, 1 0 1))`

Output: `POLYGON Z((2 3 1, 4 5 1, 7 8 2, 2 3 1))`

## ST_Angle

Introduction: Compute and return the angle between two vectors represented by the provided points or linestrings.

There are three variants possible for ST_Angle:

`ST_Angle(Geometry point1, Geometry point2, Geometry point3, Geometry point4)`
Computes the angle formed by vectors represented by point1 - point2 and point3 - point4

`ST_Angle(Geometry point1, Geometry point2, Geometry point3)`
Computes the angle formed by vectors represented by point2 - point1 and point2 - point3

`ST_Angle(Geometry line1, Geometry line2)`
Computes the angle formed by vectors S1 - E1 and S2 - E2, where S and E denote start and end points respectively

!!!Note
If any other geometry type is provided, ST_Angle throws an IllegalArgumentException.
Additionally, if any of the provided geometry is empty, ST_Angle throws an IllegalArgumentException.

!!!Note
If a 3D geometry is provided, ST_Angle computes the angle ignoring the z ordinate, equivalent to calling ST_Angle for corresponding 2D geometries.

!!!Tip
ST_Angle returns the angle in radian between 0 and 2\Pi. To convert the angle to degrees, use [ST_Degrees](./#st_degrees).


Format: `ST_Angle(p1, p2, p3, p4) | ST_Angle(p1, p2, p3) | ST_Angle(line1, line2)`

Since: `1.5.0`

Example:

```sql
ST_Angle(p1, p2, p3, p4)
```

Input: `p1: POINT (0 0)`

Input: `p2: POINT (1 1)`

Input: `p3: POINT (1 0)`

Input: `p4: POINT(6 2)`

Output: 0.4048917862850834

```sql
ST_Angle(p1, p2, p3)
```

Input: `p1: POINT (1 1)`

Input: `p2: POINT (0 0)`

Input: `p3: POINT(3 2)`

Output: 0.19739555984988044

```sql
ST_Angle(line1, line2)
```

Input: `line1: LINESTRING (0 0, 1 1)`

Input: `line2: LINESTRING (0 0, 3 2)`

Output: 0.19739555984988044

## ST_Area

Introduction: Return the area of A
Expand Down Expand Up @@ -462,6 +530,22 @@ SELECT ST_DistanceSpheroid(ST_GeomFromWKT('POINT (51.3168 -0.56)'), ST_GeomFromW

Output: `544430.9411996207`

## ST_Degrees

Introduction: Convert an angle in radian to degrees.

Format: `ST_Degrees(angleInRadian)`

Since: `1.5.0`

Example:

```sql
SELECT ST_Degrees(0.19739555984988044)
```

Output: 11.309932474020195

## ST_Envelope

Introduction: Return the envelop boundary of A
Expand Down
Loading

0 comments on commit da0e275

Please sign in to comment.