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 a768abd3 [SEDONA-238] Implement OGC GeometryType (#873)
a768abd3 is described below
commit a768abd319b772892f753335455720b6c794de69
Author: Junhao Liu <[email protected]>
AuthorDate: Wed Jun 28 01:37:26 2023 +0800
[SEDONA-238] Implement OGC GeometryType (#873)
---
.../java/org/apache/sedona/common/Functions.java | 8 +++
.../org/apache/sedona/common/utils/GeomUtils.java | 12 ++--
.../org/apache/sedona/common/FunctionsTest.java | 84 ++++++++++++++++++++++
docs/api/flink/Function.md | 34 +++++++++
docs/api/sql/Function.md | 34 +++++++++
.../main/java/org/apache/sedona/flink/Catalog.java | 1 +
.../apache/sedona/flink/expressions/Functions.java | 8 +++
.../java/org/apache/sedona/flink/FunctionTest.java | 13 +++-
python/sedona/sql/st_functions.py | 12 ++++
python/tests/sql/test_dataframe_api.py | 1 +
.../scala/org/apache/sedona/sql/UDF/Catalog.scala | 1 +
.../sql/sedona_sql/expressions/Functions.scala | 8 +++
.../sql/sedona_sql/expressions/st_functions.scala | 3 +
.../apache/sedona/sql/dataFrameAPITestScala.scala | 8 +++
.../org/apache/sedona/sql/functionTestScala.scala | 19 +++++
15 files changed, 238 insertions(+), 8 deletions(-)
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 2d3c3f6c..78f237fb 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -594,6 +594,14 @@ public class Functions {
return "ST_" + geometry.getGeometryType();
}
+ public static String geometryTypeWithMeasured(Geometry geometry) {
+ String geometryType = geometry.getGeometryType().toUpperCase();
+ if (GeomUtils.isMeasuredGeometry(geometry)) {
+ geometryType += "M";
+ }
+ return geometryType;
+ }
+
public static Geometry startPoint(Geometry geometry) {
if (geometry instanceof LineString) {
LineString line = (LineString) geometry;
diff --git a/common/src/main/java/org/apache/sedona/common/utils/GeomUtils.java
b/common/src/main/java/org/apache/sedona/common/utils/GeomUtils.java
index 1a3c5baf..9c723d91 100644
--- a/common/src/main/java/org/apache/sedona/common/utils/GeomUtils.java
+++ b/common/src/main/java/org/apache/sedona/common/utils/GeomUtils.java
@@ -14,13 +14,7 @@
package org.apache.sedona.common.utils;
import org.locationtech.jts.geom.*;
-import org.locationtech.jts.geom.Point;
-import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.impl.CoordinateArraySequence;
-
-import org.locationtech.jts.geom.CoordinateSequence;
-import org.locationtech.jts.geom.CoordinateSequenceFilter;
-import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.ByteOrderValues;
import org.locationtech.jts.io.WKBWriter;
import org.locationtech.jts.io.WKTWriter;
@@ -31,7 +25,6 @@ import
org.locationtech.jts.algorithm.distance.DiscreteHausdorffDistance;
import java.nio.ByteOrder;
import java.util.*;
-import java.util.List;
import static org.locationtech.jts.geom.Coordinate.NULL_ORDINATE;
@@ -494,4 +487,9 @@ public class GeomUtils {
}
return hausdorffDistanceObj.distance();
}
+
+ public static Boolean isMeasuredGeometry(Geometry geom) {
+ Coordinate coordinate = geom.getCoordinate();
+ return !Double.isNaN(coordinate.getM());
+ }
}
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 16970a47..7e72e808 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -1117,6 +1117,90 @@ public class FunctionsTest {
assertEquals(expectedPolygon2.toText(),
actualGeomCollection.getGeometryN(0).getGeometryN(1).getGeometryN(1).toText());
}
+ @Test
+ public void geometryTypeWithMeasured2D() {
+ String expected1 = "POINT";
+ String actual1 =
Functions.geometryTypeWithMeasured(GEOMETRY_FACTORY.createPoint(new
Coordinate(10, 5)));
+ assertEquals(expected1, actual1);
+
+ // Create a point with measure value
+ CoordinateXYM coords = new CoordinateXYM(2, 3, 4);
+ Point measuredPoint = GEOMETRY_FACTORY.createPoint(coords);
+ String expected2 = "POINTM";
+ String actual2 = Functions.geometryTypeWithMeasured(measuredPoint);
+ assertEquals(expected2, actual2);
+
+ // Create a linestring with measure value
+ CoordinateXYM[] coordsLineString = new CoordinateXYM[] {new
CoordinateXYM(1, 2, 3), new CoordinateXYM(4, 5, 6)};
+ LineString measuredLineString =
GEOMETRY_FACTORY.createLineString(coordsLineString);
+ String expected3 = "LINESTRINGM";
+ String actual3 =
Functions.geometryTypeWithMeasured(measuredLineString);
+ assertEquals(expected3, actual3);
+
+ // Create a polygon with measure value
+ CoordinateXYM[] coordsPolygon = new CoordinateXYM[] {new
CoordinateXYM(0, 0, 0), new CoordinateXYM(1, 1, 0), new CoordinateXYM(0, 1, 0),
new CoordinateXYM(0, 0, 0)};
+ Polygon measuredPolygon =
GEOMETRY_FACTORY.createPolygon(coordsPolygon);
+ String expected4 = "POLYGONM";
+ String actual4 = Functions.geometryTypeWithMeasured(measuredPolygon);
+ assertEquals(expected4, actual4);
+ }
+
+ @Test
+ public void geometryTypeWithMeasured3D() {
+ String expected1 = "POINT";
+ String actual1 =
Functions.geometryTypeWithMeasured(GEOMETRY_FACTORY.createPoint(new
Coordinate(10, 5, 1)));
+ assertEquals(expected1, actual1);
+
+ // Create a point with measure value
+ CoordinateXYZM coordsPoint = new CoordinateXYZM(2, 3, 4, 0);
+ Point measuredPoint = GEOMETRY_FACTORY.createPoint(coordsPoint);
+ String expected2 = "POINTM";
+ String actual2 = Functions.geometryTypeWithMeasured(measuredPoint);
+ assertEquals(expected2, actual2);
+
+ // Create a linestring with measure value
+ CoordinateXYZM[] coordsLineString = new CoordinateXYZM[] {new
CoordinateXYZM(1, 2, 3, 0), new CoordinateXYZM(4, 5, 6, 0)};
+ LineString measuredLineString =
GEOMETRY_FACTORY.createLineString(coordsLineString);
+ String expected3 = "LINESTRINGM";
+ String actual3 =
Functions.geometryTypeWithMeasured(measuredLineString);
+ assertEquals(expected3, actual3);
+
+ // Create a polygon with measure value
+ CoordinateXYZM[] coordsPolygon = new CoordinateXYZM[] {new
CoordinateXYZM(0, 0, 0, 0), new CoordinateXYZM(1, 1, 0, 0), new
CoordinateXYZM(0, 1, 0, 0), new CoordinateXYZM(0, 0, 0, 0)};
+ Polygon measuredPolygon =
GEOMETRY_FACTORY.createPolygon(coordsPolygon);
+ String expected4 = "POLYGONM";
+ String actual4 = Functions.geometryTypeWithMeasured(measuredPolygon);
+ assertEquals(expected4, actual4);
+ }
+
+ @Test
+ public void geometryTypeWithMeasuredCollection() {
+ String expected1 = "GEOMETRYCOLLECTION";
+ String actual1 =
Functions.geometryTypeWithMeasured(GEOMETRY_FACTORY.createGeometryCollection(new
Geometry[] {GEOMETRY_FACTORY.createPoint(new Coordinate(10, 5))}));
+ assertEquals(expected1, actual1);
+
+ // Create a geometrycollection with measure value
+ CoordinateXYM coords = new CoordinateXYM(2, 3, 4);
+ Point measuredPoint = GEOMETRY_FACTORY.createPoint(coords);
+ String expected2 = "GEOMETRYCOLLECTIONM";
+ String actual2 =
Functions.geometryTypeWithMeasured(GEOMETRY_FACTORY.createGeometryCollection(new
Geometry[] {measuredPoint}));
+ assertEquals(expected2, actual2);
+
+ // Create a geometrycollection with measure value
+ CoordinateXYM[] coordsLineString = new CoordinateXYM[] {new
CoordinateXYM(1, 2, 3), new CoordinateXYM(4, 5, 6)};
+ LineString measuredLineString =
GEOMETRY_FACTORY.createLineString(coordsLineString);
+ String expected3 = "GEOMETRYCOLLECTIONM";
+ String actual3 =
Functions.geometryTypeWithMeasured(GEOMETRY_FACTORY.createGeometryCollection(new
Geometry[] {measuredLineString}));
+ assertEquals(expected3, actual3);
+
+ // Create a geometrycollection with measure value
+ CoordinateXYM[] coordsPolygon = new CoordinateXYM[] {new
CoordinateXYM(0, 0, 0), new CoordinateXYM(1, 1, 0), new CoordinateXYM(0, 1, 0),
new CoordinateXYM(0, 0, 0)};
+ Polygon measuredPolygon =
GEOMETRY_FACTORY.createPolygon(coordsPolygon);
+ String expected4 = "GEOMETRYCOLLECTIONM";
+ String actual4 =
Functions.geometryTypeWithMeasured(GEOMETRY_FACTORY.createGeometryCollection(new
Geometry[] {measuredPolygon}));
+ assertEquals(expected4, actual4);
+ }
+
@Test
public void hausdorffDistanceDefaultGeom2D() throws Exception {
Polygon polygon1 = GEOMETRY_FACTORY.createPolygon(coordArray3d(1, 0,
1, 1, 1, 2, 2, 1, 5, 2, 0, 1, 1, 0, 1));
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index 52e8fe54..e953b5ac 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -1,3 +1,37 @@
+## GeometryType
+
+Introduction: Returns the type of the geometry as a string. Eg: 'LINESTRING',
'POLYGON', 'MULTIPOINT', etc. This function also indicates if the geometry is
measured, by returning a string of the form 'POINTM'.
+
+Format: `GeometryType (A:geometry)`
+
+Since: `v1.5.0`
+
+Example:
+
+```sql
+SELECT GeometryType(ST_GeomFromText('LINESTRING(77.29 29.07,77.42 29.26,77.27
29.31,77.29 29.07)'));
+```
+
+Result:
+
+```
+ geometrytype
+--------------
+ LINESTRING
+```
+
+```sql
+SELECT GeometryType(ST_GeomFromText('POINTM(0 0 1)'));
+```
+
+Result:
+
+```
+ geometrytype
+--------------
+ POINTM
+```
+
## ST_3DDistance
Introduction: Return the 3-dimensional minimum cartesian distance between A
and B
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index 931b6f66..3e9f443d 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -1,3 +1,37 @@
+## GeometryType
+
+Introduction: Returns the type of the geometry as a string. Eg: 'LINESTRING',
'POLYGON', 'MULTIPOINT', etc. This function also indicates if the geometry is
measured, by returning a string of the form 'POINTM'.
+
+Format: `GeometryType (A:geometry)`
+
+Since: `v1.5.0`
+
+Example:
+
+```sql
+SELECT GeometryType(ST_GeomFromText('LINESTRING(77.29 29.07,77.42 29.26,77.27
29.31,77.29 29.07)'));
+```
+
+Result:
+
+```
+ geometrytype
+--------------
+ LINESTRING
+```
+
+```sql
+SELECT GeometryType(ST_GeomFromText('POINTM(0 0 1)'));
+```
+
+Result:
+
+```
+ geometrytype
+--------------
+ POINTM
+```
+
## ST_3DDistance
Introduction: Return the 3-dimensional minimum cartesian distance between A
and B
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 2f2657b9..2e515dbd 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -36,6 +36,7 @@ public class Catalog {
new Constructors.ST_GeomFromKML(),
new Constructors.ST_MPolyFromText(),
new Constructors.ST_MLineFromText(),
+ new Functions.GeometryType(),
new Functions.ST_Area(),
new Functions.ST_AreaSpheroid(),
new Functions.ST_Azimuth(),
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 7c6c132d..b8b55b12 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
@@ -21,6 +21,14 @@ import org.opengis.referencing.FactoryException;
import org.opengis.referencing.operation.TransformException;
public class Functions {
+ public static class GeometryType extends ScalarFunction {
+ @DataTypeHint("String")
+ public String eval(@DataTypeHint(value = "RAW", bridgedTo =
org.locationtech.jts.geom.Geometry.class) Object o) {
+ Geometry geom = (Geometry) o;
+ return
org.apache.sedona.common.Functions.geometryTypeWithMeasured(geom);
+ }
+ }
+
public static class ST_Area extends ScalarFunction {
@DataTypeHint("Double")
public Double eval(@DataTypeHint(value = "RAW", bridgedTo =
org.locationtech.jts.geom.Geometry.class) Object o) {
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 b26f75ad..983004e4 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -153,7 +153,6 @@ public class FunctionTest extends TestBase{
}
-
@Test
public void testDimension(){
Table pointTable = tableEnv.sqlQuery(
@@ -164,6 +163,7 @@ public class FunctionTest extends TestBase{
"SELECT
ST_Dimension(ST_GeomFromWKT('GEOMETRYCOLLECTION(MULTIPOLYGON(((0 0, 0 1, 1 1, 1
0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2 2))), MULTIPOINT(6 6, 7 7, 8 8))'))");
assertEquals(2, first(pointTable).getField(0));
}
+
@Test
public void testDistance() {
Table pointTable = createPointTable(testDataSize);
@@ -250,6 +250,17 @@ public class FunctionTest extends TestBase{
assertEquals(first(pointTable).getField(0), "s0000");
}
+ @Test
+ public void testGeometryType() {
+ Table pointTable = tableEnv.sqlQuery(
+ "SELECT GeometryType(ST_GeomFromText('LINESTRING(77.29
29.07,77.42 29.26,77.27 29.31,77.29 29.07)'))");
+ assertEquals("LINESTRING", first(pointTable).getField(0));
+
+ pointTable = tableEnv.sqlQuery(
+ "SELECT GeometryType(ST_GeomFromText('POINTM(2.0 3.5
10.2)'))");
+ assertEquals("POINTM", first(pointTable).getField(0));
+ }
+
@Test
public void testPointOnSurface() {
Table pointTable = createPointTable_real(testDataSize);
diff --git a/python/sedona/sql/st_functions.py
b/python/sedona/sql/st_functions.py
index e20f1912..417be7cb 100644
--- a/python/sedona/sql/st_functions.py
+++ b/python/sedona/sql/st_functions.py
@@ -24,6 +24,7 @@ from sedona.sql.dataframe_api import call_sedona_function,
ColumnOrName, ColumnO
__all__ = [
+ "GeometryType",
"ST_3DDistance",
"ST_AddPoint",
"ST_Area",
@@ -121,6 +122,17 @@ __all__ = [
_call_st_function = partial(call_sedona_function, "st_functions")
+@validate_argument_types
+def GeometryType(geometry: ColumnOrName):
+ """Return the type of the geometry as a string.
+ This function also indicates if the geometry is measured, by returning a
string of the form 'POINTM'.
+
+ :param geometry: Geometry column to calculate the dimension for.
+ :type geometry: ColumnOrName
+ :return: Type of geometry as a string column.
+ :rtype: Column
+ """
+ return _call_st_function("GeometryType", geometry)
@validate_argument_types
def ST_3DDistance(a: ColumnOrName, b: ColumnOrName) -> Column:
diff --git a/python/tests/sql/test_dataframe_api.py
b/python/tests/sql/test_dataframe_api.py
index 69961a43..f6419bde 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -49,6 +49,7 @@ test_configurations = [
(stc.ST_PolygonFromText, ("multiple_point", lambda: f.lit(',')),
"constructor", "", "POLYGON ((0 0, 1 0, 1 1, 0 0))"),
# functions
+ (stf.GeometryType, ("line",), "linestring_geom", "", "LINESTRING"),
(stf.ST_3DDistance, ("a", "b"), "two_points", "", 5.0),
(stf.ST_Affine, ("geom", 1.0, 2.0, 1.0, 2.0, 1.0, 2.0, 1.0, 1.0, 1.0, 1.0,
1.0, 1.0), "square_geom", "", "POLYGON ((2 3, 4 5, 5 6, 3 4, 2 3))"),
(stf.ST_AddPoint, ("line", lambda: f.expr("ST_Point(1.0, 1.0)")),
"linestring_geom", "", "LINESTRING (0 0, 1 0, 2 0, 3 0, 4 0, 5 0, 1 1)"),
diff --git a/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
b/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
index ccb36175..096fee0c 100644
--- a/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
+++ b/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
@@ -36,6 +36,7 @@ object Catalog {
val expressions: Seq[FunctionDescription] = Seq(
// Expression for vectors
+ function[GeometryType](),
function[ST_PointFromText](),
function[ST_PolygonFromText](),
function[ST_LineStringFromText](),
diff --git
a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
index 1ef150d3..4222b16d 100644
---
a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
+++
b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
@@ -1025,6 +1025,7 @@ case class ST_Dimension(inputExpressions: Seq[Expression])
copy(inputExpressions = newChildren)
}
}
+
case class ST_BoundingDiagonal(inputExpressions: Seq[Expression])
extends InferredExpression(Functions.boundingDiagonal _) {
protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) =
{
@@ -1038,3 +1039,10 @@ case class ST_HausdorffDistance(inputExpressions:
Seq[Expression])
copy(inputExpressions = newChildren)
}
}
+
+case class GeometryType(inputExpressions: Seq[Expression])
+ extends InferredExpression(Functions.geometryTypeWithMeasured _) with
FoldableExpression {
+ protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) =
{
+ copy(inputExpressions = newChildren)
+ }
+}
diff --git
a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
index 9c5a64a9..b22fd84b 100644
---
a/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
+++
b/sql/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
@@ -23,6 +23,9 @@ import
org.apache.spark.sql.sedona_sql.expressions.collect.{ST_Collect}
import org.locationtech.jts.operation.buffer.BufferParameters
object st_functions extends DataFrameAPI {
+ def GeometryType(geometry: Column): Column =
wrapExpression[GeometryType](geometry)
+ def GeometryType(geometry: String): Column =
wrapExpression[GeometryType](geometry)
+
def ST_3DDistance(a: Column, b: Column): Column =
wrapExpression[ST_3DDistance](a, b)
def ST_3DDistance(a: String, b: String): Column =
wrapExpression[ST_3DDistance](a, b)
diff --git
a/sql/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
b/sql/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
index a1c2ebf7..a78a0e35 100644
---
a/sql/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
+++
b/sql/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
@@ -1046,5 +1046,13 @@ class dataFrameAPITestScala extends TestBaseScala {
assert(expected == actual)
assert(expected == actualDefaultValue)
}
+
+ it("Passed GeometryType") {
+ val polyDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((1 2, 2
1, 2 0, 4 1, 1 2))') AS geom")
+ val df = polyDf.select(GeometryType("geom"))
+ val expected = "POLYGON"
+ val actual = df.take(1)(0).get(0).asInstanceOf[String]
+ assert(expected == actual)
+ }
}
}
diff --git
a/sql/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
b/sql/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
index d31849ea..762cb7b2 100644
--- a/sql/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
+++ b/sql/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
@@ -2078,4 +2078,23 @@ class functionTestScala extends TestBaseScala with
Matchers with GeometrySample
}
}
+ it ("should pass GeometryType") {
+ val geomTestCases = Map (
+ ("'POINT (51.3168 -0.56)'") -> "'POINT'",
+ ("'POINT (0 0 1)'") -> "'POINT'",
+ ("'LINESTRING (0 0, 0 90)'") -> "'LINESTRING'",
+ ("'POLYGON ((0 0,0 5,5 0,0 0))'") -> "'POLYGON'",
+ ("'POINTM (1 2 3)'") -> "'POINTM'",
+ ("'LINESTRINGM (0 0 1, 0 90 1)'") -> "'LINESTRINGM'",
+ ("'POLYGONM ((0 0 1, 0 5 1, 5 0 1, 0 0 1))'") -> "'POLYGONM'"
+ )
+ for ((geom, expectedResult) <- geomTestCases) {
+ val df = sparkSession.sql(s"SELECT
GeometryType(ST_GeomFromText($geom)), " +
+ s"$expectedResult")
+ val actual = df.take(1)(0).get(0).asInstanceOf[String]
+ val expected = df.take(1)(0).get(1).asInstanceOf[String]
+ assertEquals(expected, actual)
+ }
+ }
+
}