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-294 | SEDONA-310] Add ST_Angle and ST_Degrees to sedona #870

Merged
merged 15 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -970,8 +970,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 @@ -1000,6 +998,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
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 @@ -1033,6 +1033,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