You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sedona.apache.org by ji...@apache.org on 2022/10/02 18:33:58 UTC

[incubator-sedona] branch master updated: [SEDONA-166] Support type-safe dataframe style API (#693)

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/incubator-sedona.git


The following commit(s) were added to refs/heads/master by this push:
     new ff54bd3f [SEDONA-166] Support type-safe dataframe style API (#693)
ff54bd3f is described below

commit ff54bd3f1a8911fd81cc0372999a403c7c62fb47
Author: Douglas Dennis <do...@gmail.com>
AuthorDate: Sun Oct 2 11:33:53 2022 -0700

    [SEDONA-166] Support type-safe dataframe style API (#693)
---
 .gitignore                                         |    1 +
 docs/tutorial/sql-python.md                        |   58 ++
 docs/tutorial/sql.md                               |   24 +
 python/sedona/register/java_libs.py                |    4 +
 python/sedona/sql/dataframe_api.py                 |  140 +++
 python/sedona/sql/st_aggregates.py                 |   49 +
 python/sedona/sql/st_constructors.py               |  206 ++++
 python/sedona/sql/st_functions.py                  | 1025 ++++++++++++++++++++
 python/sedona/sql/st_predicates.py                 |  147 +++
 python/tests/sql/test_dataframe_api.py             |  383 ++++++++
 .../sql/sedona_sql/expressions/DataFrameAPI.scala  |   69 ++
 .../sql/sedona_sql/expressions/st_aggregates.scala |   55 ++
 .../sedona_sql/expressions/st_constructors.scala   |   68 ++
 .../sql/sedona_sql/expressions/st_functions.scala  |  251 +++++
 .../sql/sedona_sql/expressions/st_predicates.scala |   51 +
 .../apache/sedona/sql/dataFrameAPITestScala.scala  |  835 ++++++++++++++++
 16 files changed, 3366 insertions(+)

diff --git a/.gitignore b/.gitignore
index 8fb8f585..8f6bbd43 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,4 @@
 /.metals/
 /.vscode/
 .Rproj.user
+__pycache__
diff --git a/docs/tutorial/sql-python.md b/docs/tutorial/sql-python.md
index 9746a653..15c9c7ad 100644
--- a/docs/tutorial/sql-python.md
+++ b/docs/tutorial/sql-python.md
@@ -256,6 +256,64 @@ gdf.plot(
 <br>
 <br>
 
+### DataFrame Style API
+
+Sedona functions can be called used a DataFrame style API similar to PySpark's own functions.
+The functions are spread across four different modules: `sedona.sql.st_constructors`, `sedona.sql.st_functions`, `sedona.sql.st_predicates`, and `sedona.sql.st_aggregates`.
+All of the functions can take columns or strings as arguments and will return a column representing the sedona function call.
+This makes them integratable with `DataFrame.select`, `DataFrame.join`, and all of the PySpark functions found in the `pyspark.sql.functions` module.
+
+As an example of the flexibility:
+
+```python3
+from pyspark.sql import functions as f
+
+from sedona.sql import st_constructors as stc
+
+df = spark.sql("SELECT array(0.0, 1.0, 2.0) AS values")
+
+min_value = f.array_min("values")
+max_value = f.array_max("values")
+
+df = df.select(stc.ST_Point(min_value, max_value).alias("point"))
+```
+
+The above code will generate the following dataframe:
+```
++-----------+
+|point      |
++-----------+
+|POINT (0 2)|
++-----------+
+```
+
+Some functions will take native python values and infer them as literals.
+For example:
+
+```python3
+df = df.select(stc.ST_Point(1.0, 3.0).alias("point"))
+```
+
+This will generate a dataframe with a constant point in a column:
+```
++-----------+
+|point      |
++-----------+
+|POINT (1 3)|
++-----------+
+```
+
+For a description of what values a function may take please refer to their specific docstrings.
+
+The following rules are followed when passing values to the sedona functions:
+1. `Column` type arguments are passed straight through and are always accepted.
+1. `str` type arguments are always assumed to be names of columns and are wrapped in a `Column` to support that.
+If an actual string literal needs to be passed then it will need to be wrapped in a `Column` using `pyspark.sql.functions.lit`.
+1. Any other types of arguments are checked on a per function basis.
+Generally, arguments that could reasonably support a python native type are accepted and passed through.
+Check the specific docstring of the function to be sure.
+1. Shapely `Geometry` objects are not currently accepted in any of the functions.
+
 ## Creating Spark DataFrame based on shapely objects
 
 ### Supported Shapely objects
diff --git a/docs/tutorial/sql.md b/docs/tutorial/sql.md
index 4285c820..b4d0c586 100644
--- a/docs/tutorial/sql.md
+++ b/docs/tutorial/sql.md
@@ -360,3 +360,27 @@ val schema = StructType(Array(
 ))
 val joinResultDf = Adapter.toDf(joinResultPairRDD, schema, sparkSession)
 ```
+
+## DataFrame Style API
+
+Sedona functions can be used in a DataFrame style API similar to Spark functions.
+The following objects contain the exposed functions: `org.apache.spark.sql.sedona_sql.expressions.st_functions`, `org.apache.spark.sql.sedona_sql.expressions.st_constructors`, `org.apache.spark.sql.sedona_sql.expressions.st_predicates`, and `org.apache.spark.sql.sedona_sql.expressions.st_aggregates`.
+Every functions can take all `Column` arguments.
+Additionally, overloaded forms can commonly take a mix of `String` and other Scala types (such as `Double`) as arguments.
+In general the following rules apply (although check the documentation of specific functions for any exceptions):
+1. Every function returns a `Column` so that it can be used interchangeably with Spark functions as well as `DataFrame` methods such as `DataFrame.select` or `DataFrame.join`.
+1. Every function has a form that takes all `Column` arguments.
+These are the most versatile of the forms.
+1. Most functions have a form that takes a mix of `String` arguments with other Scala types.
+The exact mixture of argument types allowed is function specific.
+However, in these instances, all `String` arguments are assumed to be the names of columns and will be wrapped in a `Column` automatically.
+Non-`String` arguments are assumed to be literals that are passed to the sedona function. If you need to pass a `String` literal then you should use the all `Column` form of the sedona function and wrap the `String` literal in a `Column` with the `lit` Spark function.
+
+A short example of using this API (uses the `array_min` and `array_max` Spark functions):
+
+```Scala
+val values_df = spark.sql("SELECT array(0.0, 1.0, 2.0) AS values")
+val min_value = array_min("values")
+val max_value = array_max("values")
+val point_df = values_df.select(ST_Point(min_value, max_value).as("point"))
+```
diff --git a/python/sedona/register/java_libs.py b/python/sedona/register/java_libs.py
index f60eafb5..c93b59d9 100644
--- a/python/sedona/register/java_libs.py
+++ b/python/sedona/register/java_libs.py
@@ -48,6 +48,10 @@ class SedonaJvmLib(Enum):
     EnvelopeAdapter = "org.apache.sedona.python.wrapper.adapters.EnvelopeAdapter"
     PythonConverter = "org.apache.sedona.python.wrapper.adapters.PythonConverter"
     PythonRddToJavaRDDAdapter = "org.apache.sedona.python.wrapper.adapters.PythonRddToJavaRDDAdapter"
+    st_constructors = "org.apache.spark.sql.sedona_sql.expressions.st_constructors"
+    st_functions = "org.apache.spark.sql.sedona_sql.expressions.st_functions"
+    st_predicates = "org.apache.spark.sql.sedona_sql.expressions.st_predicates"
+    st_aggregates = "org.apache.spark.sql.sedona_sql.expressions.st_aggregates"
 
     @classmethod
     def from_str(cls, geo_lib: str) -> 'SedonaJvmLib':
diff --git a/python/sedona/sql/dataframe_api.py b/python/sedona/sql/dataframe_api.py
new file mode 100644
index 00000000..9a70934e
--- /dev/null
+++ b/python/sedona/sql/dataframe_api.py
@@ -0,0 +1,140 @@
+import functools
+import inspect
+import itertools
+from typing import (
+    Any,
+    Callable,
+    Iterable,
+    List,
+    Mapping,
+    Tuple,
+    Type,
+    Union,
+)
+import typing
+
+from pyspark.sql import SparkSession, Column, functions as f
+
+
+ColumnOrName = Union[Column, str]
+ColumnOrNameOrNumber = Union[Column, str, float, int]
+
+
+def _convert_argument_to_java_column(arg: Any) -> Column:
+    if isinstance(arg, Column):
+        return arg._jc
+    elif isinstance(arg, str):
+        return f.col(arg)._jc
+    elif isinstance(arg, Iterable):
+        return f.array(*[Column(_convert_argument_to_java_column(x)) for x in arg])._jc
+    else:
+        return f.lit(arg)._jc
+
+
+def call_sedona_function(object_name: str, function_name: str, args: Union[Any, Tuple[Any]]) -> Column:
+    spark = SparkSession.getActiveSession()
+    if spark is None:
+        raise ValueError("No active spark session was detected. Unable to call sedona function.")
+    
+    # apparently a Column is an Iterable so we need to check for it explicitely
+    if (not isinstance(args, Iterable)) or isinstance(args, str) or isinstance(args, Column):
+        args = [args]
+
+    args = map(_convert_argument_to_java_column, args)
+
+    jobject = getattr(spark._jvm, object_name)
+    jfunc = getattr(jobject, function_name)
+    
+    jc = jfunc(*args)
+    return Column(jc)
+
+
+def _get_type_list(annotated_type: Type) -> Tuple[Type, ...]:
+    """Convert a type annotation into a tuple of types.
+    For most types this will be a tuple with a single element, but
+    for Union a tuple with multiple elements will be returned.
+
+    :param annotated_type: Type annotation to convert a tuple.
+    :type annotated_type: Type
+    :return: Tuple of all types covered by the type annotation.
+    :rtype: Tuple[Type, ...]
+    """
+    # in 3.8 there is a much nicer way to do this with typing.get_origin
+    # we have to be a bit messy until we drop support for 3.7
+    if isinstance(annotated_type, typing._GenericAlias) and annotated_type.__origin__._name == "Union":
+        # again, there is a really nice method for this in 3.8: typing.get_args 
+        valid_types = annotated_type.__args__
+    else:
+        valid_types = (annotated_type,)
+    
+    return valid_types
+
+
+def _strip_extra_from_class_name(class_name):
+    return class_name[len("<class '"):-len("'>")].split(".")[-1]
+
+
+def _get_readable_name_for_type(type: Type) -> str:
+    """Get a human readable name for a type annotation used on a function's parameter.
+
+    :param type: Type annotation for a parameter.
+    :type type: Type
+    :return: Human readable name for the type annotation.
+    :rtype: str
+    """
+    if isinstance(type, typing._GenericAlias) and type.__origin__._name == "Union":
+        return f"Union[{', '.join((_strip_extra_from_class_name(str(x)) for x in type.__args__))}]"
+    return _strip_extra_from_class_name(str(type))
+
+
+def _get_bound_arguments(f: Callable, *args, **kwargs) -> Mapping[str, Any]:
+    """Bind the passed arguments to f with actual parameter names, including defaults.
+
+    :param f: Function to bind arguments for.
+    :type f: Callable
+    :return: Dictionary of parameter names to argument values.
+    :rtype: Mapping[str, Any]
+    """
+    f_signature = inspect.signature(f)
+    bound_args = f_signature.bind(*args, **kwargs)
+    bound_args.apply_defaults()
+    return bound_args
+
+
+def _check_bound_arguments(bound_args: Mapping[str, Any], type_annotations: List[Type], function_name: str) -> None:
+    """Check bound arguments against type annotations and raise a ValueError if any do not match.
+
+    :param bound_args: Bound arguments to check.
+    :type bound_args: Mapping[str, Any]
+    :param type_annotations: Type annotations to check bound_args against.
+    :type type_annotations: List[Type]
+    :param function_name: Name of the function that is being checked for, used in the exception if raised.
+    :type function_name: str
+    :raises ValueError: If a bound argument does not match the parameter type.
+    """
+    for bound_arg_name, bound_arg_value in bound_args.arguments.items():
+        annotated_type = type_annotations[bound_arg_name]
+        valid_type_list = _get_type_list(annotated_type)
+        if not any([isinstance(bound_arg_value, valid_type) for valid_type in valid_type_list]):
+            raise ValueError(f"Incorrect argument type: {bound_arg_name} for {function_name} should be {_get_readable_name_for_type(annotated_type)} but received {_strip_extra_from_class_name(str(type(bound_arg_value)))}.")
+
+
+def validate_argument_types(f: Callable) -> Callable:
+    """Validates types of arguments passed to a dataframe API style function.
+    Arguments will need to be either strings, columns, or match the typehints of f.
+    This function is meant to be used a decorator.
+
+    :param f: Function to validate for.
+    :type f: Callable
+    :return: f wrapped with type validation checks.
+    :rtype: Callable
+    """
+    def validated_function(*args, **kwargs) -> Column:
+        # all arguments are Columns or strings are always legal, so only check types when one of the arguments is not a column
+        if not all([isinstance(x, Column) or isinstance(x, str) for x in itertools.chain(args, kwargs.values())]):
+            bound_args = _get_bound_arguments(f, *args, **kwargs)
+            type_annotations = typing.get_type_hints(f)
+            _check_bound_arguments(bound_args, type_annotations, f.__name__)
+
+        return f(*args, **kwargs)
+    return functools.update_wrapper(validated_function, f)
diff --git a/python/sedona/sql/st_aggregates.py b/python/sedona/sql/st_aggregates.py
new file mode 100644
index 00000000..aae5501b
--- /dev/null
+++ b/python/sedona/sql/st_aggregates.py
@@ -0,0 +1,49 @@
+from functools import partial
+
+from pyspark.sql import Column
+
+from sedona.sql.dataframe_api import ColumnOrName, call_sedona_function, validate_argument_types
+
+_call_aggregate_function = partial(call_sedona_function, "st_aggregates")
+
+__all__ = [
+    "ST_Envelope_Aggr",
+    "ST_Intersection_Aggr",
+    "ST_Union_Aggr",
+]
+
+
+@validate_argument_types
+def ST_Envelope_Aggr(geometry: ColumnOrName) -> Column:
+    """Aggregate Function: Get the aggregate envelope of a geometry column.
+
+    :param geometry: Geometry column to aggregate.
+    :type geometry: ColumnOrName
+    :return: Geometry representing the aggregate envelope of the geometry column.
+    :rtype: Column
+    """
+    return _call_aggregate_function("ST_Envelope_Aggr", geometry)
+
+
+@validate_argument_types
+def ST_Intersection_Aggr(geometry: ColumnOrName) -> Column:
+    """Aggregate Function: Get the aggregate intersection of a geometry column.
+
+    :param geometry: Geometry column to aggregate.
+    :type geometry: ColumnOrName
+    :return: Geometry representing the aggregate intersection of the geometry column.
+    :rtype: Column
+    """
+    return _call_aggregate_function("ST_Intersection_Aggr", geometry)
+
+
+@validate_argument_types
+def ST_Union_Aggr(geometry: ColumnOrName) -> Column:
+    """Aggregate Function: Get the aggregate union of a geometry column.
+
+    :param geometry: Geometry column to aggregate.
+    :type geometry: ColumnOrName
+    :return: Geometry representing the aggregate union of the geometry column.
+    :rtype: Column
+    """
+    return _call_aggregate_function("ST_Union_Aggr", geometry)
diff --git a/python/sedona/sql/st_constructors.py b/python/sedona/sql/st_constructors.py
new file mode 100644
index 00000000..bbd2d03c
--- /dev/null
+++ b/python/sedona/sql/st_constructors.py
@@ -0,0 +1,206 @@
+from functools import partial
+from typing import Optional, Union
+
+from pyspark.sql import Column
+
+from sedona.sql.dataframe_api import ColumnOrName, ColumnOrNameOrNumber, call_sedona_function, validate_argument_types
+
+
+__all__ = [
+    "ST_GeomFromGeoHash",
+    "ST_GeomFromGeoJSON",
+    "ST_GeomFromGML",
+    "ST_GeomFromKML",
+    "ST_GeomFromText",
+    "ST_GeomFromWKB",
+    "ST_GeomFromWKT",
+    "ST_LineFromText",
+    "ST_LineStringFromText",
+    "ST_Point",
+    "ST_PointFromText",
+    "ST_PolygonFromEnvelope",
+    "ST_PolygonFromText",
+]
+
+
+_call_constructor_function = partial(call_sedona_function, "st_constructors")
+
+
+@validate_argument_types
+def ST_GeomFromGeoHash(geohash: ColumnOrName, precision: Union[ColumnOrName, int]) -> Column:
+    """Generate a geometry column from a geohash column at a specified precision.
+
+    :param geohash: Geohash string column to generate from. 
+    :type geohash: ColumnOrName
+    :param precision: Geohash precision to use, either an integer or an integer column.
+    :type precision: Union[ColumnOrName, int]
+    :return: Geometry column representing the supplied geohash and precision level.
+    :rtype: Column
+    """
+    return _call_constructor_function("ST_GeomFromGeoHash", (geohash, precision))
+
+
+@validate_argument_types
+def ST_GeomFromGeoJSON(geojson_string: ColumnOrName) -> Column:
+    """Generate a geometry column from a GeoJSON string column.
+
+    :param geojson_string: GeoJSON string column to generate from.
+    :type geojson_string: ColumnOrName
+    :return: Geometry column representing the GeoJSON string.
+    :rtype: Column
+    """
+    return _call_constructor_function("ST_GeomFromGeoJSON", geojson_string)
+
+
+@validate_argument_types
+def ST_GeomFromGML(gml_string: ColumnOrName) -> Column:
+    """Generate a geometry column from a Geography Markup Language (GML) string column.
+
+    :param gml_string: GML string column to generate from.
+    :type gml_string: ColumnOrName
+    :return: Geometry column representing the GML string.
+    :rtype: Column
+    """
+    return _call_constructor_function("ST_GeomFromGML", gml_string)
+
+
+@validate_argument_types
+def ST_GeomFromKML(kml_string: ColumnOrName) -> Column:
+    """Generate a geometry column from a KML string column.
+
+    :param kml_string: KML string column to generate from.
+    :type kml_string: ColumnOrName
+    :return: Geometry column representing the KML string.
+    :rtype: Column
+    """
+    return _call_constructor_function("ST_GeomFromKML", kml_string)
+
+
+@validate_argument_types
+def ST_GeomFromText(wkt: ColumnOrName) -> Column:
+    """Generate a geometry column from a Well-Known Text (WKT) string column.
+    This is an alias of ST_GeomFromWKT.
+
+    :param wkt: WKT string column to generate from.
+    :type wkt: ColumnOrName
+    :return: Geometry column representing the WKT string.
+    :rtype: Column
+    """
+    return _call_constructor_function("ST_GeomFromText", wkt)
+
+
+@validate_argument_types
+def ST_GeomFromWKB(wkb: ColumnOrName) -> Column:
+    """Generate a geometry column from a Well-Known Binary (WKB) binary column.
+
+    :param wkb: WKB binary column to generate from.
+    :type wkb: ColumnOrName
+    :return: Geometry column representing the WKB binary.
+    :rtype: Column
+    """
+    return _call_constructor_function("ST_GeomFromWKB", wkb)
+
+
+@validate_argument_types
+def ST_GeomFromWKT(wkt: ColumnOrName) -> Column:
+    """Generate a geometry column from a Well-Known Text (WKT) string column.
+    This is an alias of ST_GeomFromText.
+
+    :param wkt: WKT string column to generate from.
+    :type wkt: ColumnOrName
+    :return: Geometry column representing the WKT string.
+    :rtype: Column
+    """
+    return _call_constructor_function("ST_GeomFromWKT", wkt)
+
+
+@validate_argument_types
+def ST_LineFromText(wkt: ColumnOrName) -> Column:
+    """Generate linestring geometry from a linestring WKT representation.
+
+    :param wkt: Linestring WKT string column to generate from.
+    :type wkt: ColumnOrName
+    :return: Linestring geometry generated from the wkt column.
+    :rtype: Column
+    """
+    return _call_constructor_function("ST_LineFromText", wkt)
+
+
+@validate_argument_types
+def ST_LineStringFromText(coords: ColumnOrName, delimiter: ColumnOrName) -> Column:
+    """Generate a linestring geometry column from a list of coords seperated by a delimiter
+    in a string column.
+
+    :param coords: String column containing a list of coords.
+    :type coords: ColumnOrName
+    :param delimiter: Delimiter that separates each coordinate in the coords column, a string constant must be wrapped as a string literal (using pyspark.sql.functions.lit).
+    :type delimiter: ColumnOrName
+    :return: Linestring geometry column generated from the list of coordinates.
+    :rtype: Column
+    """
+    return _call_constructor_function("ST_LineStringFromText", (coords, delimiter))
+
+
+@validate_argument_types
+def ST_Point(x: ColumnOrNameOrNumber, y: ColumnOrNameOrNumber, z: Optional[ColumnOrNameOrNumber] = None) -> Column:
+    """Generate either a 2D or 3D point geometry column from numeric values.
+
+    :param x: Either a number or numeric column representing the X coordinate of a point.
+    :type x: ColumnOrNameOrNumber
+    :param y: Either a number or numeric column representing the Y coordinate of a point.
+    :type y: ColumnOrNameOrNumber
+    :param z: Either a number or numeric column representing the Z coordinate of a point, if None then a 2D point is generated, defaults to None
+    :type z: Optional[ColumnOrNameOrNumber], optional
+    :return: Point geometry column generated from the coordinate values.
+    :rtype: Column
+    """
+    args = (x, y) if z is None else (x, y, z)
+    return _call_constructor_function("ST_Point", args)
+
+
+@validate_argument_types
+def ST_PointFromText(coords: ColumnOrName, delimiter: ColumnOrName) -> Column:
+    """Generate a point geometry column from coordinates separated by a delimiter and stored
+    in a string column.
+
+    :param coords: String column with the stored coordinates.
+    :type coords: ColumnOrName
+    :param delimiter: Delimiter separating the coordinates, a string constant must be wrapped as a string literal (using pyspark.sql.functions.lit).
+    :type delimiter: ColumnOrName
+    :return: Point geometry column generated from the coordinates.
+    :rtype: Column
+    """
+    return _call_constructor_function("ST_PointFromText", (coords, delimiter))
+
+
+@validate_argument_types
+def ST_PolygonFromEnvelope(min_x: ColumnOrNameOrNumber, min_y: ColumnOrNameOrNumber, max_x: ColumnOrNameOrNumber, max_y: ColumnOrNameOrNumber) -> Column:
+    """Generate a polygon geometry column from the minimum and maximum coordinates of an envelope.
+
+    :param min_x: Minimum X coordinate for the envelope.
+    :type min_x: ColumnOrNameOrNumber
+    :param min_y: Minimum Y coordinate for the envelope.
+    :type min_y: ColumnOrNameOrNumber
+    :param max_x: Maximum X coordinate for the envelope.
+    :type max_x: ColumnOrNameOrNumber
+    :param max_y: Maximum Y coordinate for the envelope.
+    :type max_y: ColumnOrNameOrNumber
+    :return: Polygon geometry column representing the envelope described by the coordinate bounds.
+    :rtype: Column
+    """
+    return _call_constructor_function("ST_PolygonFromEnvelope", (min_x, min_y, max_x, max_y))
+
+
+@validate_argument_types
+def ST_PolygonFromText(coords: ColumnOrName, delimiter: ColumnOrName) -> Column: 
+    """Generate a polygon from a list of coordinates separated by a delimiter stored
+    in a string column.
+
+    :param coords: String column containing the coordinates.
+    :type coords: ColumnOrName
+    :param delimiter: Delimiter separating the coordinates, a string constant must be wrapped as a string literal (using pyspark.sql.functions.lit). 
+    :type delimiter: ColumnOrName
+    :return: Polygon geometry column generated from the list of coordinates.
+    :rtype: Column
+    """
+    return _call_constructor_function("ST_PolygonFromText", (coords, delimiter))
diff --git a/python/sedona/sql/st_functions.py b/python/sedona/sql/st_functions.py
new file mode 100644
index 00000000..4614d3ab
--- /dev/null
+++ b/python/sedona/sql/st_functions.py
@@ -0,0 +1,1025 @@
+from functools import partial
+from typing import Optional, Union
+
+from pyspark.sql import Column
+
+from sedona.sql.dataframe_api import call_sedona_function, ColumnOrName, ColumnOrNameOrNumber, validate_argument_types
+
+
+__all__ = [
+    "ST_3DDistance",
+    "ST_AddPoint",
+    "ST_Area",
+    "ST_AsBinary",
+    "ST_AsEWKB",
+    "ST_AsEWKT",
+    "ST_AsGeoJSON",
+    "ST_AsGML",
+    "ST_AsKML",
+    "ST_AsText",
+    "ST_Azimuth",
+    "ST_Boundary",
+    "ST_Buffer",
+    "ST_BuildArea",
+    "ST_Centroid",
+    "ST_Collect",
+    "ST_CollectionExtract",
+    "ST_ConvexHull",
+    "ST_Difference",
+    "ST_Distance",
+    "ST_Dump",
+    "ST_DumpPoints",
+    "ST_EndPoint",
+    "ST_Envelope",
+    "ST_ExteriorRing",
+    "ST_FlipCoordinates",
+    "ST_Force_2D",
+    "ST_GeoHash",
+    "ST_GeometryN",
+    "ST_GeometryType",
+    "ST_InteriorRingN",
+    "ST_Intersection",
+    "ST_IsClosed",
+    "ST_IsEmpty",
+    "ST_IsRing",
+    "ST_IsSimple",
+    "ST_IsValid",
+    "ST_Length",
+    "ST_LineInterpolatePoint",
+    "ST_LineMerge",
+    "ST_LineSubstring",
+    "ST_MakePolygon",
+    "ST_MakeValid",
+    "ST_MinimumBoundingCircle",
+    "ST_MinimumBoundingRadius",
+    "ST_Multi",
+    "ST_Normalize",
+    "ST_NPoints",
+    "ST_NumGeometries",
+    "ST_NumInteriorRings",
+    "ST_PointN",
+    "ST_PointOnSurface",
+    "ST_PrecisionReduce",
+    "ST_RemovePoint",
+    "ST_Reverse",
+    "ST_SetSRID",
+    "ST_SRID",
+    "ST_StartPoint",
+    "ST_SubDivide",
+    "ST_SubDivideExplode",
+    "ST_SimplifyPreserveTopology",
+    "ST_SymDifference",
+    "ST_Transform",
+    "ST_Union",
+    "ST_X",
+    "ST_XMax",
+    "ST_XMin",
+    "ST_Y",
+    "ST_YMax",
+    "ST_YMin",
+    "ST_Z",
+]
+
+
+_call_st_function = partial(call_sedona_function, "st_functions")
+    
+
+@validate_argument_types
+def ST_3DDistance(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Calculate the 3-dimensional minimum Cartesian distance between two geometry columns.
+
+    :param a: One geometry column to use in the calculation.
+    :type a: ColumnOrName
+    :param b: Other geometry column to use in the calculation.
+    :type b: ColumnOrName
+    :return: Minimum cartesian distance between a and b as a double column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_3DDistance", (a, b))
+
+
+@validate_argument_types
+def ST_AddPoint(line_string: ColumnOrName, point: ColumnOrName, index: Optional[Union[ColumnOrName, int]] = None) -> Column:
+    """Add a point to either the end of a linestring or a specified index.
+    If index is not provided then point will be added to the end of line_string.
+
+    :param line_string: Linestring geometry column to add point to.
+    :type line_string: ColumnOrName
+    :param point: Point geometry column to add to line_string.
+    :type point: ColumnOrName
+    :param index: 0-based index to insert point at in line_string, if None then point is appended to the end of line_string, defaults to None
+    :type index: Optional[Union[ColumnOrName, int]], optional
+    :return: Linestring geometry column with point added.
+    :rtype: Column
+    """
+    args = (line_string, point) if index is None else (line_string, point, index)
+    return _call_st_function("ST_AddPoint", args)
+
+
+@validate_argument_types
+def ST_Area(geometry: ColumnOrName) -> Column:
+    """Calculate the area of a geometry.
+
+    :param geometry: Geometry column to calculate the area of.
+    :type geometry: ColumnOrName
+    :return: Area of geometry as a double column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Area", geometry)
+
+
+@validate_argument_types
+def ST_AsBinary(geometry: ColumnOrName) -> Column:
+    """Generate the Well-Known Binary (WKB) representation of a geometry.
+
+    :param geometry: Geometry column to generate WKB for.
+    :type geometry: ColumnOrName
+    :return: Well-Known Binary representation of geometry as a binary column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_AsBinary", geometry)
+
+
+@validate_argument_types
+def ST_AsEWKB(geometry: ColumnOrName) -> Column:
+    """Generate the Extended Well-Known Binary representation of a geometry.
+    As opposed to WKB, EWKB will include the SRID of the geometry.
+
+    :param geometry: Geometry to generate EWKB for.
+    :type geometry: ColumnOrName
+    :return: Extended Well-Known Binary representation of geometry as a binary column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_AsEWKB", geometry)
+
+
+@validate_argument_types
+def ST_AsEWKT(geometry: ColumnOrName) -> Column:
+    """Generate the Extended Well-Known Text representation of a geometry column.
+    As opposed to WKT, EWKT will include the SRID of the geometry.
+
+    :param geometry: Geometry column to generate EWKT for.
+    :type geometry: ColumnOrName
+    :return: Extended Well-Known Text represenation of geometry as a string column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_AsEWKT", geometry)
+
+
+@validate_argument_types
+def ST_AsGeoJSON(geometry: ColumnOrName) -> Column:
+    """Generate the GeoJSON style represenation of a geometry column.
+
+    :param geometry: Geometry column to generate GeoJSON for.
+    :type geometry: ColumnOrName
+    :return: GeoJSON representation of geometry as a string column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_AsGeoJSON", geometry)
+
+
+@validate_argument_types
+def ST_AsGML(geometry: ColumnOrName) -> Column:
+    """Generate the Geography Markup Language (GML) representation of a
+    geometry column.
+
+    :param geometry: Geometry column to generate GML for.
+    :type geometry: ColumnOrName
+    :return: GML representation of geometry as a string column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_AsGML", geometry)
+
+
+@validate_argument_types
+def ST_AsKML(geometry: ColumnOrName) -> Column:
+    """Generate the KML representation of a geometry column.
+
+    :param geometry: Geometry column to generate KML for.
+    :type geometry: ColumnOrName
+    :return: KML representation of geometry as a string column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_AsKML", geometry)
+
+
+@validate_argument_types
+def ST_AsText(geometry: ColumnOrName) -> Column:
+    """Generate the Well-Known Text (WKT) representation of a geometry column.
+
+    :param geometry: Geometry column to generate WKT for.
+    :type geometry: ColumnOrName
+    :return: WKT representation of geometry as a string column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_AsText", geometry)
+
+
+@validate_argument_types
+def ST_Azimuth(point_a: ColumnOrName, point_b: ColumnOrName) -> Column:
+    """Calculate the azimuth for two point columns in radians.
+
+    :param point_a: One point geometry column to use for the calculation.
+    :type point_a: ColumnOrName
+    :param point_b: Other point geometry column to use for the calculation.
+    :type point_b: ColumnOrName
+    :return: Azimuth for point_a and point_b in radians as a double column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Azimuth", (point_a, point_b))
+
+
+@validate_argument_types
+def ST_Boundary(geometry: ColumnOrName) -> Column:
+    """Calculate the closure of the combinatorial boundary of a geometry column.
+
+    :param geometry: Geometry column to calculate the boundary for.
+    :type geometry: ColumnOrName
+    :return: Boundary of the input geometry as a geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Boundary", geometry)
+
+
+@validate_argument_types
+def ST_Buffer(geometry: ColumnOrName, buffer: ColumnOrNameOrNumber) -> Column:
+    """Calculate a geometry that represents all points whose distance from the 
+    input geometry column is equal to or less than a given amount.
+
+    :param geometry: Input geometry column to buffer.
+    :type geometry: ColumnOrName
+    :param buffer: Either a column or value for the amount to buffer the input geometry by.
+    :type buffer: ColumnOrNameOrNumber
+    :return: Buffered geometry as a geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Buffer", (geometry, buffer))
+
+
+@validate_argument_types
+def ST_BuildArea(geometry: ColumnOrName) -> Column:
+    """Generate a geometry described by the consituent linework of the input
+    geometry column.
+
+    :param geometry: Linestring or multilinestring geometry column to use as input.
+    :type geometry: ColumnOrName
+    :return: Area formed by geometry as a geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_BuildArea", geometry)
+
+
+@validate_argument_types
+def ST_Centroid(geometry: ColumnOrName) -> Column:
+    """Calculate the centroid of the given geometry column.
+
+    :param geometry: Geometry column to calculate a centroid for.
+    :type geometry: ColumnOrName
+    :return: Centroid of geometry as a point geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Centroid", geometry)
+
+
+@validate_argument_types
+def ST_Collect(*geometries: ColumnOrName) -> Column:
+    """Collect multiple geometry columns or an array of geometries into a single
+    multi-geometry or geometry collection.
+
+    :param geometries: Either a single geometry column that holds an array of geometries or multiple geometry columns.
+    :return: If the types of geometries are homogenous then a multi-geometry is returned, otherwise a geometry collection is returned.
+    :rtype: Column
+    """
+    if len(geometries) == 1:
+        return _call_st_function("ST_Collect", geometries)
+    else:
+        return _call_st_function("ST_Collect", [geometries])
+
+
+@validate_argument_types
+def ST_CollectionExtract(collection: ColumnOrName, geom_type: Optional[Union[ColumnOrName, int]] = None) -> Column:
+    """Extract a specific type of geometry from a geometry collection column
+    as a multi-geometry column.
+
+    :param collection: Column for the geometry collection.
+    :type collection: ColumnOrName
+    :param geom_type: Type of geometry to extract where 1 is point, 2 is linestring, and 3 is polygon, if None then the highest dimension geometry is extracted, defaults to None
+    :type geom_type: Optional[Union[ColumnOrName, int]], optional
+    :return: Multi-geometry column containing all geometry from collection of the selected type.
+    :rtype: Column
+    """
+    args = (collection,) if geom_type is None else (collection, geom_type)
+    return _call_st_function("ST_CollectionExtract", args)
+
+
+@validate_argument_types
+def ST_ConvexHull(geometry: ColumnOrName) -> Column:
+    """Generate the convex hull of a geometry column.
+
+    :param geometry: Geometry column to generate a convex hull for.
+    :type geometry: ColumnOrName
+    :return: Convex hull of geometry as a geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_ConvexHull", geometry)
+
+
+@validate_argument_types
+def ST_Difference(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Calculate the difference of two geometry columns. This difference
+    is not symmetric. It only returns the part of geometry a that is not
+    in b.
+
+    :param a: Geometry column to use in the calculation.
+    :type a: ColumnOrName
+    :param b: Geometry column to subtract from geometry column a.
+    :type b: ColumnOrName
+    :return: Part of geometry a that is not in b as a geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Difference", (a, b))
+
+
+@validate_argument_types
+def ST_Distance(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Calculate the minimum cartesian distance between two geometry columns.
+
+    :param a: Geometry column to use in the calculation.
+    :type a: ColumnOrName
+    :param b: Other geometry column to use in the calculation.
+    :type b: ColumnOrName
+    :return: Two-dimensional cartesian distance between a and b as a double column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Distance", (a, b))
+
+
+@validate_argument_types
+def ST_Dump(geometry: ColumnOrName) -> Column:
+    """Returns an array of geometries that are members of a multi-geometry
+    or geometry collection column. If the input geometry is a regular geometry
+    then the geometry is returned inside of a single element array. 
+
+    :param geometry: Geometry column to dump.
+    :type geometry: ColumnOrName
+    :return: Array of geometries column comprised of the members of geometry. 
+    :rtype: Column
+    """
+    return _call_st_function("ST_Dump", geometry)
+
+
+@validate_argument_types
+def ST_DumpPoints(geometry: ColumnOrName) -> Column:
+    """Return the list of points of a geometry column. Specifically, return
+    the vertices of the input geometry as an array.
+
+    :param geometry: Geometry column to dump the points of.
+    :type geometry: ColumnOrName
+    :return: Array of point geometry column comprised of the vertices of geometry.
+    :rtype: Column
+    """
+    return _call_st_function("ST_DumpPoints", geometry)
+
+
+@validate_argument_types
+def ST_EndPoint(line_string: ColumnOrName) -> Column:
+    """Return the last point of a linestring geometry column.
+
+    :param line_string: Linestring geometry column to get the end point of.
+    :type line_string: ColumnOrName
+    :return: The last point of the linestring geometry column as a point geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_EndPoint", line_string)
+
+
+@validate_argument_types
+def ST_Envelope(geometry: ColumnOrName) -> Column:
+    """Calculate the envelope boundary of a geometry column.
+
+    :param geometry: Geometry column to calculate the envelope of.
+    :type geometry: ColumnOrName
+    :return: Envelope of geometry as a geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Envelope", geometry)
+
+
+@validate_argument_types
+def ST_ExteriorRing(polygon: ColumnOrName) -> Column:
+    """Get a linestring representing the exterior ring of a polygon geometry
+    column.
+
+    :param polygon: Polygon geometry column to get the exterior ring of.
+    :type polygon: ColumnOrName
+    :return: Exterior ring of polygon as a linestring geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_ExteriorRing", polygon)
+
+
+@validate_argument_types
+def ST_FlipCoordinates(geometry: ColumnOrName) -> Column:
+    """Flip the X and Y coordinates of a geometry column.
+
+    :param geometry: Geometry column to flip coordinates for.
+    :type geometry: ColumnOrName
+    :return: Geometry column identical to geometry except with flipped coordinates.
+    :rtype: Column
+    """
+    return _call_st_function("ST_FlipCoordinates", geometry)
+
+
+@validate_argument_types
+def ST_Force_2D(geometry: ColumnOrName) -> Column:
+    """Force the geometry column to only output two dimensional representations.
+
+    :param geometry: Geometry column to force to be 2D.
+    :type geometry: ColumnOrName
+    :return: Geometry column identical to geometry except with only X and Y coordinates.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Force_2D", geometry)
+
+
+@validate_argument_types
+def ST_GeoHash(geometry: ColumnOrName, precision: Union[ColumnOrName, int]) -> Column:
+    """Return the geohash of a geometry column at a given precision level.
+
+    :param geometry: Geometry column to hash.
+    :type geometry: ColumnOrName
+    :param precision: Precision level to hash geometry at, given as an integer or an integer column.
+    :type precision: Union[ColumnOrName, int]
+    :return: Geohash of geometry as a string column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_GeoHash", (geometry, precision))
+
+
+@validate_argument_types
+def ST_GeometryN(multi_geometry: ColumnOrName, n: Union[ColumnOrName, int]) -> Column:
+    """Return the geometry at index n (0-th based) of a multi-geometry column.
+
+    :param multi_geometry: Multi-geometry column to get from.
+    :type multi_geometry: ColumnOrName
+    :param n: Index to select, given as an integer or integer column, 0-th based index, returns null if index is greater than maximum index.
+    :type n: Union[ColumnOrName, int]
+    :return: Geometry located at index n in multi_geometry as a geometry column.
+    :rtype: Column
+    :raises ValueError: If 
+    """
+    if isinstance(n, int) and n < 0:
+        raise ValueError(f"Index n for ST_GeometryN must by >= 0: {n} < 0")
+    return _call_st_function("ST_GeometryN", (multi_geometry, n))
+
+
+@validate_argument_types
+def ST_GeometryType(geometry: ColumnOrName) -> Column:
+    """Return the type of geometry in a given geometry column.
+
+    :param geometry: Geometry column to find the type for.
+    :type geometry: ColumnOrName
+    :return: Type of geometry as a string column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_GeometryType", geometry)
+
+
+@validate_argument_types
+def ST_InteriorRingN(polygon: ColumnOrName, n: Union[ColumnOrName, int]) -> Column:
+    """Return the index n (0-th based) interior ring of a polygon geometry column.
+
+    :param polygon: Polygon geometry column to get an interior ring from.
+    :type polygon: ColumnOrName
+    :param n: Index of interior ring to return as either an integer or integer column, 0-th based.
+    :type n: Union[ColumnOrName, int]
+    :raises ValueError: If n is an integer and less than 0.
+    :return: Interior ring at index n as a linestring geometry column or null if n is greater than maximum index 
+    :rtype: Column
+    """
+    if isinstance(n, int) and n < 0:
+        raise ValueError(f"Index n for ST_InteriorRingN must by >= 0: {n} < 0")
+    return _call_st_function("ST_InteriorRingN", (polygon, n))
+
+
+@validate_argument_types
+def ST_Intersection(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Calculate the intersection of two geometry columns.
+
+    :param a: One geometry column to use in the calculation.
+    :type a: ColumnOrName
+    :param b: Other geometry column to use in the calculation.
+    :type b: ColumnOrName
+    :return: Intersection of a and b as a geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Intersection", (a, b))
+
+
+@validate_argument_types
+def ST_IsClosed(geometry: ColumnOrName) -> Column:
+    """Check if the linestring in a geometry column is closed (its end point is equal to its start point).
+
+    :param geometry: Linestring geometry column to check.
+    :type geometry: ColumnOrName
+    :return: True if geometry is closed and False otherwise as a boolean column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_IsClosed", geometry)
+
+
+@validate_argument_types
+def ST_IsEmpty(geometry: ColumnOrName) -> Column:
+    """Check if the geometry in a geometry column is an empty geometry.
+
+    :param geometry: Geometry column to check.
+    :type geometry: ColumnOrName
+    :return: True if the geometry is empty and False otherwise as a boolean column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_IsEmpty", geometry)
+
+
+@validate_argument_types
+def ST_IsRing(line_string: ColumnOrName) -> Column:
+    """Check if a linestring geometry is both closed and simple.
+
+    :param line_string: Linestring geometry column to check.
+    :type line_string: ColumnOrName
+    :return: True if the linestring is both closed and simple and False otherwise as a boolean column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_IsRing", line_string)
+
+
+@validate_argument_types
+def ST_IsSimple(geometry: ColumnOrName) -> Column:
+    """Check if a geometry's only intersections are at boundary points.
+
+    :param geometry: Geometry column to check in.
+    :type geometry: ColumnOrName
+    :return: True if geometry is simple and False otherwise as a boolean column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_IsSimple", geometry)
+
+
+@validate_argument_types
+def ST_IsValid(geometry: ColumnOrName) -> Column:
+    """Check if a geometry is well formed.
+
+    :param geometry: Geometry column to check in.
+    :type geometry: ColumnOrName
+    :return: True if geometry is well formed and False otherwise as a boolean column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_IsValid", geometry)
+
+
+@validate_argument_types
+def ST_Length(geometry: ColumnOrName) -> Column:
+    """Calculate the length of a linestring geometry.
+
+    :param geometry: Linestring geometry column to calculate length for.
+    :type geometry: ColumnOrName
+    :return: Length of geometry as a double column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Length", geometry)
+
+
+@validate_argument_types
+def ST_LineInterpolatePoint(geometry: ColumnOrName, fraction: ColumnOrNameOrNumber) -> Column:
+    """Calculate a point that is interpolated along a linestring.
+
+    :param geometry: Linestring geometry column to interpolate from.
+    :type geometry: ColumnOrName
+    :param fraction: Fraction of total length along geometry to generate a point for.
+    :type fraction: ColumnOrNameOrNumber
+    :return: Interpolated point as a point geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_LineInterpolatePoint", (geometry, fraction))
+
+
+@validate_argument_types
+def ST_LineMerge(multi_line_string: ColumnOrName) -> Column:
+    """Sew together the constituent line work of a multilinestring into a
+    single linestring.
+
+    :param multi_line_string: Multilinestring geometry column to merge.
+    :type multi_line_string: ColumnOrName
+    :return: Linestring geometry column resulting from the merger of multi_line_string.
+    :rtype: Column
+    """
+    return _call_st_function("ST_LineMerge", multi_line_string)
+
+
+@validate_argument_types
+def ST_LineSubstring(line_string: ColumnOrName, start_fraction: ColumnOrNameOrNumber, end_fraction: ColumnOrNameOrNumber) -> Column:
+    """Generate a substring of a linestring geometry column.
+
+    :param line_string: Linestring geometry column to generate from.
+    :type line_string: ColumnOrName
+    :param start_fraction: Fraction of linestring to start from for the resulting substring as either a number or numeric column.
+    :type start_fraction: ColumnOrNameOrNumber
+    :param end_fraction: Fraction of linestring to end at for the resulting substring as either a number or numeric column.
+    :type end_fraction: ColumnOrNameOrNumber
+    :return: Smaller linestring that runs from start_fraction to end_fraction of line_string as a linestring geometry column, will be null if either start_fraction or end_fraction are outside the interval [0, 1].
+    :rtype: Column
+    """
+    return _call_st_function("ST_LineSubstring", (line_string, start_fraction, end_fraction))
+
+
+@validate_argument_types
+def ST_MakePolygon(line_string: ColumnOrName, holes: Optional[ColumnOrName] = None) -> Column:
+    """Create a polygon geometry from a linestring describing the exterior ring as well as an array of linestrings describing holes.
+
+    :param line_string: Closed linestring geometry column that describes the exterior ring of the polygon.
+    :type line_string: ColumnOrName
+    :param holes: Optional column for an array of closed geometry columns that describe holes in the polygon, defaults to None.
+    :type holes: Optional[ColumnOrName], optional
+    :return: Polygon geometry column created from the input linestrings.
+    :rtype: Column
+    """
+    args = (line_string,) if holes is None else (line_string, holes)
+    return _call_st_function("ST_MakePolygon", args)
+
+
+@validate_argument_types
+def ST_MakeValid(geometry: ColumnOrName, keep_collapsed: Optional[Union[ColumnOrName, bool]] = None) -> Column:
+    """Convert an invalid geometry in a geometry column into a valid geometry.
+
+    :param geometry: Geometry column that contains the invalid geometry.
+    :type geometry: ColumnOrName
+    :param keep_collapsed: If True then collapsed geometries are converted to empty geometries, otherwise they will be converted to valid geometries of a lower dimension, if None then the default value of False is used, defaults to None
+    :type keep_collapsed: Optional[Union[ColumnOrName, bool]], optional
+    :return: Geometry column that contains valid versions of the original geometry.
+    :rtype: Column
+    """
+    args = (geometry,) if keep_collapsed is None else (geometry, keep_collapsed)
+    return _call_st_function("ST_MakeValid", args)
+
+
+@validate_argument_types
+def ST_MinimumBoundingCircle(geometry: ColumnOrName, quadrant_segments: Optional[Union[ColumnOrName, int]] = None) -> Column:
+    """Generate the minimum bounding circle that contains a geometry.
+
+    :param geometry: Geometry column to generate minimum bounding circles for.
+    :type geometry: ColumnOrName
+    :param quadrant_segments: Number of quadrant segments to use, if None then use a default value, defaults to None
+    :type quadrant_segments: Optional[Union[ColumnOrName, int]], optional
+    :return: Geometry column that contains the minimum bounding circles.
+    :rtype: Column
+    """
+    args = (geometry,) if quadrant_segments is None else (geometry, quadrant_segments)
+    return _call_st_function("ST_MinimumBoundingCircle", args)
+
+
+@validate_argument_types
+def ST_MinimumBoundingRadius(geometry: ColumnOrName) -> Column:
+    """Calculate the minimum bounding radius from the centroid of a geometry that will contain it.
+
+    :param geometry: Geometry column to generate minimum bounding radii for. 
+    :type geometry: ColumnOrName
+    :return: Struct column with a center field containing point geometry for the center of geometry and a radius field with a double value for the bounding radius.
+    :rtype: Column
+    """
+    return _call_st_function("ST_MinimumBoundingRadius", geometry)
+
+
+@validate_argument_types
+def ST_Multi(geometry: ColumnOrName) -> Column:
+    """Convert the geometry column into a multi-geometry column.
+
+    :param geometry: Geometry column to convert.
+    :type geometry: ColumnOrName
+    :return: Multi-geometry form of geometry as a geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Multi", geometry)
+
+
+@validate_argument_types
+def ST_Normalize(geometry: ColumnOrName) -> Column:
+    """Convert geometry in a geometry column to a canonical form.
+
+    :param geometry: Geometry to convert.
+    :type geometry: ColumnOrName
+    :return: Geometry with points ordered in a canonical way as a geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Normalize", geometry)
+
+
+@validate_argument_types
+def ST_NPoints(geometry: ColumnOrName) -> Column:
+    """Return the number of points contained in a geometry.
+
+    :param geometry: Geometry column to return for.
+    :type geometry: ColumnOrName
+    :return: Number of points in a geometry column as an integer column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_NPoints", geometry)
+
+
+@validate_argument_types
+def ST_NumGeometries(geometry: ColumnOrName) -> Column:
+    """Return the number of geometries contained in a multi-geometry.
+
+    :param geometry: Multi-geometry column to return for.
+    :type geometry: ColumnOrName
+    :return: Number of geometries contained in a multi-geometry column as an integer column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_NumGeometries", geometry)
+
+
+@validate_argument_types
+def ST_NumInteriorRings(geometry: ColumnOrName) -> Column:
+    """Return the number of interior rings contained in a polygon geometry.
+
+    :param geometry: Polygon geometry column to return for.
+    :type geometry: ColumnOrName
+    :return: Number of interior rings polygons contain as an integer column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_NumInteriorRings", geometry)
+
+
+@validate_argument_types
+def ST_PointN(geometry: ColumnOrName, n: Union[ColumnOrName, int]) -> Column:
+    """Get the n-th point (starts at 1) for a geometry.
+
+    :param geometry: Geometry column to get the point from.
+    :type geometry: ColumnOrName
+    :param n: Index for the point to return, 1-based, negative values start from the end so -1 is the last point, values that are out of bounds return null.
+    :type n: Union[ColumnOrName, int]
+    :return: n-th point from the geometry as a point geometry column, or null if index is out of bounds.
+    :rtype: Column
+    """
+    return _call_st_function("ST_PointN", (geometry, n))
+
+
+@validate_argument_types
+def ST_PointOnSurface(geometry: ColumnOrName) -> Column:
+    """Get a point that is guaranteed to lie on the surface.
+
+    :param geometry: Geometry column containing the Surface to get a point from.
+    :type geometry: ColumnOrName
+    :return: Point that lies on geometry as a point geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_PointOnSurface", geometry)
+
+
+@validate_argument_types
+def ST_PrecisionReduce(geometry: ColumnOrName, precision: Union[ColumnOrName, int]) -> Column:
+    """Reduce the precision of the coordinates in geometry to a specified number of decimal places.
+
+    :param geometry: Geometry to reduce the precision of.
+    :type geometry: ColumnOrName
+    :param precision: Number of decimal places to reduce the precision to as either an integer or integer column, 0 reduces precision to whole numbers.
+    :type precision: Union[ColumnOrName, int]
+    :return: Geometry with precision reduced to the indicated number of decimal places as a geometry column, empty geometry if an invalid precision is passed.
+    :rtype: Column
+    """
+    return _call_st_function("ST_PrecisionReduce", (geometry, precision))
+
+
+@validate_argument_types
+def ST_RemovePoint(line_string: ColumnOrName, index: Union[ColumnOrName, int]) -> Column:
+    """Remove the specified point (0-th based) for a linestring geometry column.
+
+    :param line_string: Linestring geometry column to remove the point from.
+    :type line_string: ColumnOrName
+    :param index: Index for the point to remove as either an integer or an integer column, 0-th based, negative numbers are ignored.
+    :type index: Union[ColumnOrName, int]
+    :return: Linestring geometry column with the specified point removed, or null if the index is out of bounds.
+    :rtype: Column
+    """
+    return _call_st_function("ST_RemovePoint", (line_string, index))
+
+
+@validate_argument_types
+def ST_Reverse(geometry: ColumnOrName) -> Column:
+    """Reverse the points for the geometry.
+
+    :param geometry: Geometry column to reverse points for.
+    :type geometry: ColumnOrName
+    :return: Geometry with points in reverse order compared to the original.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Reverse", geometry)
+
+
+@validate_argument_types
+def ST_SetSRID(geometry: ColumnOrName, srid: Union[ColumnOrName, int]) -> Column:
+    """Set the SRID for geometry.
+
+    :param geometry: Geometry column to set SRID for.
+    :type geometry: ColumnOrName
+    :param srid: SRID to set as either an integer or an integer column.
+    :type srid: Union[ColumnOrName, int]
+    :return: Geometry column with SRID set to srid.
+    :rtype: Column
+    """
+    return _call_st_function("ST_SetSRID", (geometry, srid))
+
+
+@validate_argument_types
+def ST_SRID(geometry: ColumnOrName) -> Column:
+    """Get the SRID of geometry.
+
+    :param geometry: Geometry column to get SRID from.
+    :type geometry: ColumnOrName
+    :return: SRID of geometry in the geometry column as an integer column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_SRID", geometry)
+
+
+@validate_argument_types
+def ST_StartPoint(line_string: ColumnOrName) -> Column:
+    """Get the first point from a linestring.
+
+    :param line_string: Linestring geometry column to get the first points for.
+    :type line_string: ColumnOrName
+    :return: First of the linestring geometry as a point geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_StartPoint", line_string)
+
+
+@validate_argument_types
+def ST_SubDivide(geometry: ColumnOrName, max_vertices: Union[ColumnOrName, int]) -> Column:
+    """Subdivide a geometry into an array of geometries with at maximum number of vertices in each.
+
+    :param geometry: Geometry column to subdivide.
+    :type geometry: ColumnOrName
+    :param max_vertices: Maximum number of vertices to have in each subdivision.
+    :type max_vertices: Union[ColumnOrName, int]
+    :return: Array of geometries that represent the subdivision of the original geometry.
+    :rtype: Column
+    """
+    return _call_st_function("ST_SubDivide", (geometry, max_vertices))
+
+
+@validate_argument_types
+def ST_SubDivideExplode(geometry: ColumnOrName, max_vertices: Union[ColumnOrName, int]) -> Column:
+    """Same as ST_SubDivide except also explode the generated array into multiple rows.
+
+    :param geometry: Geometry column to subdivide.
+    :type geometry: ColumnOrName
+    :param max_vertices: Maximum number of vertices to have in each subdivision.
+    :type max_vertices: Union[ColumnOrName, int]
+    :return: Individual geometries exploded from the returned array of ST_SubDivide.
+    :rtype: Column
+    """
+    return _call_st_function("ST_SubDivideExplode", (geometry, max_vertices))
+
+
+@validate_argument_types
+def ST_SimplifyPreserveTopology(geometry: ColumnOrName, distance_tolerance: ColumnOrNameOrNumber) -> Column:
+    """Simplify a geometry within a specified tolerance while preserving topological relationships.
+
+    :param geometry: Geometry column to simplify.
+    :type geometry: ColumnOrName
+    :param distance_tolerance: Tolerance for merging points together to simplify the geometry as either a number or numeric column.
+    :type distance_tolerance: ColumnOrNameOrNumber
+    :return: Simplified geometry as a geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_SimplifyPreserveTopology", (geometry, distance_tolerance))
+
+
+@validate_argument_types
+def ST_SymDifference(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Calculate the symmetric difference of two geometries (the regions that are only in one of them).
+
+    :param a: One geometry column to use.
+    :type a: ColumnOrName
+    :param b: Other geometry column to use.
+    :type b: ColumnOrName
+    :return: Geometry representing the symmetric difference of a and b as a geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_SymDifference", (a, b))
+
+
+@validate_argument_types
+def ST_Transform(geometry: ColumnOrName, source_crs: ColumnOrName, target_crs: ColumnOrName, disable_error: Optional[Union[ColumnOrName, bool]] = None) -> Column:
+    """Convert a geometry from one coordinate system to another coordinate system.
+
+    :param geometry: Geometry column to convert.
+    :type geometry: ColumnOrName
+    :param source_crs: Original coordinate system for geometry as a string, a string constant must be wrapped as a string literal (using pyspark.sql.functions.lit).
+    :type source_crs: ColumnOrName
+    :param target_crs: Coordinate system geometry will be converted to as a string, a string constant must be wrapped as a string literal (using pyspark.sql.functions.lit).
+    :type target_crs: ColumnOrName
+    :param disable_error: Whether to disable the error "Bursa wolf parameters required", defaults to None
+    :type disable_error: Optional[Union[ColumnOrName, bool]], optional
+    :return: Geometry converted to the target coordinate system as an 
+    :rtype: Column
+    """
+    args = (geometry, source_crs, target_crs) if disable_error is None else (geometry, source_crs, target_crs, disable_error)
+    return _call_st_function("ST_Transform", args)
+
+
+@validate_argument_types
+def ST_Union(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Calculate the union of two geometries.
+
+    :param a: One geometry column to use.
+    :type a: ColumnOrName
+    :param b: Other geometry column to use.
+    :type b: ColumnOrName
+    :return: Geometry representing the union of a and b as a geometry column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Union", (a, b))
+
+
+@validate_argument_types
+def ST_X(point: ColumnOrName) -> Column:
+    """Return the X coordinate of a point geometry.
+
+    :param point: Point geometry column to get the coordinate for.
+    :type point: ColumnOrName
+    :return: X coordinate of the point geometry as a double column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_X", point)
+
+
+@validate_argument_types
+def ST_XMax(geometry: ColumnOrName) -> Column:
+    """Calculate the maximum X coordinate for a geometry.
+
+    :param geometry: Geometry column to get maximum X coordinate for.
+    :type geometry: ColumnOrName
+    :return: Maximum X coordinate for the geometry as a double column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_XMax", geometry)
+
+
+@validate_argument_types
+def ST_XMin(geometry: ColumnOrName) -> Column:
+    """Calculate the minimum X coordinate for a geometry.
+
+    :param geometry: Geometry column to get minimum X coordinate for.
+    :type geometry: ColumnOrName
+    :return: Minimum X coordinate for the geometry as a double column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_XMin", geometry)
+
+
+@validate_argument_types
+def ST_Y(point: ColumnOrName) -> Column:
+    """Return the Y coordinate of a point geometry.
+
+    :param point: Point geometry column to return the Y coordinate for.
+    :type point: ColumnOrName
+    :return: Y coordinate of the point geometry column as a double column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Y", point)
+
+
+@validate_argument_types
+def ST_YMax(geometry: ColumnOrName) -> Column:
+    """Calculate the maximum Y coordinate for a geometry.
+
+    :param geometry: Geometry column to get the maximum Y coordinate for.
+    :type geometry: ColumnOrName
+    :return: Maximum Y coordinate for the geometry as a double column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_YMax", geometry)
+
+
+@validate_argument_types
+def ST_YMin(geometry: ColumnOrName) -> Column:
+    """Calculate the minimum Y coordinate for a geometry.
+
+    :param geometry: Geometry column to get the minimum Y coordinate for.
+    :type geometry: ColumnOrName
+    :return: Minimum Y coordinate for the geometry as a double column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_YMin", geometry)
+
+
+@validate_argument_types
+def ST_Z(point: ColumnOrName) -> Column:
+    """Return the Z coordinate of a point geometry.
+
+    :param point: Point geometry column to get the Z coordinate from.
+    :type point: ColumnOrName
+    :return: Z coordinate for the point geometry as a double column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_Z", point)
diff --git a/python/sedona/sql/st_predicates.py b/python/sedona/sql/st_predicates.py
new file mode 100644
index 00000000..e73f04cf
--- /dev/null
+++ b/python/sedona/sql/st_predicates.py
@@ -0,0 +1,147 @@
+from functools import partial
+
+from pyspark.sql import Column
+
+from sedona.sql.dataframe_api import ColumnOrName, call_sedona_function, validate_argument_types
+
+
+__all__ = [
+    "ST_Contains",
+    "ST_Crosses",
+    "ST_Disjoint",
+    "ST_Equals",
+    "ST_Intersects",
+    "ST_OrderingEquals",
+    "ST_Overlaps",
+    "ST_Touches",
+    "ST_Within",
+]
+
+
+_call_predicate_function = partial(call_sedona_function, "st_predicates")
+
+
+@validate_argument_types
+def ST_Contains(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Check whether geometry a contains geometry b.
+
+    :param a: Geometry column to check containment for.
+    :type a: ColumnOrName
+    :param b: Geometry column to check containment of.
+    :type b: ColumnOrName
+    :return: True if geometry a contains geometry b and False otherwise as a boolean column.
+    :rtype: Column
+    """
+    return _call_predicate_function("ST_Contains", (a, b))
+
+
+@validate_argument_types
+def ST_Crosses(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Check whether geometry a crosses geometry b.
+
+    :param a: Geometry to check crossing with.
+    :type a: ColumnOrName
+    :param b: Geometry to check crossing of.
+    :type b: ColumnOrName
+    :return: True if geometry a cross geometry b and False otherwise as a boolean column.
+    :rtype: Column
+    """
+    return _call_predicate_function("ST_Crosses", (a, b))
+
+
+@validate_argument_types
+def ST_Disjoint(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Check whether two geometries are disjoint.
+
+    :param a: One geometry column to check.
+    :type a: ColumnOrName
+    :param b: Other geometry column to check.
+    :type b: ColumnOrName
+    :return: True if a and b are disjoint and False otherwise as a boolean column.
+    :rtype: Column
+    """
+    return _call_predicate_function("ST_Disjoint", (a, b))
+
+
+@validate_argument_types
+def ST_Equals(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Check whether two geometries are equal regardles of vertex ordering.
+
+    :param a: One geometry column to check.
+    :type a: ColumnOrName
+    :param b: Other geometry column to check.
+    :type b: ColumnOrName
+    :return: True if a and b are equal and False otherwise, as a boolean column.
+    :rtype: Column
+    """
+    return _call_predicate_function("ST_Equals", (a, b))
+
+
+@validate_argument_types
+def ST_Intersects(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Check whether two geometries intersect.
+
+    :param a: One geometry column to check.
+    :type a: ColumnOrName
+    :param b: Other geometry column to check.
+    :type b: ColumnOrName
+    :return: True if a and b intersect and False otherwise, as a boolean column.
+    :rtype: Column
+    """
+    return _call_predicate_function("ST_Intersects", (a, b))
+
+
+@validate_argument_types
+def ST_OrderingEquals(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Check whether two geometries have identical vertices that are in the same order.
+
+    :param a: One geometry column to check.
+    :type a: ColumnOrName
+    :param b: Other geometry column to check.
+    :type b: ColumnOrName
+    :return: True if a and b are equal and False otherwise, as a boolean column.
+    :rtype: Column
+    """
+    return _call_predicate_function("ST_OrderingEquals", (a, b))
+
+
+@validate_argument_types
+def ST_Overlaps(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Check whether geometry a overlaps geometry b.
+
+    :param a: Geometry column to check overlapping for.
+    :type a: ColumnOrName
+    :param b: Geometry column to check overlapping of.
+    :type b: ColumnOrName
+    :return: True if a overlaps b and False otherwise, as a boolean column.
+    :rtype: Column
+    """
+    return _call_predicate_function("ST_Overlaps", (a, b))
+
+
+@validate_argument_types
+def ST_Touches(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Check whether two geometries touch.
+
+    :param a: One geometry column to check.
+    :type a: ColumnOrName
+    :param b: Other geometry column to check.
+    :type b: ColumnOrName
+    :return: True if a and b touch and False otherwise, as a boolean column.
+    :rtype: Column
+    """
+    return _call_predicate_function("ST_Touches", (a, b))
+
+
+@validate_argument_types
+def ST_Within(a: ColumnOrName, b: ColumnOrName) -> Column:
+    """Check if geometry a is within geometry b.
+
+    :param a: Geometry column to check.
+    :type a: ColumnOrName
+    :param b: Geometry column to check.
+    :type b: ColumnOrName
+    :return: True if a is within b and False otherwise, as a boolean column.
+    :rtype: Column
+    """
+    return _call_predicate_function("ST_Within", (a, b))
diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py
new file mode 100644
index 00000000..fce0a225
--- /dev/null
+++ b/python/tests/sql/test_dataframe_api.py
@@ -0,0 +1,383 @@
+from typing import Callable, Tuple
+
+from pyspark.sql import functions as f, Row
+import pytest
+from shapely.geometry.base import BaseGeometry
+
+from sedona.sql import (
+    st_aggregates as sta,
+    st_constructors as stc,
+    st_functions as stf,
+    st_predicates as stp,
+)
+
+from tests.test_base import TestBase
+
+
+test_configurations = [
+    # constructors
+    (stc.ST_GeomFromGeoHash, ("geohash", 4), "constructor", "ST_PrecisionReduce(geom, 2)", "POLYGON ((0.7 1.05, 1.05 1.05, 1.05 0.88, 0.7 0.88, 0.7 1.05))"),
+    (stc.ST_GeomFromGeoJSON, ("geojson",), "constructor", "", "POINT (0 1)"),
+    (stc.ST_GeomFromGML, ("gml",), "constructor", "", "LINESTRING (-71.16 42.25, -71.17 42.25, -71.18 42.25)"),
+    (stc.ST_GeomFromKML, ("kml",), "constructor", "", "LINESTRING (-71.16 42.26, -71.17 42.26)"),
+    (stc.ST_GeomFromText, ("wkt",), "linestring_wkt", "", "LINESTRING (1 2, 3 4)"),
+    (stc.ST_GeomFromWKB, ("wkb",), "constructor", "ST_PrecisionReduce(geom, 2)", "LINESTRING (-2.1 -0.35, -1.5 -0.67)"),
+    (stc.ST_GeomFromWKT, ("wkt",), "linestring_wkt", "", "LINESTRING (1 2, 3 4)"),
+    (stc.ST_LineFromText, ("wkt",), "linestring_wkt", "", "LINESTRING (1 2, 3 4)"),
+    (stc.ST_LineStringFromText, ("multiple_point", lambda: f.lit(',')), "constructor", "", "LINESTRING (0 0, 1 0, 1 1, 0 0)"),
+    (stc.ST_Point, ("x", "y"), "constructor", "", "POINT (0 1)"),
+    (stc.ST_PointFromText, ("single_point", lambda: f.lit(',')), "constructor", "", "POINT (0 1)"),
+    (stc.ST_PolygonFromEnvelope, ("minx", "miny", "maxx", "maxy"), "min_max_x_y", "", "POLYGON ((0 1, 0 3, 2 3, 2 1, 0 1))"),
+    (stc.ST_PolygonFromEnvelope, (0.0, 1.0, 2.0, 3.0), "null", "", "POLYGON ((0 1, 0 3, 2 3, 2 1, 0 1))"),
+    (stc.ST_PolygonFromText, ("multiple_point", lambda: f.lit(',')), "constructor", "", "POLYGON ((0 0, 1 0, 1 1, 0 0))"),
+
+    # functions
+    (stf.ST_3DDistance, ("a", "b"), "two_points", "", 5.0),
+    (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)"),
+    (stf.ST_AddPoint, ("line", lambda: f.expr("ST_Point(1.0, 1.0)"), 1), "linestring_geom", "", "LINESTRING (0 0, 1 1, 1 0, 2 0, 3 0, 4 0, 5 0)"),
+    (stf.ST_Area, ("geom",), "triangle_geom", "", 0.5),
+    (stf.ST_AsBinary, ("point",), "point_geom", "", "01010000000000000000000000000000000000f03f"),
+    (stf.ST_AsEWKB, (lambda: f.expr("ST_SetSRID(point, 3021)"),), "point_geom", "", "0101000020cd0b00000000000000000000000000000000f03f"),
+    (stf.ST_AsEWKT, (lambda: f.expr("ST_SetSRID(point, 4326)"),), "point_geom", "", "SRID=4326;POINT (0 1)"),
+    (stf.ST_AsGeoJSON, ("point",), "point_geom", "", "{\"type\":\"Point\",\"coordinates\":[0.0,1.0]}"),
+    (stf.ST_AsGML, ("point",), "point_geom", "", "<gml:Point>\n  <gml:coordinates>\n    0.0,1.0 \n  </gml:coordinates>\n</gml:Point>\n"),
+    (stf.ST_AsKML, ("point",), "point_geom", "", "<Point>\n  <coordinates>0.0,1.0</coordinates>\n</Point>\n"),
+    (stf.ST_AsText, ("point",), "point_geom", "", "POINT (0 1)"),
+    (stf.ST_Azimuth, ("a", "b"), "two_points", "geom * 180.0 / pi()", 90.0),
+    (stf.ST_Boundary, ("geom",), "triangle_geom", "", "LINESTRING (0 0, 1 0, 1 1, 0 0)"),
+    (stf.ST_Buffer, ("point", 1.0), "point_geom", "ST_PrecisionReduce(geom, 2)", "POLYGON ((0.98 0.8, 0.92 0.62, 0.83 0.44, 0.71 0.29, 0.56 0.17, 0.38 0.08, 0.2 0.02, 0 0, -0.2 0.02, -0.38 0.08, -0.56 0.17, -0.71 0.29, -0.83 0.44, -0.92 0.62, -0.98 0.8, -1 1, -0.98 1.2, -0.92 1.38, -0.83 1.56, -0.71 1.71, -0.56 1.83, -0.38 1.92, -0.2 1.98, 0 2, 0.2 1.98, 0.38 1.92, 0.56 1.83, 0.71 1.71, 0.83 1.56, 0.92 1.38, 0.98 1.2, 1 1, 0.98 0.8))"),
+    (stf.ST_BuildArea, ("geom",), "multiline_geom", "ST_Normalize(geom)", "POLYGON ((0 0, 1 1, 1 0, 0 0))"),
+    (stf.ST_Centroid, ("geom",), "triangle_geom", "ST_PrecisionReduce(geom, 2)", "POINT (0.67 0.33)"),
+    (stf.ST_Collect, (lambda: f.expr("array(a, b)"),), "two_points", "", "MULTIPOINT Z (0 0 0, 3 0 4)"),
+    (stf.ST_Collect, ("a", "b"), "two_points", "", "MULTIPOINT Z (0 0 0, 3 0 4)"),
+    (stf.ST_CollectionExtract, ("geom",), "geom_collection", "", "MULTILINESTRING ((0 0, 1 0))"),
+    (stf.ST_CollectionExtract, ("geom", 1), "geom_collection", "", "MULTIPOINT (0 0)"),
+    (stf.ST_ConvexHull, ("geom",), "triangle_geom", "", "POLYGON ((0 0, 1 1, 1 0, 0 0))"),
+    (stf.ST_Difference, ("a", "b"), "overlapping_polys", "", "POLYGON ((1 0, 0 0, 0 1, 1 1, 1 0))"),
+    (stf.ST_Distance, ("a", "b"), "two_points", "", 3.0),
+    (stf.ST_Dump, ("geom",), "multipoint", "", ["POINT (0 0)", "POINT (1 1)"]),
+    (stf.ST_DumpPoints, ("line",), "linestring_geom", "", ["POINT (0 0)", "POINT (1 0)", "POINT (2 0)", "POINT (3 0)", "POINT (4 0)", "POINT (5 0)"]),
+    (stf.ST_EndPoint, ("line",), "linestring_geom", "", "POINT (5 0)"),
+    (stf.ST_Envelope, ("geom",), "triangle_geom", "", "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))"),
+    (stf.ST_ExteriorRing, ("geom",), "triangle_geom", "", "LINESTRING (0 0, 1 0, 1 1, 0 0)"),
+    (stf.ST_FlipCoordinates, ("point",), "point_geom", "", "POINT (1 0)"),
+    (stf.ST_Force_2D, ("point",), "point_geom", "", "POINT (0 1)"),
+    (stf.ST_GeometryN, ("geom", 0), "multipoint", "", "POINT (0 0)"),
+    (stf.ST_GeometryType, ("point",), "point_geom", "", "ST_Point"),
+    (stf.ST_InteriorRingN, ("geom", 0), "geom_with_hole", "", "LINESTRING (1 1, 2 2, 2 1, 1 1)"),
+    (stf.ST_Intersection, ("a", "b"), "overlapping_polys", "", "POLYGON ((2 0, 1 0, 1 1, 2 1, 2 0))"),
+    (stf.ST_IsClosed, ("geom",), "closed_linestring_geom", "", True),
+    (stf.ST_IsEmpty, ("geom",), "empty_geom", "", True),
+    (stf.ST_IsRing, ("line",), "linestring_geom", "", False),
+    (stf.ST_IsSimple, ("geom",), "triangle_geom", "", True),
+    (stf.ST_IsValid, (lambda: f.col("geom"),), "triangle_geom", "", True),
+    (stf.ST_Length, ("line",), "linestring_geom", "", 5.0),
+    (stf.ST_LineInterpolatePoint, ("line", 0.5), "linestring_geom", "", "POINT (2.5 0)"),
+    (stf.ST_LineMerge, ("geom",), "multiline_geom", "", "LINESTRING (0 0, 1 0, 1 1, 0 0)"),
+    (stf.ST_LineSubstring, ("line", 0.5, 1.0), "linestring_geom", "", "LINESTRING (2.5 0, 3 0, 4 0, 5 0)"),
+    (stf.ST_MakeValid, ("geom",), "invalid_geom", "", "MULTIPOLYGON (((1 5, 3 3, 1 1, 1 5)), ((5 3, 7 5, 7 1, 5 3)))"),
+    (stf.ST_MakePolygon, ("geom",), "closed_linestring_geom", "", "POLYGON ((0 0, 1 0, 1 1, 0 0))"),
+    (stf.ST_MinimumBoundingCircle, ("line",), "linestring_geom", "ST_PrecisionReduce(geom, 2)", "POLYGON ((4.95 -0.49, 4.81 -0.96, 4.58 -1.39, 4.27 -1.77, 3.89 -2.08, 3.46 -2.31, 2.99 -2.45, 2.5 -2.5, 2.01 -2.45, 1.54 -2.31, 1.11 -2.08, 0.73 -1.77, 0.42 -1.39, 0.19 -0.96, 0.05 -0.49, 0 0, 0.05 0.49, 0.19 0.96, 0.42 1.39, 0.73 1.77, 1.11 2.08, 1.54 2.31, 2.01 2.45, 2.5 2.5, 2.99 2.45, 3.46 2.31, 3.89 2.08, 4.27 1.77, 4.58 1.39, 4.81 0.96, 4.95 0.49, 5 0, 4.95 -0.49))"),
+    (stf.ST_MinimumBoundingCircle, ("line", 2), "linestring_geom", "ST_PrecisionReduce(geom, 2)", "POLYGON ((4.95 -0.49, 4.81 -0.96, 4.58 -1.39, 4.27 -1.77, 3.89 -2.08, 3.46 -2.31, 2.99 -2.45, 2.5 -2.5, 2.01 -2.45, 1.54 -2.31, 1.11 -2.08, 0.73 -1.77, 0.42 -1.39, 0.19 -0.96, 0.05 -0.49, 0 0, 0.05 0.49, 0.19 0.96, 0.42 1.39, 0.73 1.77, 1.11 2.08, 1.54 2.31, 2.01 2.45, 2.5 2.5, 2.99 2.45, 3.46 2.31, 3.89 2.08, 4.27 1.77, 4.58 1.39, 4.81 0.96, 4.95 0.49, 5 0, 4.95 -0.49))"),
+    (stf.ST_MinimumBoundingRadius, ("line",), "linestring_geom", "", {"center": "POINT (2.5 0)", "radius": 2.5}),
+    (stf.ST_Multi, ("point",), "point_geom", "", "MULTIPOINT (0 1)"),
+    (stf.ST_Normalize, ("geom",), "triangle_geom", "", "POLYGON ((0 0, 1 1, 1 0, 0 0))"),
+    (stf.ST_NPoints, ("line",), "linestring_geom", "", 6),
+    (stf.ST_NumGeometries, ("geom",), "multipoint", "", 2),
+    (stf.ST_NumInteriorRings, ("geom",), "geom_with_hole", "", 1),
+    (stf.ST_PointN, ("line", 2), "linestring_geom", "", "POINT (1 0)"),
+    (stf.ST_PointOnSurface, ("line",), "linestring_geom", "", "POINT (2 0)"),
+    (stf.ST_PrecisionReduce, ("geom", 1), "precision_reduce_point", "", "POINT (0.1 0.2)"),
+    (stf.ST_RemovePoint, ("line", 1), "linestring_geom", "", "LINESTRING (0 0, 2 0, 3 0, 4 0, 5 0)"),
+    (stf.ST_Reverse, ("line",), "linestring_geom", "", "LINESTRING (5 0, 4 0, 3 0, 2 0, 1 0, 0 0)"),
+    (stf.ST_SetSRID, ("point", 3021), "point_geom", "ST_SRID(geom)", 3021),
+    (stf.ST_SimplifyPreserveTopology, ("geom", 0.2), "0.9_poly", "", "POLYGON ((0 0, 1 0, 1 1, 0 0))"),
+    (stf.ST_SRID, ("point",), "point_geom", "", 0),
+    (stf.ST_StartPoint, ("line",), "linestring_geom", "", "POINT (0 0)"),
+    (stf.ST_SubDivide, ("line", 5), "linestring_geom", "", ["LINESTRING (0 0, 2.5 0)", "LINESTRING (2.5 0, 5 0)"]),
+    (stf.ST_SubDivideExplode, ("line", 5), "linestring_geom", "collect_list(geom)", ["LINESTRING (0 0, 2.5 0)", "LINESTRING (2.5 0, 5 0)"]),
+    (stf.ST_SymDifference, ("a", "b"), "overlapping_polys", "", "MULTIPOLYGON (((1 0, 0 0, 0 1, 1 1, 1 0)), ((2 0, 2 1, 3 1, 3 0, 2 0)))"),
+    (stf.ST_Transform, ("point", lambda: f.lit("EPSG:4326"), lambda: f.lit("EPSG:32649")), "point_geom", "ST_PrecisionReduce(geom, 2)", "POINT (-33788209.77 0)"),
+    (stf.ST_Union, ("a", "b"), "overlapping_polys", "", "POLYGON ((1 0, 0 0, 0 1, 1 1, 2 1, 3 1, 3 0, 2 0, 1 0))"),
+    (stf.ST_X, ("b",), "two_points", "", 3.0),
+    (stf.ST_XMax, ("line",), "linestring_geom", "", 5.0),
+    (stf.ST_XMin, ("line",), "linestring_geom", "", 0.0),
+    (stf.ST_Y, ("b",), "two_points", "", 0.0),
+    (stf.ST_YMax, ("geom",), "triangle_geom", "", 1.0),
+    (stf.ST_YMin, ("geom",), "triangle_geom", "", 0.0),
+    (stf.ST_Z, ("b",), "two_points", "", 4.0),
+
+    # predicates
+    (stp.ST_Contains, ("geom", lambda: f.expr("ST_Point(0.5, 0.25)")), "triangle_geom", "", True),
+    (stp.ST_Crosses, ("line", "poly"), "line_crossing_poly", "", True),
+    (stp.ST_Disjoint, ("a", "b"), "two_points", "", True),
+    (stp.ST_Equals, ("line", lambda: f.expr("ST_Reverse(line)")), "linestring_geom", "", True),
+    (stp.ST_Intersects, ("a", "b"), "overlapping_polys", "", True),
+    (stp.ST_OrderingEquals, ("line", lambda: f.expr("ST_Reverse(line)")), "linestring_geom", "", False),
+    (stp.ST_Overlaps, ("a", "b"), "overlapping_polys", "", True),
+    (stp.ST_Touches, ("a", "b"), "touching_polys", "", True),
+    (stp.ST_Within, (lambda: f.expr("ST_Point(0.5, 0.25)"), "geom"), "triangle_geom", "", True),
+
+    # aggregates
+    (sta.ST_Envelope_Aggr, ("geom",), "exploded_points", "", "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))"),
+    (sta.ST_Intersection_Aggr, ("geom",), "exploded_polys", "", "LINESTRING (1 0, 1 1)"),
+    (sta.ST_Union_Aggr, ("geom",), "exploded_polys", "", "POLYGON ((1 0, 0 0, 0 1, 1 1, 2 1, 2 0, 1 0))"),
+]
+
+wrong_type_configurations = [
+    # constructors
+    (stc.ST_GeomFromGeoHash, (None, 4)),
+    (stc.ST_GeomFromGeoHash, ("", None)),
+    (stc.ST_GeomFromGeoHash, ("", 4.0)),
+    (stc.ST_GeomFromGeoJSON, (None,)),
+    (stc.ST_GeomFromGML, (None,)),
+    (stc.ST_GeomFromKML, (None,)),
+    (stc.ST_GeomFromText, (None,)),
+    (stc.ST_GeomFromWKB, (None,)),
+    (stc.ST_GeomFromWKT, (None,)),
+    (stc.ST_LineFromText, (None,)),
+    (stc.ST_LineStringFromText, (None, "")),
+    (stc.ST_LineStringFromText, ("", None)),
+    (stc.ST_Point, (None, "")),
+    (stc.ST_Point, ("", None)),
+    (stc.ST_PointFromText, (None, "")),
+    (stc.ST_PointFromText, ("", None)),
+    (stc.ST_PolygonFromEnvelope, (None, "", "", "")),
+    (stc.ST_PolygonFromEnvelope, ("", None, "", "")),
+    (stc.ST_PolygonFromEnvelope, ("", "", None, "")),
+    (stc.ST_PolygonFromEnvelope, ("", "", "", None)),
+    (stc.ST_PolygonFromText, (None, "")),
+    (stc.ST_PolygonFromText, ("", None)),
+
+    # functions
+    (stf.ST_3DDistance, (None, "")),
+    (stf.ST_3DDistance, ("", None)),
+    (stf.ST_AddPoint, (None, "")),
+    (stf.ST_AddPoint, ("", None)),
+    (stf.ST_Area, (None,)),
+    (stf.ST_AsBinary, (None,)),
+    (stf.ST_AsEWKB, (None,)),
+    (stf.ST_AsEWKT, (None,)),
+    (stf.ST_AsGeoJSON, (None,)),
+    (stf.ST_AsGML, (None,)),
+    (stf.ST_AsKML, (None,)),
+    (stf.ST_AsText, (None,)),
+    (stf.ST_Azimuth, (None, "")),
+    (stf.ST_Azimuth, ("", None)),
+    (stf.ST_Boundary, (None,)),
+    (stf.ST_Buffer, (None, 1.0)),
+    (stf.ST_Buffer, ("", None)),
+    (stf.ST_BuildArea, (None,)),
+    (stf.ST_Centroid, (None,)),
+    (stf.ST_Collect, (None,)),
+    (stf.ST_CollectionExtract, (None,)),
+    (stf.ST_ConvexHull, (None,)),
+    (stf.ST_Difference, (None, "b")),
+    (stf.ST_Difference, ("", None)),
+    (stf.ST_Distance, (None, "")),
+    (stf.ST_Distance, ("", None)),
+    (stf.ST_Dump, (None,)),
+    (stf.ST_DumpPoints, (None,)),
+    (stf.ST_EndPoint, (None,)),
+    (stf.ST_Envelope, (None,)),
+    (stf.ST_ExteriorRing, (None,)),
+    (stf.ST_FlipCoordinates, (None,)),
+    (stf.ST_Force_2D, (None,)),
+    (stf.ST_GeometryN, (None, 0)),
+    (stf.ST_GeometryN, ("", None)),
+    (stf.ST_GeometryN, ("", 0.0)),
+    (stf.ST_GeometryType, (None,)),
+    (stf.ST_InteriorRingN, (None, 0)),
+    (stf.ST_InteriorRingN, ("", None)),
+    (stf.ST_InteriorRingN, ("", 0.0)),
+    (stf.ST_Intersection, (None, "")),
+    (stf.ST_Intersection, ("", None)),
+    (stf.ST_IsClosed, (None,)),
+    (stf.ST_IsEmpty, (None,)),
+    (stf.ST_IsRing, (None,)),
+    (stf.ST_IsSimple, (None,)),
+    (stf.ST_IsValid, (None,)),
+    (stf.ST_Length, (None,)),
+    (stf.ST_LineInterpolatePoint, (None, 0.5)),
+    (stf.ST_LineInterpolatePoint, ("", None)),
+    (stf.ST_LineMerge, (None,)),
+    (stf.ST_LineSubstring, (None, 0.5, 1.0)),
+    (stf.ST_LineSubstring, ("", None, 1.0)),
+    (stf.ST_LineSubstring, ("", 0.5, None)),
+    (stf.ST_MakeValid, (None,)),
+    (stf.ST_MakePolygon, (None,)),
+    (stf.ST_MinimumBoundingCircle, (None,)),
+    (stf.ST_MinimumBoundingRadius, (None,)),
+    (stf.ST_Multi, (None,)),
+    (stf.ST_Normalize, (None,)),
+    (stf.ST_NPoints, (None,)),
+    (stf.ST_NumGeometries, (None,)),
+    (stf.ST_NumInteriorRings, (None,)),
+    (stf.ST_PointN, (None, 2)),
+    (stf.ST_PointN, ("", None)),
+    (stf.ST_PointN, ("", 2.0)),
+    (stf.ST_PointOnSurface, (None,)),
+    (stf.ST_PrecisionReduce, (None, 1)),
+    (stf.ST_PrecisionReduce, ("", None)),
+    (stf.ST_PrecisionReduce, ("", 1.0)),
+    (stf.ST_RemovePoint, (None, 1)),
+    (stf.ST_RemovePoint, ("", None)),
+    (stf.ST_RemovePoint, ("", 1.0)),
+    (stf.ST_Reverse, (None,)),
+    (stf.ST_SetSRID, (None, 3021)),
+    (stf.ST_SetSRID, ("", None)),
+    (stf.ST_SetSRID, ("", 3021.0)),
+    (stf.ST_SimplifyPreserveTopology, (None, 0.2)),
+    (stf.ST_SimplifyPreserveTopology, ("", None)),
+    (stf.ST_SRID, (None,)),
+    (stf.ST_StartPoint, (None,)),
+    (stf.ST_SubDivide, (None, 5)),
+    (stf.ST_SubDivide, ("", None)),
+    (stf.ST_SubDivide, ("", 5.0)),
+    (stf.ST_SubDivideExplode, (None, 5)),
+    (stf.ST_SubDivideExplode, ("", None)),
+    (stf.ST_SubDivideExplode, ("", 5.0)),
+    (stf.ST_SymDifference, (None, "")),
+    (stf.ST_SymDifference, ("", None)),
+    (stf.ST_Transform, (None, "", "")),
+    (stf.ST_Transform, ("", None, "")),
+    (stf.ST_Transform, ("", "", None)),
+    (stf.ST_Union, (None, "")),
+    (stf.ST_Union, ("", None)),
+    (stf.ST_X, (None,)),
+    (stf.ST_XMax, (None,)),
+    (stf.ST_XMin, (None,)),
+    (stf.ST_Y, (None,)),
+    (stf.ST_YMax, (None,)),
+    (stf.ST_YMin, (None,)),
+    (stf.ST_Z, (None,)),
+
+    # predicates
+    (stp.ST_Contains, (None, "")),
+    (stp.ST_Contains, ("", None)),
+    (stp.ST_Crosses, (None, "")),
+    (stp.ST_Crosses, ("", None)),
+    (stp.ST_Disjoint, (None, "")),
+    (stp.ST_Disjoint, ("", None)),
+    (stp.ST_Equals, (None, "")),
+    (stp.ST_Equals, ("", None)),
+    (stp.ST_Intersects, (None, "")),
+    (stp.ST_Intersects, ("", None)),
+    (stp.ST_OrderingEquals, (None, "")),
+    (stp.ST_OrderingEquals, ("", None)),
+    (stp.ST_Overlaps, (None, "")),
+    (stp.ST_Overlaps, ("", None)),
+    (stp.ST_Touches, (None, "")),
+    (stp.ST_Touches, ("", None)),
+    (stp.ST_Within, (None, "")),
+    (stp.ST_Within, ("", None)),
+
+    # aggregates
+    (sta.ST_Envelope_Aggr, (None,)),
+    (sta.ST_Intersection_Aggr, (None,)),
+    (sta.ST_Union_Aggr, (None,)),
+]
+
+class TestDataFrameAPI(TestBase):
+
+    @pytest.fixture
+    def base_df(self, request):
+        wkb = '0102000000020000000000000084d600c00000000080b5d6bf00000060e1eff7bf00000080075de5bf'
+        geojson = "{ \"type\": \"Feature\", \"properties\": { \"prop\": \"01\" }, \"geometry\": { \"type\": \"Point\", \"coordinates\": [ 0.0, 1.0 ] }},"
+        gml_string = "<gml:LineString srsName=\"EPSG:4269\"><gml:coordinates>-71.16,42.25 -71.17,42.25 -71.18,42.25</gml:coordinates></gml:LineString>"
+        kml_string = "<LineString><coordinates>-71.16,42.26 -71.17,42.26</coordinates></LineString>"
+        
+        if request.param == "constructor":
+            return TestDataFrameAPI.spark.sql("SELECT null").selectExpr(
+                "0.0 AS x",
+                "1.0 AS y",
+                "'0.0,1.0' AS single_point",
+                "'0.0,0.0,1.0,0.0,1.0,1.0,0.0,0.0' AS multiple_point", 
+                f"X'{wkb}' AS wkb",
+                f"'{geojson}' AS geojson",
+                "'s00twy01mt' AS geohash",
+                f"'{gml_string}' AS gml",
+                f"'{kml_string}' AS kml"
+            )
+        elif request.param == "point_geom":
+            return TestDataFrameAPI.spark.sql("SELECT ST_Point(0.0, 1.0) AS point")
+        elif request.param == "linestring_geom":
+            return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0, 2 0, 3 0, 4 0, 5 0)') AS line")
+        elif request.param == "linestring_wkt":
+            return TestDataFrameAPI.spark.sql("SELECT 'LINESTRING (1 2, 3 4)' AS wkt")
+        elif request.param == "min_max_x_y":
+            return TestDataFrameAPI.spark.sql("SELECT 0.0 AS minx, 1.0 AS miny, 2.0 AS maxx, 3.0 AS maxy")
+        elif request.param == "null":
+            return TestDataFrameAPI.spark.sql("SELECT null")
+        elif request.param == "triangle_geom":
+            return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 0))') AS geom")
+        elif request.param == "two_points":
+            return TestDataFrameAPI.spark.sql("SELECT ST_Point(0.0, 0.0, 0.0) AS a, ST_Point(3.0, 0.0, 4.0) AS b")
+        elif request.param == "invalid_geom":
+            return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('POLYGON ((1 5, 1 1, 3 3, 5 3, 7 1, 7 5, 5 3, 3 3, 1 5))') AS geom")
+        elif request.param == "overlapping_polys":
+            return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('POLYGON((0 0, 2 0, 2 1, 0 1, 0 0))') AS a, ST_GeomFromWKT('POLYGON((1 0, 3 0, 3 1, 1 1, 1 0))') AS b")
+        elif request.param == "multipoint":
+            return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('MULTIPOINT ((0 0), (1 1))') AS geom")
+        elif request.param == "geom_with_hole":
+            return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 3 0, 3 3, 0 0), (1 1, 2 2, 2 1, 1 1))') AS geom")
+        elif request.param == "0.9_poly":
+            return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 0.9, 1 1, 0 0))') AS geom")
+        elif request.param == "precision_reduce_point":
+            return TestDataFrameAPI.spark.sql("SELECT ST_Point(0.12, 0.23) AS geom")
+        elif request.param == "closed_linestring_geom":
+            return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0, 1 1, 0 0)') AS geom")
+        elif request.param == "empty_geom":
+            return TestDataFrameAPI.spark.sql("SELECT ST_Difference(ST_Point(0.0, 0.0), ST_Point(0.0, 0.0)) AS geom")
+        elif request.param == "multiline_geom":
+            return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('MULTILINESTRING ((0 0, 1 0), (1 0, 1 1), (1 1, 0 0))') AS geom")
+        elif request.param == "geom_collection":
+            return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(0 0, 1 0))') AS geom")
+        elif request.param == "exploded_points":
+            return TestDataFrameAPI.spark.sql("SELECT explode(array(ST_Point(0.0, 0.0), ST_Point(1.0, 1.0))) AS geom")
+        elif request.param == "exploded_polys":
+            return TestDataFrameAPI.spark.sql("SELECT explode(array(ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))'), ST_GeomFromWKT('POLYGON ((1 0, 2 0, 2 1, 1 1, 1 0))'))) AS geom")
+        elif request.param == "touching_polys":
+            return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))') AS a, ST_GeomFromWKT('POLYGON ((1 0, 2 0, 2 1, 1 1, 1 0))') AS b")
+        elif request.param == "line_crossing_poly":
+            return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 2 1)') AS line, ST_GeomFromWKT('POLYGON ((1 0, 2 0, 2 2, 1 2, 1 0))') AS poly")
+        raise ValueError(f"Invalid base_df name passed: {request.param}")
+
+    def _id_test_configuration(val):
+        if isinstance(val, Callable):
+            return val.__name__
+        elif isinstance(val, Tuple):
+            return f"{val}"
+        elif isinstance(val, dict):
+            return f"{val}"
+        return val
+
+    @pytest.mark.parametrize("func,args,base_df,post_process,expected_result", test_configurations, ids=_id_test_configuration, indirect=["base_df"])
+    def test_dataframe_function(self, func, args, base_df, post_process, expected_result):
+        args = [arg() if isinstance(arg, Callable) else arg for arg in args]
+
+        if len(args) == 1:
+            df = base_df.select(func(args[0]).alias("geom"))
+        else:
+            df = base_df.select(func(*args).alias("geom"))
+
+        if post_process:
+            df = df.selectExpr(f"{post_process} AS geom")
+
+        actual_result = df.collect()[0][0]
+
+        if isinstance(actual_result, BaseGeometry):
+            actual_result = actual_result.wkt
+        elif isinstance(actual_result, bytearray):
+            actual_result = actual_result.hex()
+        elif isinstance(actual_result, Row):
+            actual_result = {k: v.wkt if isinstance(v, BaseGeometry) else v for k, v in actual_result.asDict().items()}
+        elif isinstance(actual_result, list):
+            actual_result = sorted([x.wkt if isinstance(x, BaseGeometry) else x for x in actual_result])
+
+        assert(actual_result == expected_result)
+
+    @pytest.mark.parametrize("func,args", wrong_type_configurations, ids=_id_test_configuration)
+    def test_call_function_with_wrong_type(self, func, args):
+        with pytest.raises(ValueError, match=f"Incorrect argument type: [A-Za-z_0-9]+ for {func.__name__} should be [A-Za-z0-9\\[\\]_, ]+ but received [A-Za-z0-9_]+."):
+            func(*args)
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/DataFrameAPI.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/DataFrameAPI.scala
new file mode 100644
index 00000000..fa144087
--- /dev/null
+++ b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/DataFrameAPI.scala
@@ -0,0 +1,69 @@
+/*
+ * 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.spark.sql.sedona_sql.expressions
+
+import scala.reflect.ClassTag
+
+import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder
+import org.apache.spark.sql.catalyst.expressions.{Expression, Literal}
+import org.apache.spark.sql.Column
+import org.apache.spark.sql.expressions.UserDefinedAggregateFunction
+import org.apache.spark.sql.execution.aggregate.ScalaUDAF
+import org.apache.spark.sql.functions.{lit, array}
+import org.locationtech.jts.geom.Geometry
+
+trait DataFrameAPI {
+  protected def wrapExpression[E <: Expression : ClassTag](args: Any *): Column = {
+    val exprArgs = args.map(_ match {
+      case c: Column => c.expr
+      case s: String => Column(s).expr
+      case e: Expression => e
+      case x: Any => Literal(x)
+    })
+    val expressionConstructor = implicitly[ClassTag[E]].runtimeClass.getConstructor(classOf[Seq[Expression]])
+    val expressionInstance = expressionConstructor.newInstance(exprArgs).asInstanceOf[E]
+    Column(expressionInstance)
+  }
+
+  protected def wrapVarArgExpression[E <: Expression: ClassTag](arg: Seq[Any]): Column = {
+    val exprArgs = arg.map(_ match {
+      case c: Column => c.expr
+      case s: String => Column(s).expr
+      case e: Expression => e
+      case x: Any => Literal(x)
+    })
+    val expressionConstructor = implicitly[ClassTag[E]].runtimeClass.getConstructor(classOf[Seq[Expression]])
+    val expressionInstance = expressionConstructor.newInstance(exprArgs).asInstanceOf[E]
+    Column(expressionInstance)
+  }
+
+  protected def wrapAggregator[A <: UserDefinedAggregateFunction: ClassTag](arg: Any *): Column = {
+    val exprArgs = arg.map(_ match {
+      case c: Column => c.expr
+      case s: String => Column(s).expr
+      case e: Expression => e
+      case x: Any => Literal(x)
+    })
+    val aggregatorClass = implicitly[ClassTag[A]].runtimeClass
+    val aggregatorConstructor = aggregatorClass.getConstructor()
+    val aggregatorInstance = aggregatorConstructor.newInstance().asInstanceOf[UserDefinedAggregateFunction]
+    val scalaAggregator = ScalaUDAF(exprArgs, aggregatorInstance)
+    Column(scalaAggregator)
+  }
+}
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_aggregates.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_aggregates.scala
new file mode 100644
index 00000000..553f7a0c
--- /dev/null
+++ b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_aggregates.scala
@@ -0,0 +1,55 @@
+/*
+ * 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.spark.sql.sedona_sql.expressions
+
+import org.apache.spark.sql.catalyst.expressions.Expression
+import org.apache.spark.sql.Column
+import org.apache.spark.sql.functions.{col, udaf}
+
+object st_aggregates extends DataFrameAPI {
+  def ST_Envelope_Aggr(geometry: Column): Column = {
+    val aggrFunc = udaf(new ST_Envelope_Aggr)
+    aggrFunc(geometry)
+  }
+
+  def ST_Envelope_Aggr(geometry: String): Column = {
+    val aggrFunc = udaf(new ST_Envelope_Aggr)
+    aggrFunc(col(geometry))
+  }
+
+  def ST_Intersection_Aggr(geometry: Column): Column = {
+    val aggrFunc = udaf(new ST_Intersection_Aggr)
+    aggrFunc(geometry)
+  }
+
+  def ST_Intersection_Aggr(geometry: String): Column = {
+    val aggrFunc = udaf(new ST_Intersection_Aggr)
+    aggrFunc(col(geometry))
+  }
+
+  def ST_Union_Aggr(geometry: Column): Column = {
+    val aggrFunc = udaf(new ST_Union_Aggr)
+    aggrFunc(geometry)
+  }
+
+  def ST_Union_Aggr(geometry: String): Column = {
+    val aggrFunc = udaf(new ST_Union_Aggr)
+    aggrFunc(col(geometry))
+  }
+}
\ No newline at end of file
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala
new file mode 100644
index 00000000..b1ad748c
--- /dev/null
+++ b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala
@@ -0,0 +1,68 @@
+/*
+ * 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.spark.sql.sedona_sql.expressions
+
+import org.apache.spark.sql.catalyst.expressions.Expression
+import org.apache.spark.sql.Column
+
+object st_constructors extends DataFrameAPI {
+  def ST_GeomFromGeoHash(geohash: Column, precision: Column): Column = wrapExpression[ST_GeomFromGeoHash](geohash, precision)
+  def ST_GeomFromGeoHash(geohash: String, precision: Int): Column = wrapExpression[ST_GeomFromGeoHash](geohash, precision)
+
+  def ST_GeomFromGeoJSON(geojsonString: Column): Column = wrapExpression[ST_GeomFromGeoJSON](geojsonString)
+  def ST_GeomFromGeoJSON(geojsonString: String): Column = wrapExpression[ST_GeomFromGeoJSON](geojsonString)
+
+  def ST_GeomFromGML(gmlString: Column): Column = wrapExpression[ST_GeomFromGML](gmlString)
+  def ST_GeomFromGML(gmlString: String): Column = wrapExpression[ST_GeomFromGML](gmlString)
+
+  def ST_GeomFromKML(kmlString: Column): Column = wrapExpression[ST_GeomFromKML](kmlString)
+  def ST_GeomFromKML(kmlString: String): Column = wrapExpression[ST_GeomFromKML](kmlString)
+
+  def ST_GeomFromText(wkt: Column): Column = wrapExpression[ST_GeomFromText](wkt)
+  def ST_GeomFromText(wkt: String): Column = wrapExpression[ST_GeomFromText](wkt)
+
+  def ST_GeomFromWKB(wkb: Column): Column = wrapExpression[ST_GeomFromWKB](wkb)
+  def ST_GeomFromWKB(wkb: String): Column = wrapExpression[ST_GeomFromWKB](wkb)
+
+  def ST_GeomFromWKT(wkt: Column): Column = wrapExpression[ST_GeomFromWKT](wkt)
+  def ST_GeomFromWKT(wkt: String): Column = wrapExpression[ST_GeomFromWKT](wkt)
+
+  def ST_LineFromText(wkt: Column): Column = wrapExpression[ST_LineFromText](wkt)
+  def ST_LineFromText(wkt: String): Column = wrapExpression[ST_LineFromText](wkt)
+
+  def ST_LineStringFromText(coords: Column, delimiter: Column): Column = wrapExpression[ST_LineStringFromText](coords, delimiter)
+  def ST_LineStringFromText(coords: String, delimiter: String): Column = wrapExpression[ST_LineStringFromText](coords, delimiter)
+
+  def ST_Point(x: Column, y: Column): Column = wrapExpression[ST_Point](x, y)
+  def ST_Point(x: String, y: String): Column = wrapExpression[ST_Point](x, y)
+  def ST_Point(x: Double, y: Double): Column = wrapExpression[ST_Point](x, y)
+  def ST_Point(x: Column, y: Column, z: Column): Column = wrapExpression[ST_Point](x, y, z)
+  def ST_Point(x: String, y: String, z: String): Column = wrapExpression[ST_Point](x, y, z)
+  def ST_Point(x: Double, y: Double, z: Double): Column = wrapExpression[ST_Point](x, y, z)
+ 
+  def ST_PointFromText(coords: Column, delimiter: Column): Column = wrapExpression[ST_PointFromText](coords, delimiter)
+  def ST_PointFromText(coords: String, delimiter: String): Column = wrapExpression[ST_PointFromText](coords, delimiter)
+
+  def ST_PolygonFromEnvelope(minX: Column, minY: Column, maxX: Column, maxY: Column): Column = wrapExpression[ST_PolygonFromEnvelope](minX, minY, maxX, maxY)
+  def ST_PolygonFromEnvelope(minX: String, minY: String, maxX: String, maxY: String): Column = wrapExpression[ST_PolygonFromEnvelope](minX, minY, maxX, maxY)
+  def ST_PolygonFromEnvelope(minX: Double, minY: Double, maxX: Double, maxY: Double): Column = wrapExpression[ST_PolygonFromEnvelope](minX, minY, maxX, maxY)
+
+  def ST_PolygonFromText(coords: Column, delimiter: Column): Column = wrapExpression[ST_PolygonFromText](coords, delimiter)
+  def ST_PolygonFromText(coords: String, delimiter: String): Column = wrapExpression[ST_PolygonFromText](coords, delimiter)
+}
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
new file mode 100644
index 00000000..c4b2de23
--- /dev/null
+++ b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
@@ -0,0 +1,251 @@
+/*
+ * 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.spark.sql.sedona_sql.expressions
+
+import org.apache.spark.sql.catalyst.expressions.Expression
+import org.apache.spark.sql.Column
+import org.apache.spark.sql.sedona_sql.expressions.collect.{ST_Collect, ST_CollectionExtract}
+
+object st_functions extends DataFrameAPI {
+  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)
+
+  def ST_AddPoint(lineString: Column, point: Column): Column = wrapExpression[ST_AddPoint](lineString, point)
+  def ST_AddPoint(lineString: String, point: String): Column = wrapExpression[ST_AddPoint](lineString, point)
+  def ST_AddPoint(lineString: Column, point: Column, index: Column): Column = wrapExpression[ST_AddPoint](lineString, point, index)
+  def ST_AddPoint(lineString: String, point: String, index: Int): Column = wrapExpression[ST_AddPoint](lineString, point, index)
+
+  def ST_Area(geometry: Column): Column = wrapExpression[ST_Area](geometry)
+  def ST_Area(geometry: String): Column = wrapExpression[ST_Area](geometry)
+
+  def ST_AsBinary(geometry: Column): Column = wrapExpression[ST_AsBinary](geometry)
+  def ST_AsBinary(geometry: String): Column = wrapExpression[ST_AsBinary](geometry)
+
+  def ST_AsEWKB(geometry: Column): Column = wrapExpression[ST_AsEWKB](geometry)
+  def ST_AsEWKB(geometry: String): Column = wrapExpression[ST_AsEWKB](geometry)
+
+  def ST_AsEWKT(geometry: Column): Column = wrapExpression[ST_AsEWKT](geometry)
+  def ST_AsEWKT(geometry: String): Column = wrapExpression[ST_AsEWKT](geometry)
+
+  def ST_AsGeoJSON(geometry: Column): Column = wrapExpression[ST_AsGeoJSON](geometry)
+  def ST_AsGeoJSON(geometry: String): Column = wrapExpression[ST_AsGeoJSON](geometry)
+
+  def ST_AsGML(geometry: Column): Column = wrapExpression[ST_AsGML](geometry)
+  def ST_AsGML(geometry: String): Column = wrapExpression[ST_AsGML](geometry)
+
+  def ST_AsKML(geometry: Column): Column = wrapExpression[ST_AsKML](geometry)
+  def ST_AsKML(geometry: String): Column = wrapExpression[ST_AsKML](geometry)
+
+  def ST_AsText(geometry: Column): Column = wrapExpression[ST_AsText](geometry)
+  def ST_AsText(geometry: String): Column = wrapExpression[ST_AsText](geometry)
+
+  def ST_Azimuth(pointA: Column, pointB: Column): Column = wrapExpression[ST_Azimuth](pointA, pointB)
+  def ST_Azimuth(pointA: String, pointB: String): Column = wrapExpression[ST_Azimuth](pointA, pointB)
+
+  def ST_Boundary(geometry: Column): Column = wrapExpression[ST_Boundary](geometry)
+  def ST_Boundary(geometry: String): Column = wrapExpression[ST_Boundary](geometry)
+
+  def ST_Buffer(geometry: Column, buffer: Column): Column = wrapExpression[ST_Buffer](geometry, buffer)
+  def ST_Buffer(geometry: String, buffer: Double): Column = wrapExpression[ST_Buffer](geometry, buffer)
+
+  def ST_BuildArea(geometry: Column): Column = wrapExpression[ST_BuildArea](geometry)
+  def ST_BuildArea(geometry: String): Column = wrapExpression[ST_BuildArea](geometry)
+
+  def ST_Centroid(geometry: Column): Column = wrapExpression[ST_Centroid](geometry)
+  def ST_Centroid(geometry: String): Column = wrapExpression[ST_Centroid](geometry)
+
+  def ST_Collect(geoms: Column): Column = wrapExpression[ST_Collect](geoms)
+  def ST_Collect(geoms: String): Column = wrapExpression[ST_Collect](geoms)
+  def ST_Collect(geoms: Any*): Column = wrapVarArgExpression[ST_Collect](geoms)
+
+  def ST_CollectionExtract(collection: Column): Column = wrapExpression[ST_CollectionExtract](collection)
+  def ST_CollectionExtract(collection: String): Column = wrapExpression[ST_CollectionExtract](collection)
+  def ST_CollectionExtract(collection: Column, geomType: Column): Column = wrapExpression[ST_CollectionExtract](collection, geomType)
+  def ST_CollectionExtract(collection: String, geomType: Int): Column = wrapExpression[ST_CollectionExtract](collection, geomType)
+
+  def ST_ConvexHull(geometry: Column): Column = wrapExpression[ST_ConvexHull](geometry)
+  def ST_ConvexHull(geometry: String): Column = wrapExpression[ST_ConvexHull](geometry)
+  
+  def ST_Difference(a: Column, b: Column): Column = wrapExpression[ST_Difference](a, b)
+  def ST_Difference(a: String, b: String): Column = wrapExpression[ST_Difference](a, b)
+
+  def ST_Distance(a: Column, b: Column): Column = wrapExpression[ST_Distance](a, b)
+  def ST_Distance(a: String, b: String): Column = wrapExpression[ST_Distance](a, b)
+
+  def ST_Dump(geometry: Column): Column = wrapExpression[ST_Dump](geometry)
+  def ST_Dump(geometry: String): Column = wrapExpression[ST_Dump](geometry)
+
+  def ST_DumpPoints(geometry: Column): Column = wrapExpression[ST_DumpPoints](geometry)
+  def ST_DumpPoints(geometry: String): Column = wrapExpression[ST_DumpPoints](geometry)
+
+  def ST_EndPoint(lineString: Column): Column = wrapExpression[ST_EndPoint](lineString)
+  def ST_EndPoint(lineString: String): Column = wrapExpression[ST_EndPoint](lineString)
+
+  def ST_Envelope(geometry: Column): Column = wrapExpression[ST_Envelope](geometry)
+  def ST_Envelope(geometry: String): Column = wrapExpression[ST_Envelope](geometry)
+
+  def ST_ExteriorRing(polygon: Column): Column = wrapExpression[ST_ExteriorRing](polygon)
+  def ST_ExteriorRing(polygon: String): Column = wrapExpression[ST_ExteriorRing](polygon)
+
+  def ST_FlipCoordinates(geometry: Column): Column = wrapExpression[ST_FlipCoordinates](geometry)
+  def ST_FlipCoordinates(geometry: String): Column = wrapExpression[ST_FlipCoordinates](geometry)
+  
+  def ST_Force_2D(geometry: Column): Column = wrapExpression[ST_Force_2D](geometry)
+  def ST_Force_2D(geometry: String): Column = wrapExpression[ST_Force_2D](geometry)
+
+  def ST_GeoHash(geometry: Column, precision: Column): Column = wrapExpression[ST_GeoHash](geometry, precision)
+  def ST_GeoHash(geometry: String, precision: Int): Column = wrapExpression[ST_GeoHash](geometry, precision)
+
+  def ST_GeometryN(multiGeometry: Column, n: Column): Column = wrapExpression[ST_GeometryN](multiGeometry, n)
+  def ST_GeometryN(multiGeometry: String, n: Int): Column = wrapExpression[ST_GeometryN](multiGeometry, n)
+
+  def ST_GeometryType(geometry: Column): Column = wrapExpression[ST_GeometryType](geometry)
+  def ST_GeometryType(geometry: String): Column = wrapExpression[ST_GeometryType](geometry)
+
+  def ST_InteriorRingN(polygon: Column, n: Column): Column = wrapExpression[ST_InteriorRingN](polygon, n)
+  def ST_InteriorRingN(polygon: String, n: Int): Column = wrapExpression[ST_InteriorRingN](polygon, n)
+
+  def ST_Intersection(a: Column, b: Column): Column = wrapExpression[ST_Intersection](a, b)
+  def ST_Intersection(a: String, b: String): Column = wrapExpression[ST_Intersection](a, b)
+
+  def ST_IsClosed(geometry: Column): Column = wrapExpression[ST_IsClosed](geometry)
+  def ST_IsClosed(geometry: String): Column = wrapExpression[ST_IsClosed](geometry)
+  
+  def ST_IsEmpty(geometry: Column): Column = wrapExpression[ST_IsEmpty](geometry)
+  def ST_IsEmpty(geometry: String): Column = wrapExpression[ST_IsEmpty](geometry)
+
+  def ST_IsRing(lineString: Column): Column = wrapExpression[ST_IsRing](lineString)
+  def ST_IsRing(lineString: String): Column = wrapExpression[ST_IsRing](lineString)
+
+  def ST_IsSimple(geometry: Column): Column = wrapExpression[ST_IsSimple](geometry)
+  def ST_IsSimple(geometry: String): Column = wrapExpression[ST_IsSimple](geometry)
+
+  def ST_IsValid(geometry: Column): Column = wrapExpression[ST_IsValid](geometry)
+  def ST_IsValid(geometry: String): Column = wrapExpression[ST_IsValid](geometry)
+
+  def ST_Length(geometry: Column): Column = wrapExpression[ST_Length](geometry)
+  def ST_Length(geometry: String): Column = wrapExpression[ST_Length](geometry)
+
+  def ST_LineInterpolatePoint(geometry: Column, fraction: Column): Column = wrapExpression[ST_LineInterpolatePoint](geometry, fraction)
+  def ST_LineInterpolatePoint(geometry: String, fraction: Double): Column = wrapExpression[ST_LineInterpolatePoint](geometry, fraction)
+
+  def ST_LineMerge(multiLineString: Column): Column = wrapExpression[ST_LineMerge](multiLineString)
+  def ST_LineMerge(multiLineString: String): Column = wrapExpression[ST_LineMerge](multiLineString)
+
+  def ST_LineSubstring(lineString: Column, startFraction: Column, endFraction: Column): Column = wrapExpression[ST_LineSubstring](lineString, startFraction, endFraction)
+  def ST_LineSubstring(lineString: String, startFraction: Double, endFraction: Double): Column = wrapExpression[ST_LineSubstring](lineString, startFraction, endFraction)
+
+  def ST_MakePolygon(lineString: Column): Column = wrapExpression[ST_MakePolygon](lineString)
+  def ST_MakePolygon(lineString: String): Column = wrapExpression[ST_MakePolygon](lineString)
+  def ST_MakePolygon(lineString: Column, holes: Column): Column = wrapExpression[ST_MakePolygon](lineString, holes)
+  def ST_MakePolygon(lineString: String, holes: String): Column = wrapExpression[ST_MakePolygon](lineString, holes)
+
+  def ST_MakeValid(geometry: Column): Column = wrapExpression[ST_MakeValid](geometry)
+  def ST_MakeValid(geometry: String): Column = wrapExpression[ST_MakeValid](geometry)
+  def ST_MakeValid(geometry: Column, keepCollapsed: Column): Column = wrapExpression[ST_MakeValid](geometry, keepCollapsed)
+  def ST_MakeValid(geometry: String, keepCollapsed: Boolean): Column = wrapExpression[ST_MakeValid](geometry, keepCollapsed)
+
+  def ST_MinimumBoundingCircle(geometry: Column): Column = wrapExpression[ST_MinimumBoundingCircle](geometry)
+  def ST_MinimumBoundingCircle(geometry: String): Column = wrapExpression[ST_MinimumBoundingCircle](geometry)
+  def ST_MinimumBoundingCircle(geometry: Column, quadrantSegments: Column): Column = wrapExpression[ST_MinimumBoundingCircle](geometry)
+  def ST_MinimumBoundingCircle(geometry: String, quadrantSegments: Int): Column = wrapExpression[ST_MinimumBoundingCircle](geometry)
+
+  def ST_MinimumBoundingRadius(geometry: Column): Column = wrapExpression[ST_MinimumBoundingRadius](geometry)
+  def ST_MinimumBoundingRadius(geometry: String): Column = wrapExpression[ST_MinimumBoundingRadius](geometry)
+  
+  def ST_Multi(geometry: Column): Column = wrapExpression[ST_Multi](geometry)
+  def ST_Multi(geometry: String): Column = wrapExpression[ST_Multi](geometry)
+
+  def ST_Normalize(geometry: Column): Column = wrapExpression[ST_Normalize](geometry)
+  def ST_Normalize(geometry: String): Column = wrapExpression[ST_Normalize](geometry)
+
+  def ST_NPoints(geometry: Column): Column = wrapExpression[ST_NPoints](geometry)
+  def ST_NPoints(geometry: String): Column = wrapExpression[ST_NPoints](geometry)
+
+  def ST_NumGeometries(geometry: Column): Column = wrapExpression[ST_NumGeometries](geometry)
+  def ST_NumGeometries(geometry: String): Column = wrapExpression[ST_NumGeometries](geometry)
+
+  def ST_NumInteriorRings(geometry: Column): Column = wrapExpression[ST_NumInteriorRings](geometry)
+  def ST_NumInteriorRings(geometry: String): Column = wrapExpression[ST_NumInteriorRings](geometry)
+  
+  def ST_PointN(geometry: Column, n: Column): Column = wrapExpression[ST_PointN](geometry, n)
+  def ST_PointN(geometry: String, n: Int): Column = wrapExpression[ST_PointN](geometry, n)
+  
+  def ST_PointOnSurface(geometry: Column): Column = wrapExpression[ST_PointOnSurface](geometry)
+  def ST_PointOnSurface(geometry: String): Column = wrapExpression[ST_PointOnSurface](geometry)
+
+  def ST_PrecisionReduce(geometry: Column, precision: Column): Column = wrapExpression[ST_PrecisionReduce](geometry, precision)
+  def ST_PrecisionReduce(geometry: String, precision: Int): Column = wrapExpression[ST_PrecisionReduce](geometry, precision)
+
+  def ST_RemovePoint(lineString: Column, index: Column): Column = wrapExpression[ST_RemovePoint](lineString, index)
+  def ST_RemovePoint(lineString: String, index: Int): Column = wrapExpression[ST_RemovePoint](lineString, index)
+  
+  def ST_Reverse(geometry: Column): Column = wrapExpression[ST_Reverse](geometry)
+  def ST_Reverse(geometry: String): Column = wrapExpression[ST_Reverse](geometry)
+
+  def ST_SetSRID(geometry: Column, srid: Column): Column = wrapExpression[ST_SetSRID](geometry, srid)
+  def ST_SetSRID(geometry: String, srid: Int): Column = wrapExpression[ST_SetSRID](geometry, srid)
+
+  def ST_SRID(geometry: Column): Column = wrapExpression[ST_SRID](geometry)
+  def ST_SRID(geometry: String): Column = wrapExpression[ST_SRID](geometry)
+
+  def ST_StartPoint(lineString: Column): Column = wrapExpression[ST_StartPoint](lineString)
+  def ST_StartPoint(lineString: String): Column = wrapExpression[ST_StartPoint](lineString)
+
+  def ST_SubDivide(geometry: Column, maxVertices: Column): Column = wrapExpression[ST_SubDivide](geometry, maxVertices)
+  def ST_SubDivide(geometry: String, maxVertices: Int): Column = wrapExpression[ST_SubDivide](geometry, maxVertices)
+
+  def ST_SubDivideExplode(geometry: Column, maxVertices: Column): Column = wrapExpression[ST_SubDivideExplode](geometry, maxVertices)
+  def ST_SubDivideExplode(geometry: String, maxVertices: Int): Column = wrapExpression[ST_SubDivideExplode](geometry, maxVertices)
+
+  def ST_SimplifyPreserveTopology(geometry: Column, distanceTolerance: Column): Column = wrapExpression[ST_SimplifyPreserveTopology](geometry, distanceTolerance)
+  def ST_SimplifyPreserveTopology(geometry: String, distanceTolerance: Double): Column = wrapExpression[ST_SimplifyPreserveTopology](geometry, distanceTolerance)
+  
+  def ST_SymDifference(a: Column, b: Column): Column = wrapExpression[ST_SymDifference](a, b)
+  def ST_SymDifference(a: String, b: String): Column = wrapExpression[ST_SymDifference](a, b)
+
+  def ST_Transform(geometry: Column, sourceCRS: Column, targetCRS: Column): Column = wrapExpression[ST_Transform](geometry, sourceCRS, targetCRS)
+  def ST_Transform(geometry: String, sourceCRS: String, targetCRS: String): Column = wrapExpression[ST_Transform](geometry, sourceCRS, targetCRS)
+  def ST_Transform(geometry: Column, sourceCRS: Column, targetCRS: Column, disableError: Column): Column = wrapExpression[ST_Transform](geometry, sourceCRS, targetCRS, disableError)
+  def ST_Transform(geometry: String, sourceCRS: String, targetCRS: String, disableError: Boolean): Column = wrapExpression[ST_Transform](geometry, sourceCRS, targetCRS, disableError)
+  
+  def ST_Union(a: Column, b: Column): Column = wrapExpression[ST_Union](a, b)
+  def ST_Union(a: String, b: String): Column = wrapExpression[ST_Union](a, b)
+  
+  def ST_X(point: Column): Column = wrapExpression[ST_X](point)
+  def ST_X(point: String): Column = wrapExpression[ST_X](point)
+
+  def ST_XMax(geometry: Column): Column = wrapExpression[ST_XMax](geometry)
+  def ST_XMax(geometry: String): Column = wrapExpression[ST_XMax](geometry)
+  
+  def ST_XMin(geometry: Column): Column = wrapExpression[ST_XMin](geometry)
+  def ST_XMin(geometry: String): Column = wrapExpression[ST_XMin](geometry)
+
+  def ST_Y(point: Column): Column = wrapExpression[ST_Y](point)
+  def ST_Y(point: String): Column = wrapExpression[ST_Y](point)
+
+  def ST_YMax(geometry: Column): Column = wrapExpression[ST_YMax](geometry)
+  def ST_YMax(geometry: String): Column = wrapExpression[ST_YMax](geometry)
+
+  def ST_YMin(geometry: Column): Column = wrapExpression[ST_YMin](geometry)
+  def ST_YMin(geometry: String): Column = wrapExpression[ST_YMin](geometry)
+
+  def ST_Z(point: Column): Column = wrapExpression[ST_Z](point)
+  def ST_Z(point: String): Column = wrapExpression[ST_Z](point)
+}
\ No newline at end of file
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_predicates.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_predicates.scala
new file mode 100644
index 00000000..2ab1f34e
--- /dev/null
+++ b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_predicates.scala
@@ -0,0 +1,51 @@
+/*
+ * 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.spark.sql.sedona_sql.expressions
+
+import org.apache.spark.sql.catalyst.expressions.Expression
+import org.apache.spark.sql.Column
+
+object st_predicates extends DataFrameAPI {
+  def ST_Contains(a: Column, b: Column): Column = wrapExpression[ST_Contains](a, b)
+  def ST_Contains(a: String, b: String): Column = wrapExpression[ST_Contains](a, b)
+
+  def ST_Crosses(a: Column, b: Column): Column = wrapExpression[ST_Crosses](a, b)
+  def ST_Crosses(a: String, b: String): Column = wrapExpression[ST_Crosses](a, b)
+
+  def ST_Disjoint(a: Column, b: Column): Column = wrapExpression[ST_Disjoint](a, b)
+  def ST_Disjoint(a: String, b: String): Column = wrapExpression[ST_Disjoint](a, b)
+
+  def ST_Equals(a: Column, b: Column): Column = wrapExpression[ST_Equals](a, b)
+  def ST_Equals(a: String, b: String): Column = wrapExpression[ST_Equals](a, b)
+
+  def ST_Intersects(a: Column, b: Column): Column = wrapExpression[ST_Intersects](a, b)
+  def ST_Intersects(a: String, b: String): Column = wrapExpression[ST_Intersects](a, b)
+
+  def ST_OrderingEquals(a: Column, b: Column): Column = wrapExpression[ST_OrderingEquals](a, b)
+  def ST_OrderingEquals(a: String, b: String): Column = wrapExpression[ST_OrderingEquals](a, b)
+
+  def ST_Overlaps(a: Column, b: Column): Column = wrapExpression[ST_Overlaps](a, b)
+  def ST_Overlaps(a: String, b: String): Column = wrapExpression[ST_Overlaps](a, b)
+
+  def ST_Touches(a: Column, b: Column): Column = wrapExpression[ST_Touches](a, b)
+  def ST_Touches(a: String, b: String): Column = wrapExpression[ST_Touches](a, b)
+
+  def ST_Within(a: Column, b: Column): Column = wrapExpression[ST_Within](a, b)
+  def ST_Within(a: String, b: String): Column = wrapExpression[ST_Within](a, b)
+}
\ No newline at end of file
diff --git a/sql/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala b/sql/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
new file mode 100644
index 00000000..366f44ad
--- /dev/null
+++ b/sql/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
@@ -0,0 +1,835 @@
+/*
+ * 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.sql
+
+import scala.collection.mutable.WrappedArray
+
+import org.apache.commons.codec.binary.Hex
+import org.apache.spark.sql.functions.lit
+
+import org.locationtech.jts.geom.Geometry
+
+import org.apache.spark.sql.sedona_sql.expressions.st_constructors._
+import org.apache.spark.sql.sedona_sql.expressions.st_functions._
+import org.apache.spark.sql.sedona_sql.expressions.st_predicates._
+import org.apache.spark.sql.sedona_sql.expressions.st_aggregates._
+
+class dataFrameAPITestScala extends TestBaseScala {
+
+  import sparkSession.implicits._
+
+  describe("Sedona DataFrame API Test") {
+    // constructors
+    it("passed st_point") {
+      val df = sparkSession.sql("SELECT 0.0 AS x, 1.0 AS y").select(ST_Point("x", "y"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POINT (0 1)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("passed st_pointfromtext") {
+      val df = sparkSession.sql("SELECT '0.0,1.0' AS c").select(ST_PointFromText($"c", lit(',')))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POINT (0 1)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("passed st_polygonfromtext") {
+      val df = sparkSession.sql("SELECT '0.0,0.0,1.0,0.0,1.0,1.0,0.0,0.0' AS c").select(ST_PolygonFromText($"c", lit(',')))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((0 0, 1 0, 1 1, 0 0))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("passed st_linefromtext") {
+      val df = sparkSession.sql("SELECT 'Linestring(1 2, 3 4)' AS wkt").select(ST_LineFromText("wkt"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "LINESTRING (1 2, 3 4)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("passed st_linestringfromtext") {
+      val df = sparkSession.sql("SELECT '0.0,0.0,1.0,0.0' AS c").select(ST_LineStringFromText($"c", lit(',')))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "LINESTRING (0 0, 1 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("passed st_geomfromwkt") {
+      val df = sparkSession.sql("SELECT 'POINT(0.0 1.0)' AS wkt").select(ST_GeomFromWKT("wkt"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POINT (0 1)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("passed st_geomfromtext") {
+      val df = sparkSession.sql("SELECT 'POINT(0.0 1.0)' AS wkt").select(ST_GeomFromText("wkt"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POINT (0 1)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("passed st_geomfromwkb") {
+      val wkbSeq = Seq[Array[Byte]](Array[Byte](1, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, -124, -42, 0, -64, 0, 0, 0, 0, -128, -75, -42, -65, 0, 0, 0, 96, -31, -17, -9, -65, 0, 0, 0, -128, 7, 93, -27, -65))
+      val df = wkbSeq.toDF("wkb").select(ST_GeomFromWKB("wkb"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "LINESTRING (-2.1047439575195312 -0.354827880859375, -1.49606454372406 -0.6676061153411865)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("passed st_geomfromgeojson") {
+      val geojson = "{ \"type\": \"Feature\", \"properties\": { \"prop\": \"01\" }, \"geometry\": { \"type\": \"Point\", \"coordinates\": [ 0.0, 1.0 ] }},"
+      val df = Seq[String](geojson).toDF("geojson").select(ST_GeomFromGeoJSON("geojson"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POINT (0 1)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("passed st_polygonfromenvelope with column values") {
+      val df = sparkSession.sql("SELECT 0.0 AS minx, 1.0 AS miny, 2.0 AS maxx, 3.0 AS maxy")
+      val actualResult = df.select(ST_PolygonFromEnvelope("minx", "miny", "maxx", "maxy")).take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((0 1, 0 3, 2 3, 2 1, 0 1))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("passed st_polygonfromenvelope with literal values") {
+      val df = sparkSession.sql("SELECT null AS c").select(ST_PolygonFromEnvelope(0.0, 1.0, 2.0, 3.0))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((0 1, 0 3, 2 3, 2 1, 0 1))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("passed st_geomfromgeohash") {
+      val df = sparkSession.sql("SELECT 's00twy01mt' AS geohash").select(ST_GeomFromGeoHash("geohash", 4))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((0.703125 0.87890625, 0.703125 1.0546875, 1.0546875 1.0546875, 1.0546875 0.87890625, 0.703125 0.87890625))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("passed st_geomfromgml") {
+      val gmlString = "<gml:LineString srsName=\"EPSG:4269\"><gml:coordinates>-71.16028,42.258729 -71.160837,42.259112 -71.161143,42.25932</gml:coordinates></gml:LineString>"
+      val df = sparkSession.sql(s"SELECT '$gmlString' AS gml").select(ST_GeomFromGML("gml"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "LINESTRING (-71.16028 42.258729, -71.160837 42.259112, -71.161143 42.25932)"
+      assert(actualResult == expectedResult)
+    }
+
+    it ("passed st_geomfromkml") {
+      val kmlString = "<LineString><coordinates>-71.1663,42.2614 -71.1667,42.2616</coordinates></LineString>"
+      val df = sparkSession.sql(s"SELECT '$kmlString' as kml").select(ST_GeomFromKML("kml"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "LINESTRING (-71.1663 42.2614, -71.1667 42.2616)"
+      assert(actualResult == expectedResult)
+    }
+
+    // functions
+    it("Passed ST_ConvexHull") {
+      val polygonDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 0))') AS geom")
+      val df = polygonDf.select(ST_ConvexHull("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      assert(actualResult == "POLYGON ((0 0, 1 1, 1 0, 0 0))")
+    }
+
+    it("Passed ST_Buffer") {
+      val polygonDf = sparkSession.sql("SELECT ST_Point(1.0, 1.0) AS geom")
+      val df = polygonDf.select(ST_Buffer("geom", 1.0).as("geom")).selectExpr("ST_PrecisionReduce(geom, 2)")
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((1.98 0.8, 1.92 0.62, 1.83 0.44, 1.71 0.29, 1.56 0.17, 1.38 0.08, 1.2 0.02, 1 0, 0.8 0.02, 0.62 0.08, 0.44 0.17, 0.29 0.29, 0.17 0.44, 0.08 0.62, 0.02 0.8, 0 1, 0.02 1.2, 0.08 1.38, 0.17 1.56, 0.29 1.71, 0.44 1.83, 0.62 1.92, 0.8 1.98, 1 2, 1.2 1.98, 1.38 1.92, 1.56 1.83, 1.71 1.71, 1.83 1.56, 1.92 1.38, 1.98 1.2, 2 1, 1.98 0.8))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_Envelope") {
+      val polygonDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 0))') AS geom")
+      val df = polygonDf.select(ST_Envelope("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_YMax") {
+      val polygonDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 0))') AS geom")
+      val df = polygonDf.select(ST_YMax("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Double]
+      val expectedResult = 1.0
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_YMin") {
+      val polygonDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 0))') AS geom")
+      val df = polygonDf.select(ST_YMin("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Double]
+      val expectedResult = 0.0
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_Centroid") {
+      val polygonDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0))') AS geom")
+      val df = polygonDf.select(ST_Centroid("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POINT (1 1)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_Length") {
+      val lineDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS geom")
+      val df = lineDf.select(ST_Length("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Double]
+      val expectedResult = 1.0
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_Area") {
+      val polygonDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 0))') AS geom")
+      val df = polygonDf.select(ST_Area("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Double]
+      val expectedResult = 0.5
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_Distance") {
+      val pointDf = sparkSession.sql("SELECT ST_Point(0.0, 0.0) AS a, ST_Point(1.0, 0.0) as b") 
+      val df = pointDf.select(ST_Distance("a", "b"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Double]
+      val expectedResult = 1.0
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_3DDistance") {
+      val pointDf = sparkSession.sql("SELECT ST_Point(0.0, 0.0, 0.0) AS a, ST_Point(3.0, 0.0, 4.0) as b")
+      val df = pointDf.select(ST_3DDistance("a", "b"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Double]
+      val expectedResult = 5.0
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_Transform") {
+      val pointDf = sparkSession.sql("SELECT ST_Point(1.0, 1.0) AS geom")
+      val df = pointDf.select(ST_Transform($"geom", lit("EPSG:4326"), lit("EPSG:32649")).as("geom")).selectExpr("ST_PrecisionReduce(geom, 2)")
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POINT (-33741810.95 1823994.03)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_Intersection") {
+      val polygonDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON((1 1, 8 1, 8 8, 1 8, 1 1))') AS a, ST_GeomFromWKT('POLYGON((2 2, 9 2, 9 9, 2 9, 2 2))') AS b")
+      val df = polygonDf.select(ST_Intersection("a", "b"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((2 8, 8 8, 8 2, 2 2, 2 8))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_IsValid") {
+      val polygonDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))') AS geom")
+      val df = polygonDf.select(ST_IsValid("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Boolean]
+      assert(actualResult)
+    }
+
+    it("Passed ST_PrecisionReduce") {
+      val pointDf = sparkSession.sql("SELECT ST_Point(0.12, 0.23) AS geom")
+      val df = pointDf.select(ST_PrecisionReduce("geom", 1))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POINT (0.1 0.2)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_IsSimple") {
+      val triangleDf = sparkSession.sql("SELECT ST_GeomFromWkt('POLYGON ((0 0, 1 0, 1 1, 0 0))') AS geom")
+      val df = triangleDf.select(ST_IsSimple("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Boolean]
+      assert(actualResult)
+    }
+
+    it("Passed ST_MakeValid On Invalid Polygon") {
+      val invalidDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((1 5, 1 1, 3 3, 5 3, 7 1, 7 5, 5 3, 3 3, 1 5))') AS geom")
+      val df = invalidDf.select(ST_MakeValid("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "MULTIPOLYGON (((1 5, 3 3, 1 1, 1 5)), ((5 3, 7 5, 7 1, 5 3)))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_MakePolygon") {
+      val invalidDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0, 1 1, 0 0)') AS geom")
+      val df = invalidDf.select(ST_MakePolygon("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((0 0, 1 0, 1 1, 0 0))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_MakePolygon with holes") {
+      val invalidDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0, 1 1, 0 0)') AS geom, array(ST_GeomFromWKT('LINESTRING (0.5 0.1, 0.7 0.1, 0.7 0.3, 0.5 0.1)')) AS holes")
+      val df = invalidDf.select(ST_MakePolygon("geom", "holes"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((0 0, 1 0, 1 1, 0 0), (0.5 0.1, 0.7 0.1, 0.7 0.3, 0.5 0.1))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_SimplifyPreserveTopology") {
+      val polygonDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 0.9, 1 1, 0 0))') AS geom")
+      val df = polygonDf.select(ST_SimplifyPreserveTopology("geom", 0.2))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((0 0, 1 0, 1 1, 0 0))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_AsText") {
+      val pointDf = sparkSession.sql("SELECT ST_Point(0.0, 0.0) AS geom")
+      val df = pointDf.select(ST_AsText("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[String]
+      val expectedResult = "POINT (0 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_AsGeoJSON") {
+      val pointDf = sparkSession.sql("SELECT ST_Point(0.0, 0.0) AS geom")
+      val df = pointDf.select(ST_AsGeoJSON("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[String]
+      val expectedResult = "{\"type\":\"Point\",\"coordinates\":[0.0,0.0]}"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_AsBinary") {
+      val pointDf = sparkSession.sql("SELECT ST_Point(0.0, 0.0) AS geom")
+      val df = pointDf.select(ST_AsBinary("geom"))
+      val actualResult = Hex.encodeHexString(df.take(1)(0).get(0).asInstanceOf[Array[Byte]])
+      val expectedResult = "010100000000000000000000000000000000000000"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_AsGML") {
+      val pointDf = sparkSession.sql("SELECT ST_Point(0.0, 0.0) AS geom")
+      val df = pointDf.select(ST_AsGML("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[String]
+      val expectedResult = "<gml:Point>\n  <gml:coordinates>\n    0.0,0.0 \n  </gml:coordinates>\n</gml:Point>\n"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_AsKML") {
+      val pointDf = sparkSession.sql("SELECT ST_Point(0.0, 0.0) AS geom")
+      val df = pointDf.select(ST_AsKML("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[String]
+      val expectedResult = "<Point>\n  <coordinates>0.0,0.0</coordinates>\n</Point>\n"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_SRID") {
+      val pointDf = sparkSession.sql("SELECT ST_Point(0.0, 0.0) AS geom")
+      val df = pointDf.select(ST_SRID("geom"))
+      val actualResult = df.take(1)(0).getInt(0)
+      val expectedResult = 0
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_SetSRID") {
+      val pointDf = sparkSession.sql("SELECT ST_Point(0.0, 0.0) AS geom")
+      val df = pointDf.select(ST_SetSRID("geom", 3021))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].getSRID()
+      val expectedResult = 3021
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_AsEWKB") {
+      val sridPointDf = sparkSession.sql("SELECT ST_SetSRID(ST_Point(0.0, 0.0), 3021) AS geom")
+      val df = sridPointDf.select(ST_AsEWKB("geom"))
+      val actualResult = Hex.encodeHexString(df.take(1)(0).get(0).asInstanceOf[Array[Byte]])
+      val expectedResult = "0101000020cd0b000000000000000000000000000000000000"
+      assert(actualResult == expectedResult) 
+    }
+
+    it("Passed ST_NPoints") {
+      val lineDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 1)') AS geom")
+      val df = lineDf.select(ST_NPoints("geom"))
+      val actualResult = df.take(1)(0).getInt(0)
+      val expectedResult = 2
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_GeometryType") {
+      val pointDf = sparkSession.sql("SELECT ST_Point(0.0, 0.0) AS geom")
+      val df = pointDf.select(ST_GeometryType("geom"))
+      val actualResult = df.take(1)(0).getString(0)
+      val expectedResult = "ST_Point"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_Difference") {
+      val polygonDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((-3 -3, 3 -3, 3 3, -3 3, -3 -3))') AS a,ST_GeomFromWKT('POLYGON ((0 -4, 4 -4, 4 4, 0 4, 0 -4))') AS b")
+      val df = polygonDf.select(ST_Difference("a", "b"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((0 -3, -3 -3, -3 3, 0 3, 0 -3))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_SymDifference") {
+      val polygonDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((-1 -1, 1 -1, 1 1, -1 1, -1 -1))') AS a, ST_GeomFromWKT('POLYGON ((0 -2, 2 -2, 2 0, 0 0, 0 -2))') AS b")
+      val df = polygonDf.select(ST_SymDifference("a", "b"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "MULTIPOLYGON (((0 -1, -1 -1, -1 1, 1 1, 1 0, 0 0, 0 -1)), ((0 -1, 1 -1, 1 0, 2 0, 2 -2, 0 -2, 0 -1)))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_Union") {
+      val polygonDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((-3 -3, 3 -3, 3 3, -3 3, -3 -3))') AS a, ST_GeomFromWKT('POLYGON ((-2 1, 2 1, 2 4, -2 4, -2 1))') AS b")
+      val df = polygonDf.select(ST_Union("a", "b"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((2 3, 3 3, 3 -3, -3 -3, -3 3, -2 3, -2 4, 2 4, 2 3))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_Azimuth") {
+      val baseDf = sparkSession.sql("SELECT ST_Point(0.0, 0.0) AS a, ST_Point(1.0, 1.0) AS b")
+      val df = baseDf.select(ST_Azimuth("a", "b"))
+      val actualResult = df.take(1)(0).getDouble(0) * 180 / math.Pi
+      val expectedResult = 45.0
+      assert(actualResult == expectedResult)
+    }
+
+    it("Should pass ST_X") {
+      val baseDf = sparkSession.sql("SELECT ST_Point(0.0, 1.0, 2.0) AS geom")
+      val df = baseDf.select(ST_X("geom"))
+      val actualResult = df.take(1)(0).getDouble(0)
+      val expectedResult = 0.0
+      assert(actualResult == expectedResult)
+    }
+
+    it("Should pass ST_Y") {
+      val baseDf = sparkSession.sql("SELECT ST_Point(0.0, 1.0, 2.0) AS geom")
+      val df = baseDf.select(ST_Y("geom"))
+      val actualResult = df.take(1)(0).getDouble(0)
+      val expectedResult = 1.0
+      assert(actualResult == expectedResult)
+    }
+
+    it("Should pass ST_Z") {
+      val baseDf = sparkSession.sql("SELECT ST_Point(0.0, 1.0, 2.0) AS geom")
+      val df = baseDf.select(ST_Z("geom"))
+      val actualResult = df.take(1)(0).getDouble(0)
+      val expectedResult = 2.0
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_StartPoint") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS geom")
+      val df = baseDf.select(ST_StartPoint("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POINT (0 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_Boundary") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 0))') AS geom")
+      val df = baseDf.select(ST_Boundary("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "LINESTRING (0 0, 1 0, 1 1, 0 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_EndPoint") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS geom")
+      val df = baseDf.select(ST_EndPoint("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POINT (1 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_ExteriorRing") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 0))') AS geom")
+      val df = baseDf.select(ST_ExteriorRing("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "LINESTRING (0 0, 1 0, 1 1, 0 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_GeometryN") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('MULTIPOINT ((0 0))') AS geom")
+      val df = baseDf.select(ST_GeometryN("geom", 0))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POINT (0 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_InteriorRingN") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 3 0, 3 3, 0 0), (1 1, 2 2, 2 1, 1 1))') AS geom")
+      val df = baseDf.select(ST_InteriorRingN("geom", 0))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "LINESTRING (1 1, 2 2, 2 1, 1 1)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_Dump") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('MULTIPOINT ((0 0), (1 1))') AS geom")
+      val df = baseDf.select(ST_Dump("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[WrappedArray[Any]].map(_.asInstanceOf[Geometry].toText)
+      val expectedResult = Array("POINT (0 0)", "POINT (1 1)")
+      assert(actualResult(0) == expectedResult(0))
+      assert(actualResult(1) == expectedResult(1))
+    }
+
+    it("Passed ST_DumpPoints") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS geom")
+      val df = baseDf.select(ST_DumpPoints("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[WrappedArray[Any]].map(_.asInstanceOf[Geometry].toText)
+      val expectedResult = Array("POINT (0 0)", "POINT (1 0)")
+      assert(actualResult(0) == expectedResult(0))
+      assert(actualResult(1) == expectedResult(1))
+    }
+
+    it("Passed ST_IsClosed") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0, 1 1, 0 0)') AS geom")
+      val df = baseDf.select(ST_IsClosed("geom"))
+      val actualResult = df.take(1)(0).getBoolean(0)
+      assert(actualResult)
+    }
+
+    it("Passed ST_NumInteriorRings") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 3 0, 3 3, 0 0), (1 1, 2 2, 2 1, 1 1))') AS geom")
+      val df = baseDf.select(ST_NumInteriorRings("geom"))
+      val actualResult = df.take(1)(0).getInt(0)
+      val expectedResult = 1
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_AddPoint without index") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS line, ST_Point(1.0, 1.0) AS point")
+      val df = baseDf.select(ST_AddPoint("line", "point"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "LINESTRING (0 0, 1 0, 1 1)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_AddPoint with index") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS line, ST_Point(1.0, 1.0) AS point")
+      val df = baseDf.select(ST_AddPoint("line", "point", 1))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "LINESTRING (0 0, 1 1, 1 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_RemovePoint") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0, 1 1)') AS geom")
+      val df = baseDf.select(ST_RemovePoint("geom", 1))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "LINESTRING (0 0, 1 1)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_IsRing") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0, 1 1, 0 0)') AS geom")
+      val df = baseDf.select(ST_IsRing("geom"))
+      val actualResult = df.take(1)(0).getBoolean(0)
+      assert(actualResult)
+    }
+
+    it("Passed ST_Subdivide") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0, 2 0, 3 0, 4 0, 5 0)') AS geom")
+      val df = baseDf.select(ST_SubDivide("geom", 5))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[WrappedArray[Any]].map(_.asInstanceOf[Geometry].toText)
+      val expectedResult = Array[String]("LINESTRING (0 0, 2.5 0)", "LINESTRING (2.5 0, 5 0)")
+      assert(actualResult(0) == expectedResult(0))
+      assert(actualResult(1) == expectedResult(1))
+    }
+
+    it("Passed ST_SubdivideExplode") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0, 2 0, 3 0, 4 0, 5 0)') AS geom")
+      val df = baseDf.select(ST_SubDivideExplode("geom", 5))
+      val actualResults = df.take(2).map(_.get(0).asInstanceOf[Geometry].toText).sortWith(_ < _)
+      val expectedResult = Array[String]("LINESTRING (0 0, 2.5 0)", "LINESTRING (2.5 0, 5 0)")
+      assert(actualResults(0) == expectedResult(0))
+      assert(actualResults(1) == expectedResult(1))
+    }
+
+    it("Passed ST_NumGeometries") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('MULTIPOINT ((0 0), (1 1))') AS geom")
+      val df = baseDf.select(ST_NumGeometries("geom"))
+      val actualResult = df.take(1)(0).getInt(0)
+      val expectedResult = 2
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_LineMerge") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('MULTILINESTRING ((0 0, 1 0), (1 0, 2 0))') AS geom")
+      val df = baseDf.select(ST_LineMerge("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "LINESTRING (0 0, 1 0, 2 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_FlipCoordinates") {
+      val baseDf = sparkSession.sql("SELECT ST_Point(0.0, 1.0) AS geom")
+      val df = baseDf.select(ST_FlipCoordinates("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POINT (1 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_MinimumBoundingCircle with default quadrantSegments") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS geom")
+      val df = baseDf.select(ST_MinimumBoundingCircle("geom").as("geom")).selectExpr("ST_PrecisionReduce(geom, 2)")
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((0.99 -0.1, 0.96 -0.19, 0.92 -0.28, 0.85 -0.35, 0.78 -0.42, 0.69 -0.46, 0.6 -0.49, 0.5 -0.5, 0.4 -0.49, 0.31 -0.46, 0.22 -0.42, 0.15 -0.35, 0.08 -0.28, 0.04 -0.19, 0.01 -0.1, 0 0, 0.01 0.1, 0.04 0.19, 0.08 0.28, 0.15 0.35, 0.22 0.42, 0.31 0.46, 0.4 0.49, 0.5 0.5, 0.6 0.49, 0.69 0.46, 0.78 0.42, 0.85 0.35, 0.92 0.28, 0.96 0.19, 0.99 0.1, 1 0, 0.99 -0.1))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_MinimumBoundingCircle with specified quadrantSegments") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS geom")
+      val df = baseDf.select(ST_MinimumBoundingCircle("geom", 2).as("geom")).selectExpr("ST_PrecisionReduce(geom, 2)")
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((0.99 -0.1, 0.96 -0.19, 0.92 -0.28, 0.85 -0.35, 0.78 -0.42, 0.69 -0.46, 0.6 -0.49, 0.5 -0.5, 0.4 -0.49, 0.31 -0.46, 0.22 -0.42, 0.15 -0.35, 0.08 -0.28, 0.04 -0.19, 0.01 -0.1, 0 0, 0.01 0.1, 0.04 0.19, 0.08 0.28, 0.15 0.35, 0.22 0.42, 0.31 0.46, 0.4 0.49, 0.5 0.5, 0.6 0.49, 0.69 0.46, 0.78 0.42, 0.85 0.35, 0.92 0.28, 0.96 0.19, 0.99 0.1, 1 0, 0.99 -0.1))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_MinimumBoundingRadius") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS geom")
+      val df = baseDf.select(ST_MinimumBoundingRadius("geom").as("c")).select("c.center", "c.radius")
+      val rowResult = df.take(1)(0)
+      
+      val actualCenter = rowResult.get(0).asInstanceOf[Geometry].toText()
+      val actualRadius = rowResult.getDouble(1)
+      
+      val expectedCenter = "POINT (0.5 0)"
+      val expectedRadius = 0.5
+
+      assert(actualCenter == expectedCenter)
+      assert(actualRadius == expectedRadius)
+    }
+
+    it ("Passed ST_LineSubstring") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 2 0)') AS line")
+      val df = baseDf.select(ST_LineSubstring("line", 0.5, 1.0))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "LINESTRING (1 0, 2 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it ("Passed ST_LineInterpolatePoint") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 2 0)') AS line")
+      val df = baseDf.select(ST_LineInterpolatePoint("line", 0.5))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POINT (1 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it ("Passed ST_Multi"){
+      val baseDf = sparkSession.sql("SELECT ST_Point(0.0, 0.0) AS point")
+      val df = baseDf.select(ST_Multi("point"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "MULTIPOINT ((0 0))"
+      assert(actualResult == expectedResult)
+    }
+
+    it ("Passed ST_PointOnSurface") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS line")
+      val df = baseDf.select(ST_PointOnSurface("line"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POINT (0 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it ("Passed ST_Reverse") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS line")
+      val df = baseDf.select(ST_Reverse("line"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "LINESTRING (1 0, 0 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_PointN") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS line")
+      val df = baseDf.select(ST_PointN("line", 2))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POINT (1 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it ("Passed ST_AsEWKT") {
+      val baseDf = sparkSession.sql("SELECT ST_SetSRID(ST_Point(0.0, 0.0), 4326) AS point")
+      val df = baseDf.select(ST_AsEWKT("point"))
+      val actualResult = df.take(1)(0).getString(0)
+      val expectedResult = "SRID=4326;POINT (0 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it ("Passed ST_Force_2D") {
+      val baseDf = sparkSession.sql("SELECT ST_Point(0.0, 0.0, 1.0) AS point")
+      val df = baseDf.select(ST_Force_2D("point"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POINT (0 0)"
+      assert(actualResult == expectedResult)
+    }
+
+    it ("Passed ST_IsEmpty") {
+      val baseDf = sparkSession.sql("SELECT ST_Difference(ST_Point(0.0, 0.0), ST_Point(0.0, 0.0)) AS empty_geom")
+      val df = baseDf.select(ST_IsEmpty("empty_geom"))
+      val actualResult = df.take(1)(0).getBoolean(0)
+      assert(actualResult)
+    }
+
+    it("Passed ST_XMax") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS line")
+      val df = baseDf.select(ST_XMax("line"))
+      val actualResult = df.take(1)(0).getDouble(0)
+      val expectedResult = 1.0
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_XMin") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS line")
+      val df = baseDf.select(ST_XMin("line"))
+      val actualResult = df.take(1)(0).getDouble(0)
+      val expectedResult = 0.0
+      assert(actualResult == expectedResult)
+    }
+
+    it ("Passed ST_BuildArea") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('MULTILINESTRING ((0 0, 1 0), (1 0, 1 1), (1 1, 0 0))') AS multiline")
+      val df = baseDf.select(ST_BuildArea("multiline").as("geom")).selectExpr("ST_Normalize(geom)")
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((0 0, 1 1, 1 0, 0 0))"
+      assert(actualResult == expectedResult)
+    }
+
+    it ("Passed ST_Collect with array argument") {
+      val baseDf = sparkSession.sql("SELECT array(ST_Point(0.0, 0.0), ST_Point(1.0, 1.0)) as points")
+      val df = baseDf.select(ST_Collect("points"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "MULTIPOINT ((0 0), (1 1))"
+      assert(actualResult == expectedResult)
+    }
+
+    it ("Passed ST_Collect with variable arguments") {
+      val baseDf = sparkSession.sql("SELECT ST_Point(0.0, 0.0) AS a, ST_Point(1.0, 1.0) AS b")
+      val df = baseDf.select(ST_Collect("a", "b"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "MULTIPOINT ((0 0), (1 1))"
+      assert(actualResult == expectedResult)
+    }
+
+    it ("Passed St_CollectionExtract with default geomType") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(0 0, 1 0))') AS geom")
+      val df = baseDf.select(ST_CollectionExtract("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "MULTILINESTRING ((0 0, 1 0))"
+      assert(actualResult == expectedResult)
+    }
+
+    it ("Passed St_CollectionExtract with specified geomType") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(0 0, 1 0))') AS geom")
+      val df = baseDf.select(ST_CollectionExtract("geom", 1))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "MULTIPOINT ((0 0))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_Normalize") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON((0 0, 1 0, 1 1, 0 0))') AS polygon")
+      val df = baseDf.select(ST_Normalize("polygon"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((0 0, 1 1, 1 0, 0 0))"
+      assert(actualResult == expectedResult)
+    }
+
+    // predicates
+    it("Passed ST_Contains") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 0))') AS a, ST_Point(0.5, 0.25) AS b")
+      val df = baseDf.select(ST_Contains("a", "b"))
+      val actualResult = df.take(1)(0).getBoolean(0)
+      assert(actualResult)
+    }
+    it("Passed ST_Intersects") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 1)') AS a, ST_GeomFromWKT('LINESTRING (0 1, 1 0)') AS b")
+      val df = baseDf.select(ST_Intersects("a", "b"))
+      val actualResult = df.take(1)(0).getBoolean(0)
+      assert(actualResult)
+    }
+
+    it("Passed ST_Within") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 0))') AS a, ST_Point(0.5, 0.25) AS b")
+      val df = baseDf.select(ST_Within("b", "a"))
+      val actualResult = df.take(1)(0).getBoolean(0)
+      assert(actualResult)
+    }
+
+    it("Passed ST_Equals for ST_Point") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS a, ST_GeomFromWKT('LINESTRING (1 0, 0 0)') AS b")
+      val df = baseDf.select(ST_Equals("a", "b"))
+      val actualResult = df.take(1)(0).getBoolean(0)
+      assert(actualResult)
+    }
+
+    it("Passed ST_Crosses") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON((1 1, 4 1, 4 4, 1 4, 1 1))') AS a,ST_GeomFromWKT('LINESTRING(1 5, 5 1)') AS b")
+      val df = baseDf.select(ST_Crosses("a", "b"))
+      val actualResult = df.take(1)(0).getBoolean(0)
+      assert(actualResult)
+    }
+
+    it("Passed ST_Touches") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 0))') AS a, ST_GeomFromWKT('POLYGON ((1 1, 1 0, 2 0, 1 1))') AS b")
+      val df = baseDf.select(ST_Touches("a", "b"))
+      val actualResult = df.take(1)(0).getBoolean(0)
+      assert(actualResult)
+    }
+
+    it("Passed ST_Overlaps") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 0))') AS a, ST_GeomFromWKT('POLYGON ((0.5 1, 1.5 0, 2 0, 0.5 1))') AS b")
+      val df = baseDf.select(ST_Overlaps("a", "b"))
+      val actualResult = df.take(1)(0).getBoolean(0)
+      assert(actualResult)
+    }
+
+    it("Passed ST_Disjoint") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 0))') AS a, ST_GeomFromWKT('POLYGON ((2 0, 3 0, 3 1, 2 0))') AS b")
+      val df = baseDf.select(ST_Disjoint("a", "b"))
+      val actualResult = df.take(1)(0).getBoolean(0)
+      assert(actualResult)
+    }
+
+    it("Passed ST_OrderingEquals") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS a, ST_GeomFromWKT('LINESTRING (1 0, 0 0)') AS b")
+      val df = baseDf.select(ST_OrderingEquals("a", "b"))
+      val actualResult = df.take(1)(0).getBoolean(0)
+      assert(!actualResult)
+    }
+
+    // aggregates
+    it("Passed ST_Envelope_Aggr") {
+      val baseDf = sparkSession.sql("SELECT explode(array(ST_Point(0.0, 0.0), ST_Point(1.0, 1.0))) AS geom")
+      val df = baseDf.select(ST_Envelope_Aggr("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_Union_Aggr") {
+      val baseDf = sparkSession.sql("SELECT explode(array(ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))'), ST_GeomFromWKT('POLYGON ((1 0, 2 0, 2 1, 1 1, 1 0))'))) AS geom")
+      val df = baseDf.select(ST_Union_Aggr("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((1 0, 0 0, 0 1, 1 1, 2 1, 2 0, 1 0))"
+      assert(actualResult == expectedResult)
+    }
+
+    it("Passed ST_Intersection_Aggr") {
+      val baseDf = sparkSession.sql("SELECT explode(array(ST_GeomFromWKT('POLYGON ((0 0, 2 0, 2 1, 0 1, 0 0))'), ST_GeomFromWKT('POLYGON ((1 0, 3 0, 3 1, 1 1, 1 0))'))) AS geom")
+      val df = baseDf.select(ST_Intersection_Aggr("geom"))
+      val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+      val expectedResult = "POLYGON ((2 0, 1 0, 1 1, 2 1, 2 0))"
+      assert(actualResult == expectedResult)
+    }
+  }
+}