This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sedona.git


The following commit(s) were added to refs/heads/master by this push:
     new 9f5003e12 [SEDONA-610] Add ST_IsValidTrajectory (#1481)
9f5003e12 is described below

commit 9f5003e126b66374cf0940997af5b75e92532399
Author: Furqaan Khan <[email protected]>
AuthorDate: Thu Jun 20 18:45:08 2024 -0400

    [SEDONA-610] Add ST_IsValidTrajectory (#1481)
    
    * feat: add ST_IsValidTrajectory
    
    * remove print statements and add example
---
 .../java/org/apache/sedona/common/Functions.java   | 20 ++++++++++++
 .../org/apache/sedona/common/FunctionsTest.java    | 15 +++++++++
 docs/api/flink/Function.md                         | 36 ++++++++++++++++++++++
 docs/api/sql/Function.md                           | 36 ++++++++++++++++++++++
 .../main/java/org/apache/sedona/flink/Catalog.java |  1 +
 .../apache/sedona/flink/expressions/Functions.java | 10 ++++++
 .../java/org/apache/sedona/flink/FunctionTest.java | 22 +++++++++++++
 .../scala/org/apache/sedona/sql/UDF/Catalog.scala  |  1 +
 .../sql/sedona_sql/expressions/Functions.scala     |  8 +++++
 .../sql/sedona_sql/expressions/st_functions.scala  |  5 +++
 .../apache/sedona/sql/dataFrameAPITestScala.scala  | 11 +++++++
 .../org/apache/sedona/sql/functionTestScala.scala  | 13 +++++++-
 12 files changed, 177 insertions(+), 1 deletion(-)

diff --git a/common/src/main/java/org/apache/sedona/common/Functions.java 
b/common/src/main/java/org/apache/sedona/common/Functions.java
index 552c37c8e..58fdcdbbb 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -727,6 +727,26 @@ public class Functions {
     return isValidOp.isValid();
   }
 
+  public static boolean isValidTrajectory(Geometry geom) {
+    if (!(geom.getClass().getSimpleName().equals("LineString"))) {
+      return false;
+    }
+
+    if (!Functions.hasM(geom)) {
+      return false;
+    }
+
+    double measure = -1 * Double.MAX_VALUE;
+    Coordinate[] coordinates = geom.getCoordinates();
+    for (int i = 0; i < geom.getNumPoints(); i++) {
+      if (coordinates[i].getM() <= measure) {
+        return false;
+      }
+      measure = coordinates[i].getM();
+    }
+    return true;
+  }
+
   public static Geometry addPoint(Geometry linestring, Geometry point) {
     return addPoint(linestring, point, -1);
   }
diff --git a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java 
b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
index eac55be8b..a3aa562b6 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -3459,6 +3459,21 @@ public class FunctionsTest extends TestBase {
         e.getMessage());
   }
 
+  @Test
+  public void test() throws ParseException {
+    Geometry geom = Constructors.geomFromEWKT("MULTILINESTRING M ((0 0 1,0 1 
2), (0 0 1,0 1 2))");
+    boolean actual = Functions.isValidTrajectory(geom);
+    assertFalse(actual);
+
+    geom = Constructors.geomFromEWKT("LINESTRING M (0 0 1, 0 1 2)");
+    actual = Functions.isValidTrajectory(geom);
+    assertTrue(actual);
+
+    geom = Constructors.geomFromEWKT("LINESTRING M (0 0 1, 0 1 1)");
+    actual = Functions.isValidTrajectory(geom);
+    assertFalse(actual);
+  }
+
   @Test
   public void isValidDetail() throws ParseException {
     // Valid geometry
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index 02068ca9d..ec1705f93 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -2156,6 +2156,42 @@ gid  |                  validity_info
 
 ```
 
+## ST_IsValidTrajectory
+
+Introduction: This function checks if a geometry is a valid trajectory 
representation. For a trajectory to be considered valid, it must be a 
LineString that includes measure (M) values. The key requirement is that the M 
values increase from one vertex to the next as you move along the line.
+
+Format: `ST_IsValidTrajectory(geom: Geometry)`
+
+Since: `v1.6.1`
+
+SQL Example:
+
+```sql
+SELECT ST_IsValidTrajectory(
+               ST_GeomFromText('LINESTRING M (0 0 1, 0 1 2)')
+)
+```
+
+Output:
+
+```
+true
+```
+
+SQL Example:
+
+```sql
+SELECT ST_IsValidTrajectory(
+               ST_GeomFromText('LINESTRING M (0 0 1, 0 1 0)')
+)
+```
+
+Output:
+
+```sql
+false
+```
+
 ## ST_Length
 
 Introduction: Returns the perimeter of A.
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index 591312b8e..a7b1891d1 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -2201,6 +2201,42 @@ gid  |                  validity_info
 
 ```
 
+## ST_IsValidTrajectory
+
+Introduction: This function checks if a geometry is a valid trajectory 
representation. For a trajectory to be considered valid, it must be a 
LineString that includes measure (M) values. The key requirement is that the M 
values increase from one vertex to the next as you move along the line.
+
+Format: `ST_IsValidTrajectory(geom: Geometry)`
+
+Since: `v1.6.1`
+
+SQL Example:
+
+```sql
+SELECT ST_IsValidTrajectory(
+               ST_GeomFromText('LINESTRING M (0 0 1, 0 1 2)')
+)
+```
+
+Output:
+
+```
+true
+```
+
+SQL Example:
+
+```sql
+SELECT ST_IsValidTrajectory(
+               ST_GeomFromText('LINESTRING M (0 0 1, 0 1 0)')
+)
+```
+
+Output:
+
+```sql
+false
+```
+
 ## ST_Length
 
 Introduction: Returns the perimeter of A.
diff --git a/flink/src/main/java/org/apache/sedona/flink/Catalog.java 
b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
index 9013c5fde..165205c52 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -136,6 +136,7 @@ public class Catalog {
       new Functions.ST_IsPolygonCW(),
       new Functions.ST_IsRing(),
       new Functions.ST_IsSimple(),
+      new Functions.ST_IsValidTrajectory(),
       new Functions.ST_IsValid(),
       new Functions.ST_Normalize(),
       new Functions.ST_AddMeasure(),
diff --git 
a/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java 
b/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
index 96e120387..20965a634 100644
--- a/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
+++ b/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
@@ -1842,6 +1842,16 @@ public class Functions {
     }
   }
 
+  public static class ST_IsValidTrajectory extends ScalarFunction {
+    @DataTypeHint("Boolean")
+    public Boolean eval(
+        @DataTypeHint(value = "RAW", bridgedTo = 
org.locationtech.jts.geom.Geometry.class)
+            Object o) {
+      Geometry geometry = (Geometry) o;
+      return org.apache.sedona.common.Functions.isValidTrajectory(geometry);
+    }
+  }
+
   public static class ST_IsValidReason extends ScalarFunction {
     @DataTypeHint("String")
     public String eval(
diff --git a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java 
b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
index 83f15b7de..db05f81fe 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -2441,6 +2441,28 @@ public class FunctionTest extends TestBase {
     assertEquals(3, result, 0);
   }
 
+  @Test
+  public void testIsValidTrajectory() {
+    Table table =
+        tableEnv.sqlQuery(
+            "SELECT ST_GeomFromText('LINESTRING M (0 0 1, 0 1 2)') as geom1, 
ST_GeomFromText('LINESTRING M (0 0 1, 0 1 1)') as geom2");
+    boolean result =
+        (boolean)
+            first(
+                    table.select(
+                        
call(Functions.ST_IsValidTrajectory.class.getSimpleName(), $("geom1"))))
+                .getField(0);
+    assertTrue(result);
+
+    result =
+        (boolean)
+            first(
+                    table.select(
+                        
call(Functions.ST_IsValidTrajectory.class.getSimpleName(), $("geom2"))))
+                .getField(0);
+    assertFalse(result);
+  }
+
   @Test
   public void testIsValidReason() {
     // Test with an invalid geometry (bow-tie polygon)
diff --git 
a/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala 
b/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
index c2bee9c09..6353e16f2 100644
--- a/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
+++ b/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
@@ -90,6 +90,7 @@ object Catalog {
     function[ST_UnaryUnion](),
     function[ST_Union](),
     function[ST_IsValidDetail](),
+    function[ST_IsValidTrajectory](),
     function[ST_IsValid](),
     function[ST_IsEmpty](),
     function[ST_ReducePrecision](),
diff --git 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
index b8d5bf90c..3f33a66d0 100644
--- 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
+++ 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
@@ -352,6 +352,14 @@ case class ST_IsValidDetail(children: Seq[Expression])
     .add("location", GeometryUDT, nullable = true)
 }
 
+case class ST_IsValidTrajectory(inputExpressions: Seq[Expression])
+    extends 
InferredExpression(inferrableFunction1(Functions.isValidTrajectory)) {
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = 
{
+    copy(inputExpressions = newChildren)
+  }
+}
+
 /**
  * Test if Geometry is valid.
  *
diff --git 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
index d879c4549..b53a79d06 100644
--- 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
+++ 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
@@ -237,6 +237,11 @@ object st_functions extends DataFrameAPI {
   def ST_IsSimple(geometry: Column): Column = 
wrapExpression[ST_IsSimple](geometry)
   def ST_IsSimple(geometry: String): Column = 
wrapExpression[ST_IsSimple](geometry)
 
+  def ST_IsValidTrajectory(geometry: Column): Column =
+    wrapExpression[ST_IsValidTrajectory](geometry)
+  def ST_IsValidTrajectory(geometry: String): Column =
+    wrapExpression[ST_IsValidTrajectory](geometry)
+
   def ST_IsValid(geometry: Column): Column = 
wrapExpression[ST_IsValid](geometry)
   def ST_IsValid(geometry: String): Column = 
wrapExpression[ST_IsValid](geometry)
 
diff --git 
a/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala 
b/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
index 0e1aef5c0..189733033 100644
--- 
a/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
+++ 
b/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
@@ -2073,6 +2073,17 @@ class dataFrameAPITestScala extends TestBaseScala {
       assertTrue(actual)
     }
 
+    it("Should pass ST_IsValidTrajectory") {
+      val baseDf = sparkSession.sql(
+        "SELECT ST_GeomFromText('LINESTRING M (0 0 1, 0 1 2)') as geom1, 
ST_GeomFromText('LINESTRING M (0 0 1, 0 1 1)') as geom2")
+      var actual =
+        
baseDf.select(ST_IsValidTrajectory("geom1")).first().get(0).asInstanceOf[Boolean]
+      assertTrue("Valid", actual)
+
+      actual = 
baseDf.select(ST_IsValidTrajectory("geom2")).first().get(0).asInstanceOf[Boolean]
+      assertFalse("Not valid", actual)
+    }
+
     it("Passed ST_IsValidDetail") {
       // Valid Geometry
       var baseDf = sparkSession.sql(
diff --git 
a/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala 
b/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
index 4b3d18dbc..76743f619 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
@@ -25,7 +25,7 @@ import 
org.apache.spark.sql.catalyst.expressions.GenericRowWithSchema
 import org.apache.spark.sql.functions._
 import org.apache.spark.sql.{DataFrame, Row}
 import org.geotools.referencing.CRS
-import org.junit.Assert.assertEquals
+import org.junit.Assert.{assertEquals, assertFalse, assertTrue}
 import org.locationtech.jts.algorithm.MinimumBoundingCircle
 import org.locationtech.jts.geom.{Coordinate, Geometry, GeometryFactory, 
Polygon}
 import org.locationtech.jts.io.WKTWriter
@@ -3230,6 +3230,17 @@ class functionTestScala
 
   }
 
+  it("Should pass ST_IsValidTrajectory") {
+    val baseDf = sparkSession.sql(
+      "SELECT ST_GeomFromText('LINESTRING M (0 0 1, 0 1 2)') as geom1, 
ST_GeomFromText('LINESTRING M (0 0 1, 0 1 1)') as geom2")
+    var actual =
+      
baseDf.selectExpr("ST_IsValidTrajectory(geom1)").first().get(0).asInstanceOf[Boolean]
+    assertTrue("Valid", actual)
+
+    actual = 
baseDf.selectExpr("ST_IsValidTrajectory(geom2)").first().get(0).asInstanceOf[Boolean]
+    assertFalse("Not valid", actual)
+  }
+
   it("Should pass ST_IsValidDetail") {
     val testData = Seq(
       (5330, "POLYGON ((0 0, 3 3, 0 3, 3 0, 0 0))"),

Reply via email to