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/08/16 07:59:09 UTC

[incubator-sedona] branch master updated: [SEDONA-132] Move some functions to a common module (#647)

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 f2e61b85 [SEDONA-132] Move some functions to a common module (#647)
f2e61b85 is described below

commit f2e61b85dc95235bffd5aa49a8db35ad0735f1a7
Author: Adam Binford <ad...@gmail.com>
AuthorDate: Tue Aug 16 01:59:04 2022 -0600

    [SEDONA-132] Move some functions to a common module (#647)
---
 .../scripts/prepare_sparklyr_sedona_test_env.sh    |   2 +-
 R/tests/testthat/helper-initialize.R               |   2 +-
 common/.gitignore                                  |   9 +
 {core => common}/pom.xml                           |  35 +--
 common/src/.gitignore                              |   1 +
 common/src/main/.gitignore                         |   1 +
 .../java/org/apache/sedona/common/Functions.java   | 132 ++++++++++
 .../java/org/apache/sedona/common/utils/BBox.java  |  60 +++++
 .../org/apache/sedona/common}/utils/GeomUtils.java |   5 +-
 .../common/utils/GeometryGeoHashEncoder.java       |  30 ++-
 .../sedona/common/utils/PointGeoHashEncoder.java   |  94 +++++++
 core/pom.xml                                       |   6 +
 .../apache/sedona/core/geometryObjects/Circle.java |   2 +-
 .../sedona/core/spatialOperator/JoinQuery.java     |   2 +-
 .../apache/sedona/core/spatialRDD/SpatialRDD.java  |   2 +-
 .../shapefileParser/shapes/GeometrySerdeTest.java  |   2 +-
 .../shapefileParser/shapes/ShapefileRDDTest.java   |   2 +-
 .../shapes/ShapefileReaderTest.java                |   2 +-
 .../sedona/core/geometryObjects/CircleTest.java    |   2 +-
 .../core/spatialRDD/SpatialRDDWriterTest.java      |   2 +-
 flink/pom.xml                                      |   5 +
 .../main/java/org/apache/sedona/flink/Catalog.java |   1 -
 .../apache/sedona/flink/expressions/Functions.java |  90 ++-----
 pom.xml                                            |   3 +
 sql/pom.xml                                        |   5 +
 .../sql/sedona_sql/expressions/Functions.scala     | 284 +++------------------
 .../expressions/NullSafeExpressions.scala          | 148 ++++++++++-
 .../expressions/geohash/GeoHashDecoder.scala       |  11 +-
 .../expressions/geohash/PointGeoHashEncoder.scala  | 101 --------
 .../sedona/sql/functions/geohash/Fixtures.scala    |   5 +-
 viz/pom.xml                                        |   5 +
 31 files changed, 561 insertions(+), 490 deletions(-)

diff --git a/.github/workflows/scripts/prepare_sparklyr_sedona_test_env.sh b/.github/workflows/scripts/prepare_sparklyr_sedona_test_env.sh
index 7a25f0e5..dc72029c 100644
--- a/.github/workflows/scripts/prepare_sparklyr_sedona_test_env.sh
+++ b/.github/workflows/scripts/prepare_sparklyr_sedona_test_env.sh
@@ -19,7 +19,7 @@
 
 sedona_jar_files () {
   local subdir
-  for subdir in 'core' 'sql' 'viz'; do
+  for subdir in 'common' 'core' 'sql' 'viz'; do
     local artifact_id="$(
       mvn \
         org.apache.maven.plugins:maven-help-plugin:3.2.0:evaluate \
diff --git a/R/tests/testthat/helper-initialize.R b/R/tests/testthat/helper-initialize.R
index 4ca1eefe..521d800a 100644
--- a/R/tests/testthat/helper-initialize.R
+++ b/R/tests/testthat/helper-initialize.R
@@ -114,7 +114,7 @@ expect_geom_equal <- function(sc, lhs, rhs) {
     testthat::expect_true(
       invoke_static(
         sc,
-        "org.apache.sedona.core.utils.GeomUtils",
+        "org.apache.sedona.common.utils.GeomUtils",
         "equalsExactGeom",
         lhs[[i]],
         rhs[[i]]
diff --git a/common/.gitignore b/common/.gitignore
new file mode 100644
index 00000000..79869dd0
--- /dev/null
+++ b/common/.gitignore
@@ -0,0 +1,9 @@
+/target/
+/.settings/
+/.classpath
+/.project
+/dependency-reduced-pom.xml
+/doc/
+/.idea/
+*.iml
+/latest/
diff --git a/core/pom.xml b/common/pom.xml
similarity index 52%
copy from core/pom.xml
copy to common/pom.xml
index 336eb293..ffd5de3e 100644
--- a/core/pom.xml
+++ b/common/pom.xml
@@ -22,13 +22,13 @@
     <parent>
         <groupId>org.apache.sedona</groupId>
         <artifactId>sedona-parent</artifactId>
-        <version>1.3.0-incubating-SNAPSHOT</version>
+        <version>1.2.1-incubating-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
-    <artifactId>sedona-core-${spark.compat.version}_${scala.compat.version}</artifactId>
+    <artifactId>sedona-common</artifactId>
 
     <name>${project.groupId}:${project.artifactId}</name>
-    <description>A cluster computing system for processing large-scale spatial data: RDD API. Apache Sedona is an effort undergoing incubation at The Apache Software Foundation (ASF), sponsored by the Apache Incubator. Incubation is required of all newly accepted projects until a further review indicates that the infrastructure, communications, and decision making process have stabilized in a manner consistent with other successful ASF projects. While incubation status is not necessarily [...]
+    <description>A cluster computing system for processing large-scale spatial data: Common API. Apache Sedona is an effort undergoing incubation at The Apache Software Foundation (ASF), sponsored by the Apache Incubator. Incubation is required of all newly accepted projects until a further review indicates that the infrastructure, communications, and decision making process have stabilized in a manner consistent with other successful ASF projects. While incubation status is not necessar [...]
     <url>http://sedona.apache.org/</url>
     <packaging>jar</packaging>
 
@@ -37,35 +37,6 @@
     </properties>
 
     <dependencies>
-        <!-- Test -->
-        <dependency>
-            <groupId>org.apache.hadoop</groupId>
-            <artifactId>hadoop-minicluster</artifactId>
-            <version>${hadoop.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-all</artifactId>
-            <version>1.8.5</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.geotools</groupId>
-            <artifactId>gt-shapefile</artifactId>
-            <version>${geotools.version}</version>
-            <scope>test</scope>
-            <exclusions>
-                <exclusion>
-                    <groupId>org.locationtech.jts</groupId>
-                    <artifactId>jts-core</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>com.fasterxml.jackson.core</groupId>
-                    <artifactId>*</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
     </dependencies>
     <build>
         <sourceDirectory>src/main/java</sourceDirectory>
diff --git a/common/src/.gitignore b/common/src/.gitignore
new file mode 100644
index 00000000..5509140f
--- /dev/null
+++ b/common/src/.gitignore
@@ -0,0 +1 @@
+*.DS_Store
diff --git a/common/src/main/.gitignore b/common/src/main/.gitignore
new file mode 100644
index 00000000..5509140f
--- /dev/null
+++ b/common/src/main/.gitignore
@@ -0,0 +1 @@
+*.DS_Store
diff --git a/common/src/main/java/org/apache/sedona/common/Functions.java b/common/src/main/java/org/apache/sedona/common/Functions.java
new file mode 100644
index 00000000..7ce2dc30
--- /dev/null
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -0,0 +1,132 @@
+/**
+ * Licensed 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.common;
+
+import java.util.Optional;
+import org.apache.sedona.common.utils.GeomUtils;
+import org.apache.sedona.common.utils.GeometryGeoHashEncoder;
+import org.geotools.geometry.jts.JTS;
+import org.geotools.referencing.CRS;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.LineString;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+
+
+
+public class Functions {
+    public static Geometry buffer(Geometry geometry, double radius) {
+        return geometry.buffer(radius);
+    }
+
+    public static double distance(Geometry left, Geometry right) {
+        return left.distance(right);
+    }
+
+    public static double xMin(Geometry geometry) {
+        Coordinate[] points = geometry.getCoordinates();
+        double min = Double.MAX_VALUE;
+        for(int i=0; i < points.length; i++){
+            min = Math.min(points[i].getX(), min);
+        }
+        return min;
+    }
+    
+    public static double xMax(Geometry geometry) {
+        Coordinate[] points = geometry.getCoordinates();
+        double max = Double.MIN_VALUE;
+        for (int i=0; i < points.length; i++) {
+            max = Math.max(points[i].getX(), max);
+        }
+        return max;
+    }
+
+    public static double yMin(Geometry geometry) {
+        Coordinate[] points = geometry.getCoordinates();
+        double min = Double.MAX_VALUE;
+        for(int i=0; i < points.length; i++){
+            min = Math.min(points[i].getY(), min);
+        }
+        return min;
+    }
+    
+    public static double yMax(Geometry geometry) {
+        Coordinate[] points = geometry.getCoordinates();
+        double max = Double.MIN_VALUE;
+        for (int i=0; i < points.length; i++) {
+            max = Math.max(points[i].getY(), max);
+        }
+        return max;
+    }
+
+    public static Geometry transform(Geometry geometry, String sourceCRS, String targetCRS)
+        throws FactoryException, TransformException {
+        return transform(geometry, sourceCRS, targetCRS, false);
+    }
+
+    public static Geometry transform(Geometry geometry, String sourceCRS, String targetCRS, boolean lenient)
+        throws FactoryException, TransformException {
+        CoordinateReferenceSystem sourceCRScode = CRS.decode(sourceCRS);
+        CoordinateReferenceSystem targetCRScode = CRS.decode(targetCRS);
+        MathTransform transform = CRS.findMathTransform(sourceCRScode, targetCRScode, lenient);
+        return JTS.transform(geometry, transform);
+    }
+
+    public static Geometry flipCoordinates(Geometry geometry) {
+        GeomUtils.flipCoordinates(geometry);
+        return geometry;
+    }
+
+    public static String geohash(Geometry geometry, int precision) {
+        return GeometryGeoHashEncoder.calculate(geometry, precision);   
+    }
+
+    public static Geometry pointOnSurface(Geometry geometry) {
+        return GeomUtils.getInteriorPoint(geometry);
+    }
+
+    public static Geometry reverse(Geometry geometry) {
+        return geometry.reverse();
+    }
+
+    public static Geometry pointN(Geometry geometry, int n) {
+        if(!(geometry instanceof LineString)) {
+            return null;
+        }
+        return GeomUtils.getNthPoint((LineString)geometry, n);
+    }
+
+    public static Geometry exteriorRing(Geometry geometry) {
+        return GeomUtils.getExteriorRing(geometry);
+    }
+
+    public static String asEWKT(Geometry geometry) {
+        return GeomUtils.getEWKT(geometry);
+    }
+
+    public static Geometry force2D(Geometry geometry) {
+        return GeomUtils.get2dGeom(geometry);
+    }
+
+    public static boolean isEmpty(Geometry geometry) {
+        return geometry.isEmpty();
+    }
+
+    public static Geometry buildArea(Geometry geometry) {
+        return GeomUtils.buildArea(geometry);
+    }
+}
diff --git a/common/src/main/java/org/apache/sedona/common/utils/BBox.java b/common/src/main/java/org/apache/sedona/common/utils/BBox.java
new file mode 100644
index 00000000..898ccee5
--- /dev/null
+++ b/common/src/main/java/org/apache/sedona/common/utils/BBox.java
@@ -0,0 +1,60 @@
+/*
+ * 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.common.utils;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+
+public class BBox {
+    double startLon;
+    double endLon;
+    double startLat;
+    double endLat;
+
+    static GeometryFactory geometryFactory = new GeometryFactory();
+
+    public BBox(double startLon, double endLon, double startLat, double endLat) {
+        this.startLon = startLon;
+        this.endLon = endLon;
+        this.startLat = startLat;
+        this.endLat = endLat;
+    }
+
+    public BBox(BBox other) {
+        this(other.startLon, other.endLon, other.startLat, other.endLat);
+    }
+
+    public Point getCentroid() {
+        double lon = this.startLon + ((this.startLon + this.endLon)/2);
+        double lat = this.startLat + ((this.startLat + this.endLat)/2);
+        return geometryFactory.createPoint(new Coordinate(lon, lat));
+    }
+
+    public Polygon toPolygon() {
+        return geometryFactory.createPolygon(new Coordinate[] {
+            new Coordinate(this.startLon, this.startLat),
+            new Coordinate(this.startLon, this.endLat),
+            new Coordinate(this.endLon, this.endLat),
+            new Coordinate(this.endLon, this.startLat),
+            new Coordinate(this.startLon, this.startLat)
+        });
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/sedona/core/utils/GeomUtils.java b/common/src/main/java/org/apache/sedona/common/utils/GeomUtils.java
similarity index 99%
rename from core/src/main/java/org/apache/sedona/core/utils/GeomUtils.java
rename to common/src/main/java/org/apache/sedona/common/utils/GeomUtils.java
index f5b9b119..857ca1d2 100644
--- a/core/src/main/java/org/apache/sedona/core/utils/GeomUtils.java
+++ b/common/src/main/java/org/apache/sedona/common/utils/GeomUtils.java
@@ -11,7 +11,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sedona.core.utils;
+package org.apache.sedona.common.utils;
 
 import org.locationtech.jts.geom.*;
 import org.locationtech.jts.geom.impl.CoordinateArraySequence;
@@ -27,8 +27,7 @@ import java.util.*;
 
 import static org.locationtech.jts.geom.Coordinate.NULL_ORDINATE;
 
-public class GeomUtils
-{
+public class GeomUtils {
     public static String printGeom(Geometry geom) {
         if(geom.getUserData()!=null) return geom.toText() + "\t" + geom.getUserData();
         else return geom.toText();
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/geohash/GeometryGeoHashEncoder.scala b/common/src/main/java/org/apache/sedona/common/utils/GeometryGeoHashEncoder.java
similarity index 51%
rename from sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/geohash/GeometryGeoHashEncoder.scala
rename to common/src/main/java/org/apache/sedona/common/utils/GeometryGeoHashEncoder.java
index f1ecbc19..d1e8e1a7 100644
--- a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/geohash/GeometryGeoHashEncoder.scala
+++ b/common/src/main/java/org/apache/sedona/common/utils/GeometryGeoHashEncoder.java
@@ -16,22 +16,28 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.spark.sql.sedona_sql.expressions.geohash
+package org.apache.sedona.common.utils;
 
-import org.locationtech.jts.geom.{Coordinate, Geometry, GeometryFactory}
+import java.util.Optional;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
 
-object GeometryGeoHashEncoder {
-  private val geometryFactory = new GeometryFactory()
-  def calculate(geom: Geometry, precision: Int): Option[String] = {
-    val gbox = geom.getEnvelope.getEnvelopeInternal
+public class GeometryGeoHashEncoder {
+  private static GeometryFactory geometryFactory = new GeometryFactory();
+
+  public static String calculate(Geometry geom, int precision) {
+    Envelope gbox = geom.getEnvelope().getEnvelopeInternal();
     // Latitude can take values in [-90, 90]
     // Longitude can take values in [-180, 180]
-    if (gbox.getMinX < -180 || gbox.getMinY < -90 || gbox.getMaxX > 180 || gbox.getMaxY > 90) None
-    else {
-      val lon = gbox.getMinX + (gbox.getMaxX - gbox.getMinX) / 2
-      val lat = gbox.getMinY + (gbox.getMaxY - gbox.getMinY) / 2
-
-      Some(PointGeoHashEncoder.calculateGeoHash(geometryFactory.createPoint(new Coordinate(lon, lat)), precision))
+    if (gbox.getMinX() < -180 || gbox.getMinY() < -90 || gbox.getMaxX() > 180 || gbox.getMaxY() > 90) {
+        return null;
     }
+
+    double lon = gbox.getMinX() + (gbox.getMaxX() - gbox.getMinX()) / 2;
+    double lat = gbox.getMinY() + (gbox.getMaxY() - gbox.getMinY()) / 2;
+
+    return PointGeoHashEncoder.calculateGeoHash(geometryFactory.createPoint(new Coordinate(lon, lat)), precision);
   }
 }
diff --git a/common/src/main/java/org/apache/sedona/common/utils/PointGeoHashEncoder.java b/common/src/main/java/org/apache/sedona/common/utils/PointGeoHashEncoder.java
new file mode 100644
index 00000000..2305f2ea
--- /dev/null
+++ b/common/src/main/java/org/apache/sedona/common/utils/PointGeoHashEncoder.java
@@ -0,0 +1,94 @@
+/*
+ * 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.common.utils;
+
+import org.locationtech.jts.geom.Point;
+
+
+public class PointGeoHashEncoder {
+    private static String base32 = "0123456789bcdefghjkmnpqrstuvwxyz";
+    private static int[] bits = new int[] { 16, 8, 4, 2, 1 };
+
+    public static String calculateGeoHash(Point geom, long precision) {
+        BBox bbox = new BBox(-180, 180, -90, 90);
+        long precisionUpdated = Math.min(precision, 20);
+        if (precision <= 0) {
+            return "";
+        }
+        return geoHashAggregate(geom, precisionUpdated, 0, "", true, bbox, 0, 0);
+
+    }
+
+    private static String geoHashAggregate(Point point, long precision, long currentPrecision,
+        String geoHash, boolean isEven, BBox bbox, int bit, int ch) {
+        if (currentPrecision >= precision) {
+            return geoHash;
+        }
+        
+        BBox updatedBbox = null;
+        int updatedCh = -1;
+        if (isEven) {
+            double mid = (bbox.startLon + bbox.endLon) / 2.0;
+            if (point.getX() >= mid) {
+                updatedBbox = new BBox(bbox);
+                updatedBbox.startLon = mid;
+                updatedCh = ch | bits[bit];
+            } else {
+                updatedBbox = new BBox(bbox);
+                updatedBbox.endLon = mid;
+                updatedCh = ch;
+            }
+        } else {
+            double mid = (bbox.startLat + bbox.endLat) / 2.0;
+            if (point.getY() >= mid) {
+                updatedBbox = new BBox(bbox);
+                updatedBbox.startLat = mid;
+                updatedCh = ch | bits[bit];
+            } else {
+                updatedBbox = new BBox(bbox);
+                updatedBbox.endLat = mid;
+                updatedCh = ch;
+            }
+        }
+        if (bit < 4) {
+            return geoHashAggregate(
+                point,
+                precision,
+                currentPrecision,
+                geoHash,
+                !isEven,
+                updatedBbox,
+                bit + 1,
+                updatedCh
+            );
+        } else {
+            String geoHashUpdated = geoHash + base32.charAt(updatedCh);
+            return geoHashAggregate(
+                point,
+                precision,
+                currentPrecision + 1,
+                geoHashUpdated,
+                !isEven,
+                updatedBbox,
+                0,
+                0
+            );
+        }
+    }
+}
diff --git a/core/pom.xml b/core/pom.xml
index 336eb293..5908d3f1 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -37,6 +37,12 @@
     </properties>
 
     <dependencies>
+        <dependency>
+            <groupId>org.apache.sedona</groupId>
+            <artifactId>sedona-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
         <!-- Test -->
         <dependency>
             <groupId>org.apache.hadoop</groupId>
diff --git a/core/src/main/java/org/apache/sedona/core/geometryObjects/Circle.java b/core/src/main/java/org/apache/sedona/core/geometryObjects/Circle.java
index 6e26c36e..b0f94cd6 100644
--- a/core/src/main/java/org/apache/sedona/core/geometryObjects/Circle.java
+++ b/core/src/main/java/org/apache/sedona/core/geometryObjects/Circle.java
@@ -19,7 +19,7 @@
 
 package org.apache.sedona.core.geometryObjects;
 
-import org.apache.sedona.core.utils.GeomUtils;
+import org.apache.sedona.common.utils.GeomUtils;
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.CoordinateFilter;
 import org.locationtech.jts.geom.CoordinateSequenceComparator;
diff --git a/core/src/main/java/org/apache/sedona/core/spatialOperator/JoinQuery.java b/core/src/main/java/org/apache/sedona/core/spatialOperator/JoinQuery.java
index 78aa017d..46be5284 100644
--- a/core/src/main/java/org/apache/sedona/core/spatialOperator/JoinQuery.java
+++ b/core/src/main/java/org/apache/sedona/core/spatialOperator/JoinQuery.java
@@ -22,6 +22,7 @@ package org.apache.sedona.core.spatialOperator;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
+import org.apache.sedona.common.utils.GeomUtils;
 import org.apache.sedona.core.enums.IndexType;
 import org.apache.sedona.core.enums.JoinBuildSide;
 import org.apache.sedona.core.geometryObjects.Circle;
@@ -34,7 +35,6 @@ import org.apache.sedona.core.monitoring.Metric;
 import org.apache.sedona.core.spatialPartitioning.SpatialPartitioner;
 import org.apache.sedona.core.spatialRDD.CircleRDD;
 import org.apache.sedona.core.spatialRDD.SpatialRDD;
-import org.apache.sedona.core.utils.GeomUtils;
 import org.apache.spark.SparkContext;
 import org.apache.spark.api.java.JavaPairRDD;
 import org.apache.spark.api.java.JavaRDD;
diff --git a/core/src/main/java/org/apache/sedona/core/spatialRDD/SpatialRDD.java b/core/src/main/java/org/apache/sedona/core/spatialRDD/SpatialRDD.java
index 0044e3b9..4f1123d5 100644
--- a/core/src/main/java/org/apache/sedona/core/spatialRDD/SpatialRDD.java
+++ b/core/src/main/java/org/apache/sedona/core/spatialRDD/SpatialRDD.java
@@ -21,13 +21,13 @@ package org.apache.sedona.core.spatialRDD;
 
 import org.apache.commons.lang.NullArgumentException;
 import org.apache.log4j.Logger;
+import org.apache.sedona.common.utils.GeomUtils;
 import org.apache.sedona.core.enums.GridType;
 import org.apache.sedona.core.enums.IndexType;
 import org.apache.sedona.core.spatialPartitioning.*;
 import org.apache.sedona.core.spatialPartitioning.quadtree.StandardQuadTree;
 import org.apache.sedona.core.spatialRddTool.IndexBuilder;
 import org.apache.sedona.core.spatialRddTool.StatCalculator;
-import org.apache.sedona.core.utils.GeomUtils;
 import org.apache.sedona.core.utils.RDDSampleUtils;
 import org.apache.spark.api.java.JavaRDD;
 import org.apache.spark.api.java.function.FlatMapFunction;
diff --git a/core/src/test/java/org/apache/sedona/core/formatMapper/shapefileParser/shapes/GeometrySerdeTest.java b/core/src/test/java/org/apache/sedona/core/formatMapper/shapefileParser/shapes/GeometrySerdeTest.java
index 3bb9af4a..5fdb60ee 100644
--- a/core/src/test/java/org/apache/sedona/core/formatMapper/shapefileParser/shapes/GeometrySerdeTest.java
+++ b/core/src/test/java/org/apache/sedona/core/formatMapper/shapefileParser/shapes/GeometrySerdeTest.java
@@ -22,9 +22,9 @@ package org.apache.sedona.core.formatMapper.shapefileParser.shapes;
 import com.esotericsoftware.kryo.Kryo;
 import com.esotericsoftware.kryo.io.Input;
 import com.esotericsoftware.kryo.io.Output;
+import org.apache.sedona.common.utils.GeomUtils;
 import org.apache.sedona.core.geometryObjects.Circle;
 import org.apache.sedona.core.geometryObjects.GeometrySerde;
-import org.apache.sedona.core.utils.GeomUtils;
 import org.junit.Test;
 import org.locationtech.jts.geom.Geometry;
 import org.locationtech.jts.geom.GeometryCollection;
diff --git a/core/src/test/java/org/apache/sedona/core/formatMapper/shapefileParser/shapes/ShapefileRDDTest.java b/core/src/test/java/org/apache/sedona/core/formatMapper/shapefileParser/shapes/ShapefileRDDTest.java
index c0bf3c94..488ab8df 100644
--- a/core/src/test/java/org/apache/sedona/core/formatMapper/shapefileParser/shapes/ShapefileRDDTest.java
+++ b/core/src/test/java/org/apache/sedona/core/formatMapper/shapefileParser/shapes/ShapefileRDDTest.java
@@ -20,13 +20,13 @@ package org.apache.sedona.core.formatMapper.shapefileParser.shapes;
 
 import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
+import org.apache.sedona.common.utils.GeomUtils;
 import org.apache.sedona.core.formatMapper.shapefileParser.ShapefileRDD;
 import org.apache.sedona.core.formatMapper.shapefileParser.boundary.BoundBox;
 import org.apache.sedona.core.spatialOperator.RangeQuery;
 import org.apache.sedona.core.spatialRDD.LineStringRDD;
 import org.apache.sedona.core.spatialRDD.PointRDD;
 import org.apache.sedona.core.spatialRDD.PolygonRDD;
-import org.apache.sedona.core.utils.GeomUtils;
 import org.apache.spark.SparkConf;
 import org.apache.spark.api.java.JavaSparkContext;
 import org.geotools.data.DataStore;
diff --git a/core/src/test/java/org/apache/sedona/core/formatMapper/shapefileParser/shapes/ShapefileReaderTest.java b/core/src/test/java/org/apache/sedona/core/formatMapper/shapefileParser/shapes/ShapefileReaderTest.java
index 6663e689..904381bf 100644
--- a/core/src/test/java/org/apache/sedona/core/formatMapper/shapefileParser/shapes/ShapefileReaderTest.java
+++ b/core/src/test/java/org/apache/sedona/core/formatMapper/shapefileParser/shapes/ShapefileReaderTest.java
@@ -26,6 +26,7 @@ import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.fs.RemoteIterator;
 import org.apache.hadoop.hdfs.HdfsConfiguration;
 import org.apache.hadoop.hdfs.MiniDFSCluster;
+import org.apache.sedona.common.utils.GeomUtils;
 import org.apache.sedona.core.TestBase;
 import org.apache.sedona.core.formatMapper.shapefileParser.ShapefileReader;
 import org.apache.sedona.core.formatMapper.shapefileParser.boundary.BoundBox;
@@ -34,7 +35,6 @@ import org.apache.sedona.core.spatialRDD.LineStringRDD;
 import org.apache.sedona.core.spatialRDD.PointRDD;
 import org.apache.sedona.core.spatialRDD.PolygonRDD;
 import org.apache.sedona.core.spatialRDD.SpatialRDD;
-import org.apache.sedona.core.utils.GeomUtils;
 import org.geotools.data.DataStore;
 import org.geotools.data.DataStoreFinder;
 import org.geotools.data.FeatureSource;
diff --git a/core/src/test/java/org/apache/sedona/core/geometryObjects/CircleTest.java b/core/src/test/java/org/apache/sedona/core/geometryObjects/CircleTest.java
index 7ef7c3cd..bd5fd416 100644
--- a/core/src/test/java/org/apache/sedona/core/geometryObjects/CircleTest.java
+++ b/core/src/test/java/org/apache/sedona/core/geometryObjects/CircleTest.java
@@ -18,7 +18,7 @@
  */
 package org.apache.sedona.core.geometryObjects;
 
-import org.apache.sedona.core.utils.GeomUtils;
+import org.apache.sedona.common.utils.GeomUtils;
 import org.junit.Test;
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.Envelope;
diff --git a/core/src/test/java/org/apache/sedona/core/spatialRDD/SpatialRDDWriterTest.java b/core/src/test/java/org/apache/sedona/core/spatialRDD/SpatialRDDWriterTest.java
index 8931f0c9..0e22c3f7 100644
--- a/core/src/test/java/org/apache/sedona/core/spatialRDD/SpatialRDDWriterTest.java
+++ b/core/src/test/java/org/apache/sedona/core/spatialRDD/SpatialRDDWriterTest.java
@@ -21,8 +21,8 @@ package org.apache.sedona.core.spatialRDD;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang.NullArgumentException;
+import org.apache.sedona.common.utils.GeomUtils;
 import org.apache.sedona.core.enums.FileDataSplitter;
-import org.apache.sedona.core.utils.GeomUtils;
 import org.apache.spark.storage.StorageLevel;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
diff --git a/flink/pom.xml b/flink/pom.xml
index abd838b1..7bdbd409 100644
--- a/flink/pom.xml
+++ b/flink/pom.xml
@@ -38,6 +38,11 @@
     </properties>
 
     <dependencies>
+        <dependency>
+            <groupId>org.apache.sedona</groupId>
+            <artifactId>sedona-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
         <dependency>
             <groupId>org.apache.sedona</groupId>
             <artifactId>sedona-core-${spark.compat.version}_${scala.compat.version}</artifactId>
diff --git a/flink/src/main/java/org/apache/sedona/flink/Catalog.java b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
index 81c2a457..3d8dd88a 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -14,7 +14,6 @@
 package org.apache.sedona.flink;
 
 import org.apache.flink.table.functions.UserDefinedFunction;
-import org.apache.sedona.core.spatialPartitioning.PartitioningUtils;
 import org.apache.sedona.flink.expressions.*;
 
 public class Catalog {
diff --git a/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java b/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
index c0542afa..3ca77b8b 100644
--- a/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
+++ b/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
@@ -15,15 +15,13 @@ package org.apache.sedona.flink.expressions;
 
 import org.apache.flink.table.annotation.DataTypeHint;
 import org.apache.flink.table.functions.ScalarFunction;
-import org.apache.sedona.core.utils.GeomUtils;
-import org.locationtech.jts.io.WKTWriter;
-import org.apache.spark.sql.sedona_sql.expressions.geohash.GeometryGeoHashEncoder;
-import org.apache.spark.sql.sedona_sql.expressions.geohash.PointGeoHashEncoder;
+import org.apache.sedona.common.utils.GeomUtils;
 import org.geotools.geometry.jts.JTS;
 import org.geotools.referencing.CRS;
+import org.locationtech.jts.io.WKTWriter;
+import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.Geometry;
 import org.locationtech.jts.geom.LineString;
-import org.locationtech.jts.geom.Coordinate;
 import org.opengis.referencing.FactoryException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.MathTransform;
@@ -39,7 +37,7 @@ public class Functions {
         @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
         public Geometry eval(Object o, @DataTypeHint("Double") Double radius) {
             Geometry geom = (Geometry) o;
-            return geom.buffer(radius);
+            return org.apache.sedona.common.Functions.buffer(geom, radius);
         }
     }
 
@@ -49,7 +47,7 @@ public class Functions {
                 @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o2) {
             Geometry geom1 = (Geometry) o1;
             Geometry geom2 = (Geometry) o2;
-            return geom1.distance(geom2);
+            return org.apache.sedona.common.Functions.distance(geom1, geom2);
         }
     }
 
@@ -57,12 +55,7 @@ public class Functions {
         @DataTypeHint("Double")
         public Double eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o){
             Geometry geom = (Geometry) o;
-            Coordinate[] points= geom.getCoordinates();
-            double min=Double.MAX_VALUE;
-            for(int i=0;i<points.length;i++){
-                min=Math.min(points[i].getY(),min);
-            }
-            return min;
+            return org.apache.sedona.common.Functions.yMin(geom);
         }
     }
 
@@ -70,29 +63,17 @@ public class Functions {
         @DataTypeHint("Double")
         public Double eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o){
             Geometry geom = (Geometry) o;
-            Coordinate[] points= geom.getCoordinates();
-            double max=Double.MIN_VALUE;
-            for(int i=0;i<points.length;i++){
-                max=Math.max(points[i].getY(),max);
-            }
-            return max;
+            return org.apache.sedona.common.Functions.yMax(geom);
         }
     }
 
 
     public static class ST_Transform extends ScalarFunction {
         @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
-        public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o, @DataTypeHint("String") String sourceCRS, @DataTypeHint("String") String targetCRS) {
+        public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o, @DataTypeHint("String") String sourceCRS, @DataTypeHint("String") String targetCRS)
+            throws FactoryException, TransformException {
             Geometry geom = (Geometry) o;
-            try {
-                CoordinateReferenceSystem sourceCRScode = CRS.decode(sourceCRS);
-                CoordinateReferenceSystem targetCRScode = CRS.decode(targetCRS);
-                MathTransform transform = CRS.findMathTransform(sourceCRScode, targetCRScode);
-                geom = JTS.transform(geom, transform);
-            } catch (FactoryException | TransformException e) {
-                e.printStackTrace();
-            }
-            return geom;
+            return org.apache.sedona.common.Functions.transform(geom, sourceCRS, targetCRS);
         }
     }
 
@@ -100,8 +81,7 @@ public class Functions {
         @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
         public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o) {
             Geometry geom = (Geometry) o;
-            GeomUtils.flipCoordinates(geom);
-            return geom;
+            return org.apache.sedona.common.Functions.flipCoordinates(geom);
         }
     }
 
@@ -109,11 +89,7 @@ public class Functions {
         @DataTypeHint("RAW")
         public Optional<String> eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object geometry, Integer precision) {
             Geometry geom = (Geometry) geometry;
-            Option<String> geoHash = GeometryGeoHashEncoder.calculate(geom, precision);
-            if (geoHash.isDefined()){
-                return Optional.of(geoHash.get());
-            }
-            return Optional.empty();
+            return Optional.ofNullable(org.apache.sedona.common.Functions.geohash(geom, precision));
         }
     }
 
@@ -121,8 +97,7 @@ public class Functions {
         @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
         public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o) {
             Geometry geom = (Geometry) o;
-            GeomUtils.getInteriorPoint(geom);
-            return geom;
+            return org.apache.sedona.common.Functions.pointOnSurface(geom);
         }
     }
 
@@ -130,26 +105,23 @@ public class Functions {
         @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
         public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o) {
             Geometry geom = (Geometry) o;
-            return geom.reverse();
+            return org.apache.sedona.common.Functions.reverse(geom);
         }
     }
 
     public static class ST_PointN extends ScalarFunction {
         @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
         public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o, int n) {
-            if(!(o instanceof LineString)) {
-                return null;
-            }
-            LineString lineString = (LineString) o;
-            return GeomUtils.getNthPoint(lineString, n);
+            Geometry geom = (Geometry) o;
+            return org.apache.sedona.common.Functions.pointN(geom, n);
         }
     }
 
     public static class ST_ExteriorRing extends ScalarFunction {
         @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
         public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o) {
-            Geometry geometry = (Geometry) o;
-            return GeomUtils.getExteriorRing(geometry);
+            Geometry geom = (Geometry) o;
+            return org.apache.sedona.common.Functions.exteriorRing(geom);
         }
     }
 
@@ -157,7 +129,7 @@ public class Functions {
         @DataTypeHint("String")
         public String eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o) {
             Geometry geom = (Geometry) o;
-            return GeomUtils.getEWKT(geom);
+            return org.apache.sedona.common.Functions.asEWKT(geom);
         }
     }
 
@@ -165,7 +137,7 @@ public class Functions {
         @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
         public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o) {
             Geometry geom = (Geometry) o;
-            return GeomUtils.get2dGeom(geom);
+            return org.apache.sedona.common.Functions.force2D(geom);
         }
     }
 
@@ -173,7 +145,7 @@ public class Functions {
         @DataTypeHint("Boolean")
         public boolean eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o) {
             Geometry geom = (Geometry) o;
-            return geom.isEmpty();
+            return org.apache.sedona.common.Functions.isEmpty(geom);
         }
     }
 
@@ -181,14 +153,7 @@ public class Functions {
         @DataTypeHint("Double")
         public Double eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o) {
             Geometry geom = (Geometry) o;
-            Coordinate[] coord = geom.getCoordinates();
-            double max = Double.MIN_VALUE;
-            for (int i = 0; i < coord.length; i++) {
-                if (coord[i].getX() > max) {
-                    max = coord[i].getX();
-                }
-            }
-            return max;
+            return org.apache.sedona.common.Functions.xMax(geom);
         }
     }
 
@@ -196,14 +161,7 @@ public class Functions {
         @DataTypeHint("Double")
         public Double eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o) {
             Geometry geom = (Geometry) o;
-            Coordinate[] coord = geom.getCoordinates();
-            double min = Double.MAX_VALUE;
-            for(int i=0;i< coord.length;i++){
-                if(coord[i].getX()<min){
-                    min = coord[i].getX();
-                }
-            }
-            return min;
+            return org.apache.sedona.common.Functions.xMin(geom);
         }
     }
 
@@ -211,7 +169,7 @@ public class Functions {
         @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
         public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o) {
             Geometry geom = (Geometry) o;
-            return GeomUtils.buildArea(geom);
+            return org.apache.sedona.common.Functions.buildArea(geom);
         }
     }
 }
diff --git a/pom.xml b/pom.xml
index d92b7018..aae83e16 100644
--- a/pom.xml
+++ b/pom.xml
@@ -558,6 +558,7 @@
                 <scaladoc.arg>-no-java-comments</scaladoc.arg>
             </properties>
             <modules>
+                <module>common</module>
                 <module>core</module>
                 <module>sql</module>
                 <module>viz</module>
@@ -579,6 +580,7 @@
                 <scaladoc.arg>-no-java-comments</scaladoc.arg>
             </properties>
             <modules>
+                <module>common</module>
                 <module>core</module>
                 <module>sql</module>
                 <module>viz</module>
@@ -601,6 +603,7 @@
                 <scaladoc.arg />
             </properties>
             <modules>
+                <module>common</module>
                 <module>core</module>
                 <module>sql</module>
                 <module>viz</module>
diff --git a/sql/pom.xml b/sql/pom.xml
index 781939d6..c3de7f99 100644
--- a/sql/pom.xml
+++ b/sql/pom.xml
@@ -37,6 +37,11 @@
     </properties>
 
     <dependencies>
+        <dependency>
+            <groupId>org.apache.sedona</groupId>
+            <artifactId>sedona-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
         <dependency>
             <groupId>org.apache.sedona</groupId>
             <artifactId>sedona-core-${spark.compat.version}_${scala.compat.version}</artifactId>
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
index 738e8001..a4097f18 100644
--- a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
+++ b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
@@ -18,17 +18,18 @@
  */
 package org.apache.spark.sql.sedona_sql.expressions
 
+import org.apache.sedona.common.Functions
+import org.apache.sedona.common.utils.GeomUtils
 import org.apache.sedona.core.geometryObjects.Circle
-import org.apache.sedona.core.utils.GeomUtils
 import org.apache.sedona.sql.utils.GeometrySerializer
 import org.apache.spark.internal.Logging
 import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.catalyst.expressions.codegen.{CodegenContext, CodegenFallback, ExprCode}
-import org.apache.spark.sql.catalyst.expressions.{BoundReference, Expression, Generator}
+import org.apache.spark.sql.catalyst.expressions._
 import org.apache.spark.sql.catalyst.util.{ArrayData, GenericArrayData}
 import org.apache.spark.sql.sedona_sql.UDT.GeometryUDT
 import org.apache.spark.sql.sedona_sql.expressions.collect.Collect
-import org.apache.spark.sql.sedona_sql.expressions.geohash.{GeoHashDecoder, GeometryGeoHashEncoder, InvalidGeoHashException}
+import org.apache.spark.sql.sedona_sql.expressions.geohash.{GeoHashDecoder, InvalidGeoHashException}
 import org.apache.spark.sql.sedona_sql.expressions.implicits._
 import org.apache.spark.sql.sedona_sql.expressions.subdivide.GeometrySubDivider
 import org.apache.spark.sql.types.{ArrayType, _}
@@ -61,18 +62,7 @@ import scala.util.{Failure, Success, Try}
   * @param inputExpressions This function takes two geometries and calculates the distance between two objects.
   */
 case class ST_Distance(inputExpressions: Seq[Expression])
-  extends BinaryGeometryExpression with CodegenFallback {
-  assert(inputExpressions.length == 2)
-
-  override def toString: String = s" **${ST_Distance.getClass.getName}**  "
-
-  override def nullSafeEval(leftGeometry: Geometry, rightGeometry: Geometry): Any = {
-    leftGeometry.distance(rightGeometry)
-  }
-
-  override def dataType = DoubleType
-
-  override def children: Seq[Expression] = inputExpressions
+  extends InferredBinaryExpression(Functions.distance) {
 
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
     copy(inputExpressions = newChildren)
@@ -81,21 +71,7 @@ case class ST_Distance(inputExpressions: Seq[Expression])
 
 
 case class ST_YMax(inputExpressions: Seq[Expression])
-  extends UnaryGeometryExpression with CodegenFallback {
-  assert(inputExpressions.length == 1)
-
-  override protected def nullSafeEval(geometry: Geometry): Any = {
-    val seqRev : Array[Coordinate] = geometry.getCoordinates()
-    var maxVal:Double = Double.MinValue
-    for(x <- seqRev ){
-      maxVal=Math.max(maxVal,x.getY())
-    }
-    maxVal
-  }
-
-  override def dataType: DataType = DoubleType
-
-  override def children: Seq[Expression] = inputExpressions
+  extends InferredUnaryExpression(Functions.yMax) {
 
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
     copy(inputExpressions = newChildren)
@@ -103,26 +79,13 @@ case class ST_YMax(inputExpressions: Seq[Expression])
 }
 
 case class ST_YMin(inputExpressions: Seq[Expression])
-  extends UnaryGeometryExpression with CodegenFallback {
-  assert(inputExpressions.length == 1)
-
-  override protected def nullSafeEval(geometry: Geometry): Any = {
-    val seqRev : Array[Coordinate] = geometry.getCoordinates()
-    var minVal:Double = Double.MaxValue
-    for(x <- seqRev){
-      minVal=Math.min(minVal,x.getY())
-    }
-    minVal
-  }
-
-  override def dataType: DataType = DoubleType
-
-  override def children: Seq[Expression] = inputExpressions
+  extends InferredUnaryExpression(Functions.yMin) {
 
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
     copy(inputExpressions = newChildren)
   }
 }
+
 case class ST_3DDistance(inputExpressions: Seq[Expression])
   extends BinaryGeometryExpression with CodegenFallback {
   assert(inputExpressions.length == 2)
@@ -191,26 +154,7 @@ case class ST_NPoints(inputExpressions: Seq[Expression])
   * @param inputExpressions
   */
 case class ST_Buffer(inputExpressions: Seq[Expression])
-  extends Expression with CodegenFallback {
-  assert(inputExpressions.length == 2)
-
-  override def nullable: Boolean = true
-
-  override def eval(input: InternalRow): Any = {
-    val buffer: Double = inputExpressions(1).eval(input) match {
-      case a: Decimal => a.toDouble
-      case a: Double => a
-      case a: Int => a
-    }
-    inputExpressions(0).toGeometry(input) match {
-      case geometry: Geometry => geometry.buffer(buffer).toGenericArrayData
-      case _ => null
-    }
-  }
-
-  override def dataType: DataType = GeometryUDT
-
-  override def children: Seq[Expression] = inputExpressions
+  extends InferredBinaryExpression(Functions.buffer) with CodegenFallback {
 
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
     copy(inputExpressions = newChildren)
@@ -318,22 +262,18 @@ case class ST_Transform(inputExpressions: Seq[Expression])
   override def nullable: Boolean = true
 
   override def eval(input: InternalRow): Any = {
-    val originalGeometry = inputExpressions(0).toGeometry(input)
+    val geometry = inputExpressions(0).toGeometry(input)
     val sourceCRS = inputExpressions(1).asString(input)
     val targetCRS = inputExpressions(2).asString(input)
 
-    (originalGeometry, sourceCRS, targetCRS) match {
-      case (originalGeometry: Geometry, sourceCRS: String, targetCRS: String) =>
-        val sourceCRScode = CRS.decode(sourceCRS)
-        val targetCRScode = CRS.decode(targetCRS)
-        var transform: MathTransform = null
-        if (inputExpressions.length == 4) {
-          transform = CRS.findMathTransform(sourceCRScode, targetCRScode, inputExpressions(3).eval(input).asInstanceOf[Boolean])
-        }
-        else {
-          transform = CRS.findMathTransform(sourceCRScode, targetCRScode, false)
+    (geometry, sourceCRS, targetCRS) match {
+      case (geometry: Geometry, sourceCRS: String, targetCRS: String) =>
+        val lenient = if (inputExpressions.length == 4) {
+          inputExpressions(3).eval(input).asInstanceOf[Boolean]
+        } else {
+          false
         }
-        JTS.transform(originalGeometry, transform).toGenericArrayData
+        Functions.transform(geometry, sourceCRS, targetCRS, lenient).toGenericArrayData
       case (_, _, _) => null
     }
   }
@@ -1027,19 +967,7 @@ case class ST_EndPoint(inputExpressions: Seq[Expression])
 }
 
 case class ST_ExteriorRing(inputExpressions: Seq[Expression])
-  extends UnaryGeometryExpression with CodegenFallback {
-
-  override protected def nullSafeEval(geometry: Geometry): Any = {
-    geometry match {
-      case polygon: Polygon => polygon.getExteriorRing.toGenericArrayData
-      case _ => null
-    }
-
-  }
-
-  override def dataType: DataType = GeometryUDT
-
-  override def children: Seq[Expression] = inputExpressions
+  extends InferredUnaryExpression(Functions.exteriorRing) {
 
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
     copy(inputExpressions = newChildren)
@@ -1333,17 +1261,7 @@ case class ST_NumGeometries(inputExpressions: Seq[Expression])
   * @param inputExpressions Geometry
   */
 case class ST_FlipCoordinates(inputExpressions: Seq[Expression])
-  extends UnaryGeometryExpression with CodegenFallback {
-  assert(inputExpressions.length == 1)
-
-  override protected def nullSafeEval(geometry: Geometry): Any = {
-    GeomUtils.flipCoordinates(geometry)
-    geometry.toGenericArrayData
-  }
-
-  override def dataType: DataType = GeometryUDT
-
-  override def children: Seq[Expression] = inputExpressions
+  extends InferredUnaryExpression(Functions.flipCoordinates) {
 
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
     copy(inputExpressions = newChildren)
@@ -1449,32 +1367,7 @@ case class ST_MakePolygon(inputExpressions: Seq[Expression])
 }
 
 case class ST_GeoHash(inputExpressions: Seq[Expression])
-  extends Expression with CodegenFallback {
-  assert(inputExpressions.length == 2)
-
-  override def nullable: Boolean = true
-
-  override def eval(input: InternalRow): Any = {
-    val geometry = inputExpressions(0).toGeometry(input)
-
-    val precision = inputExpressions(1).toInt(input)
-
-    geometry match {
-      case geom: Geometry =>
-        val geoHash = GeometryGeoHashEncoder.calculate(geom, precision)
-        geoHash match {
-          case Some(value) => UTF8String.fromString(value)
-          case None => null
-        }
-
-      case _ => null
-    }
-
-  }
-
-  override def dataType: DataType = StringType
-
-  override def children: Seq[Expression] = inputExpressions
+  extends InferredBinaryExpression(Functions.geohash) with CodegenFallback {
 
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
     copy(inputExpressions = newChildren)
@@ -1581,16 +1474,7 @@ case class ST_Multi(inputExpressions: Seq[Expression]) extends UnaryGeometryExpr
  * @param inputExpressions Geometry
  */
 case class ST_PointOnSurface(inputExpressions: Seq[Expression])
-  extends UnaryGeometryExpression with CodegenFallback {
-  assert(inputExpressions.length == 1)
-
-  override protected def nullSafeEval(geometry: Geometry): Any = {
-    new GenericArrayData(GeometrySerializer.serialize(GeomUtils.getInteriorPoint(geometry)))
-  }
-
-  override def dataType: DataType = GeometryUDT
-
-  override def children: Seq[Expression] = inputExpressions
+  extends InferredUnaryExpression(Functions.pointOnSurface) {
 
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
     copy(inputExpressions = newChildren)
@@ -1603,16 +1487,7 @@ case class ST_PointOnSurface(inputExpressions: Seq[Expression])
  * @param inputExpressions
  */
 case class ST_Reverse(inputExpressions: Seq[Expression])
-  extends UnaryGeometryExpression with CodegenFallback {
-  assert(inputExpressions.length == 1)
-
-  override protected def nullSafeEval(geometry: Geometry): Any = {
-    new GenericArrayData(GeometrySerializer.serialize(geometry.reverse()))
-  }
-
-  override def dataType: DataType = GeometryUDT
-
-  override def children: Seq[Expression] = inputExpressions
+  extends InferredUnaryExpression(Functions.reverse) {
 
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
     copy(inputExpressions = newChildren)
@@ -1625,30 +1500,7 @@ case class ST_Reverse(inputExpressions: Seq[Expression])
  * @param inputExpressions sequence of 2 input arguments, a geometry and a value 'n'
  */
 case class ST_PointN(inputExpressions: Seq[Expression])
-  extends Expression with CodegenFallback {
-  inputExpressions.validateLength(2)
-
-  override def nullable: Boolean = true
-
-  override def eval(input: InternalRow): Any = {
-    val geometry = inputExpressions.head.toGeometry(input)
-    val n = inputExpressions(1).toInt(input)
-    getNthPoint(geometry, n)
-  }
-
-  private def getNthPoint(geometry: Geometry, n: Int): GenericArrayData = {
-    geometry match {
-      case linestring: LineString => val point = GeomUtils.getNthPoint(linestring, n)
-        point match {
-          case geometry: Geometry => new GenericArrayData(GeometrySerializer.serialize(geometry))
-          case _ => null
-        }
-      case _ => null
-    }
-  }
-  override def dataType: DataType = GeometryUDT
-
-  override def children: Seq[Expression] = inputExpressions
+  extends InferredBinaryExpression(Functions.pointN) {
 
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
       copy(inputExpressions = newChildren)
@@ -1661,16 +1513,7 @@ case class ST_PointN(inputExpressions: Seq[Expression])
  * @param inputExpressions
  */
 case class ST_Force_2D(inputExpressions: Seq[Expression])
-  extends UnaryGeometryExpression with CodegenFallback {
-  assert(inputExpressions.length == 1)
-
-  override protected def nullSafeEval(geometry: Geometry): Any = {
-    new GenericArrayData(GeometrySerializer.serialize(GeomUtils.get2dGeom(geometry)))
-  }
-
-  override def dataType: DataType = GeometryUDT
-
-  override def children: Seq[Expression] = inputExpressions
+  extends InferredUnaryExpression(Functions.force2D) {
 
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
     copy(inputExpressions = newChildren)
@@ -1683,16 +1526,7 @@ case class ST_Force_2D(inputExpressions: Seq[Expression])
  * @param inputExpressions
  */
 case class ST_AsEWKT(inputExpressions: Seq[Expression])
-  extends UnaryGeometryExpression with CodegenFallback {
-  assert(inputExpressions.length == 1)
-
-  override protected def nullSafeEval(geometry: Geometry): Any = {
-    UTF8String.fromString(GeomUtils.getEWKT(geometry))
-  }
-
-  override def dataType: DataType = StringType
-
-  override def children: Seq[Expression] = inputExpressions
+  extends  InferredUnaryExpression(Functions.asEWKT) {
 
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
     copy(inputExpressions = newChildren)
@@ -1705,16 +1539,7 @@ case class ST_AsEWKT(inputExpressions: Seq[Expression])
  * @param inputExpressions
  */
 case class ST_IsEmpty(inputExpressions: Seq[Expression])
-  extends UnaryGeometryExpression with CodegenFallback {
-  assert(inputExpressions.length == 1)
-
-  override protected def nullSafeEval(geometry: Geometry): Any = {
-    geometry.isEmpty()
-  }
-
-  override def dataType: DataType = BooleanType
-
-  override def children: Seq[Expression] = inputExpressions
+  extends InferredUnaryExpression(Functions.isEmpty) {
 
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
     copy(inputExpressions = newChildren)
@@ -1727,25 +1552,7 @@ case class ST_IsEmpty(inputExpressions: Seq[Expression])
  * @param inputExpressions
  */
 case class ST_XMax(inputExpressions: Seq[Expression])
-  extends UnaryGeometryExpression with CodegenFallback {
-  assert(inputExpressions.length == 1)
-
-
-  override protected def nullSafeEval(geometry: Geometry): Any = {
-    var coord:Array[Coordinate] = geometry.getCoordinates()
-    var maxval = Double.MinValue
-    for (point<-coord) {
-      if(point.getX()>maxval){
-        maxval = point.getX()
-      }
-    }
-    maxval
-
-  }
-
-  override def dataType: DataType = DoubleType
-
-  override def children: Seq[Expression] = inputExpressions
+  extends InferredUnaryExpression(Functions.xMax) {
 
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
     copy(inputExpressions = newChildren)
@@ -1758,25 +1565,7 @@ case class ST_XMax(inputExpressions: Seq[Expression])
  * @param inputExpressions
  */
 case class ST_XMin(inputExpressions: Seq[Expression])
-  extends UnaryGeometryExpression with CodegenFallback {
-  assert(inputExpressions.length == 1)
-
-
-  override protected def nullSafeEval(geometry: Geometry): Any = {
-    var coord: Array[Coordinate] = geometry.getCoordinates()
-    var minval = Double.MaxValue
-    for (point <- coord) {
-      if (point.getX() < minval) {
-        minval = point.getX()
-      }
-    }
-    minval
-
-  }
-
-  override def dataType: DataType = DoubleType
-
-  override def children: Seq[Expression] = inputExpressions
+  extends InferredUnaryExpression(Functions.xMin) {
 
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
     copy(inputExpressions = newChildren)
@@ -1790,22 +1579,7 @@ case class ST_XMin(inputExpressions: Seq[Expression])
  * @param inputExpressions
  */
 case class ST_BuildArea(inputExpressions: Seq[Expression])
-  extends Expression with CodegenFallback {
-  assert(inputExpressions.length == 1)
-
-  override def nullable: Boolean = true
-
-  override def eval(input: InternalRow): Any = {
-    val geometry = inputExpressions.head.toGeometry(input)
-    geometry match {
-      case geom: Geometry => new GenericArrayData(GeometrySerializer.serialize(GeomUtils.buildArea(geom)))
-      case _ => null
-    }
-  }
-
-  override def dataType: DataType = GeometryUDT
-
-  override def children: Seq[Expression] = inputExpressions
+  extends InferredUnaryExpression(Functions.buildArea) {
 
   protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]): Expression = {
     copy(inputExpressions = newChildren)
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/NullSafeExpressions.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/NullSafeExpressions.scala
index 8b7af2b0..87ed9ce1 100644
--- a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/NullSafeExpressions.scala
+++ b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/NullSafeExpressions.scala
@@ -18,10 +18,19 @@
  */
 package org.apache.spark.sql.sedona_sql.expressions
 
+import org.apache.sedona.sql.utils.GeometrySerializer
 import org.apache.spark.sql.catalyst.InternalRow
-import org.apache.spark.sql.catalyst.expressions.Expression
-import org.locationtech.jts.geom.Geometry
+import org.apache.spark.sql.catalyst.expressions._
+import org.apache.spark.sql.catalyst.expressions.codegen.CodegenFallback
+import org.apache.spark.sql.catalyst.util.GenericArrayData
 import org.apache.spark.sql.sedona_sql.expressions.implicits._
+import org.apache.spark.sql.sedona_sql.UDT.GeometryUDT
+import org.apache.spark.sql.types._
+import org.apache.spark.unsafe.types.UTF8String
+import org.locationtech.jts.geom.Geometry
+
+import scala.reflect.runtime.universe._
+
 
 abstract class UnaryGeometryExpression extends Expression {
   def inputExpressions: Seq[Expression]
@@ -54,4 +63,137 @@ abstract class BinaryGeometryExpression extends Expression {
   }
 
   protected def nullSafeEval(leftGeometry: Geometry, rightGeometry: Geometry): Any
-}
\ No newline at end of file
+}
+
+// This is a compile time type shield for the types we are able to infer. Anything
+// other than these types will cause a compilation error. This is the Scala
+// 2 way of making a union type.
+sealed class InferrableType[T: TypeTag]
+object InferrableType {
+  implicit val geometryInstance: InferrableType[Geometry] =
+    new InferrableType[Geometry] {}
+  implicit val doubleInstance: InferrableType[Double] =
+    new InferrableType[Double] {}
+  implicit val booleanInstance: InferrableType[Boolean] =
+    new InferrableType[Boolean] {}
+  implicit val intInstance: InferrableType[Int] =
+    new InferrableType[Int] {}
+  implicit val stringInstance: InferrableType[String] =
+    new InferrableType[String] {}
+}
+
+object InferredTypes {
+  def buildExtractor[T: TypeTag](expr: Expression): InternalRow => T = {
+    if (typeOf[T] =:= typeOf[Geometry]) {
+      input: InternalRow => expr.toGeometry(input).asInstanceOf[T]
+    } else if (typeOf[T] =:= typeOf[String]) {
+      input: InternalRow => expr.asString(input).asInstanceOf[T]
+    } else {
+      input: InternalRow => expr.eval(input).asInstanceOf[T]
+    }
+  }
+
+  def buildSerializer[T: TypeTag]: T => Any = {
+    if (typeOf[T] =:= typeOf[Geometry]) {
+      output: T => if (output != null) {
+        output.asInstanceOf[Geometry].toGenericArrayData
+      } else {
+        null
+      }
+    } else if (typeOf[T] =:= typeOf[String]) {
+      output: T => if (output != null) {
+        UTF8String.fromString(output.asInstanceOf[String])
+      } else {
+        null
+      }
+    } else {
+      output: T => output
+    }
+  }
+
+  def inferSparkType[T: TypeTag]: DataType = {
+    if (typeOf[T] =:= typeOf[Geometry]) {
+      GeometryUDT
+    } else if (typeOf[T] =:= typeOf[Double]) {
+      DoubleType
+    } else if (typeOf[T] =:= typeOf[Int]) {
+      IntegerType
+    } else if (typeOf[T] =:= typeOf[String]) {
+      StringType
+    } else {
+      BooleanType
+    }
+  }
+}
+
+/**
+  * The implicit TypeTag's tell Scala to maintain generic type info at runtime. Normally type
+  * erasure would remove any knowledge of what the passed in generic type is.
+  */
+abstract class InferredUnaryExpression[A1: InferrableType, R: InferrableType]
+    (f: (A1) => R)
+    (implicit val a1Tag: TypeTag[A1], implicit val rTag: TypeTag[R])
+    extends Expression with ImplicitCastInputTypes with CodegenFallback with Serializable {
+  import InferredTypes._
+
+  def inputExpressions: Seq[Expression]
+  assert(inputExpressions.length == 1)
+
+  override def children: Seq[Expression] = inputExpressions
+
+  override def toString: String = s" **${getClass.getName}**  "
+
+  override def inputTypes: Seq[AbstractDataType] = Seq(inferSparkType[A1])
+
+  override def nullable: Boolean = true
+
+  override def dataType = inferSparkType[R]
+
+  lazy val extract = buildExtractor[A1](inputExpressions(0))
+
+  lazy val serialize = buildSerializer[R]
+
+  override def eval(input: InternalRow): Any = {
+    val value = extract(input)
+    if (value != null) {
+      serialize(f(value))
+    } else {
+      null
+    }
+  }
+}
+
+abstract class InferredBinaryExpression[A1: InferrableType, A2: InferrableType, R: InferrableType]
+    (f: (A1, A2) => R)
+    (implicit val a1Tag: TypeTag[A1], implicit val a2Tag: TypeTag[A2], implicit val rTag: TypeTag[R])
+    extends Expression with ImplicitCastInputTypes with CodegenFallback with Serializable {
+  import InferredTypes._
+
+  def inputExpressions: Seq[Expression]
+  assert(inputExpressions.length == 2)
+
+  override def children: Seq[Expression] = inputExpressions
+
+  override def toString: String = s" **${getClass.getName}**  "
+
+  override def inputTypes: Seq[AbstractDataType] = Seq(inferSparkType[A1], inferSparkType[A2])
+
+  override def nullable: Boolean = true
+
+  override def dataType = inferSparkType[R]
+
+  lazy val extractLeft = buildExtractor[A1](inputExpressions(0))
+  lazy val extractRight = buildExtractor[A2](inputExpressions(1))
+
+  lazy val serialize = buildSerializer[R]
+
+  override def eval(input: InternalRow): Any = {
+    val left = extractLeft(input)
+    val right = extractRight(input)
+    if (left != null && right != null) {
+      serialize(f(left, right))
+    } else {
+      null
+    }
+  }
+}
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/geohash/GeoHashDecoder.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/geohash/GeoHashDecoder.scala
index ae21b841..9e510cd0 100644
--- a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/geohash/GeoHashDecoder.scala
+++ b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/geohash/GeoHashDecoder.scala
@@ -18,6 +18,7 @@
  */
 package org.apache.spark.sql.sedona_sql.expressions.geohash
 
+import org.apache.sedona.common.utils.BBox
 import org.locationtech.jts.geom.{Geometry, GeometryFactory}
 
 import scala.collection.mutable
@@ -68,10 +69,10 @@ object GeoHashDecoder {
 }
 
 private case class LatLon(var lons: mutable.Seq[Double], var lats: mutable.Seq[Double]){
-  def getBbox(): BBox = BBox(
-    startLat = lats.head,
-    endLat = lats(1),
-    startLon = lons.head,
-    endLon = lons(1)
+  def getBbox(): BBox = new BBox(
+    lons.head,
+    lons(1),
+    lats.head,
+    lats(1)
   )
 }
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/geohash/PointGeoHashEncoder.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/geohash/PointGeoHashEncoder.scala
deleted file mode 100644
index eaacd123..00000000
--- a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/geohash/PointGeoHashEncoder.scala
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.geohash
-import org.locationtech.jts.geom.{Coordinate, GeometryFactory, Point, Polygon}
-
-
-object PointGeoHashEncoder {
-  private val base32 = "0123456789bcdefghjkmnpqrstuvwxyz"
-  private val bits = Seq(16, 8, 4, 2, 1)
-
-  def calculateGeoHash(geom: Point, precision: Long): String = {
-    val bbox = BBox(-180, 180, -90, 90)
-    val precisionUpdated = math.min(precision, 20)
-    if (precision <= 0) ""
-    else geoHashAggregate(
-      geom, precisionUpdated, 0, "", true, bbox, 0, 0
-    )
-
-  }
-
-  private def geoHashAggregate(point: Point, precision: Long, currentPrecision: Long,
-                               geoHash: String, isEven: Boolean, bbox: BBox, bit: Int, ch: Int): String = {
-    if (currentPrecision >= precision) geoHash
-    else {
-      val geoHashUpdate = if (isEven){
-        val mid = (bbox.startLon + bbox.endLon) / 2.0
-        if (point.getX >= mid) GeoHashUpdate(bbox.copy(startLon = mid), ch | bits(bit))
-        else GeoHashUpdate(bbox.copy(endLon = mid), ch)
-      }else {
-        val mid = (bbox.startLat + bbox.endLat) / 2.0
-        if (point.getY >= mid) GeoHashUpdate(bbox.copy(startLat = mid), ch | bits(bit))
-        else GeoHashUpdate(bbox.copy(endLat = mid), ch)
-      }
-      if (bit < 4) {
-
-        geoHashAggregate(
-          point,
-          precision,
-          currentPrecision,
-          geoHash,
-          !isEven,
-          geoHashUpdate.bbox,
-          bit + 1,
-          geoHashUpdate.ch
-        )
-      }
-      else {
-        val geoHashUpdated = geoHash + base32(geoHashUpdate.ch)
-        geoHashAggregate(
-          point,
-          precision,
-          currentPrecision + 1,
-          geoHashUpdated,
-          !isEven,
-          geoHashUpdate.bbox,
-          0,
-          0
-        )
-      }
-    }
-  }
-}
-
-private[geohash] sealed case class BBox(startLon: Double, endLon: Double, startLat: Double, endLat: Double){
-  private val geometryFactory = new GeometryFactory()
-  def getCentroid(): Point = {
-    val lon = startLon + ((startLon + endLon)/2)
-    val lat = startLat + ((startLat + endLat)/2)
-    geometryFactory.createPoint(new Coordinate(lon, lat))
-  }
-
-  def toPolygon(): Polygon = {
-    geometryFactory.createPolygon(
-      Array(
-        new Coordinate(startLon, startLat),
-        new Coordinate(startLon, endLat),
-        new Coordinate(endLon, endLat),
-        new Coordinate(endLon, startLat),
-        new Coordinate(startLon, startLat)
-      )
-    )
-  }
-}
-
-private[geohash] sealed case class GeoHashUpdate(bbox: BBox, ch: Int)
\ No newline at end of file
diff --git a/sql/src/test/scala/org/apache/sedona/sql/functions/geohash/Fixtures.scala b/sql/src/test/scala/org/apache/sedona/sql/functions/geohash/Fixtures.scala
index b98acdde..1d729286 100644
--- a/sql/src/test/scala/org/apache/sedona/sql/functions/geohash/Fixtures.scala
+++ b/sql/src/test/scala/org/apache/sedona/sql/functions/geohash/Fixtures.scala
@@ -18,7 +18,8 @@
  */
 package org.apache.sedona.sql.functions.geohash
 
-import org.apache.spark.sql.sedona_sql.expressions.geohash.{GeoHashDecoder, GeometryGeoHashEncoder}
+import org.apache.sedona.common.utils.GeometryGeoHashEncoder
+import org.apache.spark.sql.sedona_sql.expressions.geohash.GeoHashDecoder
 import org.locationtech.jts.geom.Geometry
 import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor2, TableFor3, TableFor4}
 
@@ -63,7 +64,7 @@ object Fixtures extends TableDrivenPropertyChecks {
   )
 
   def calculateGeoHash(geom: Geometry, precision: Int): Option[String] = {
-    GeometryGeoHashEncoder.calculate(geom, precision)
+    Option(GeometryGeoHashEncoder.calculate(geom, precision))
   }
 
   def decodeGeoHash(geohash: String, precision: Option[Int]): Geometry =
diff --git a/viz/pom.xml b/viz/pom.xml
index b427a8c4..4a4530f8 100644
--- a/viz/pom.xml
+++ b/viz/pom.xml
@@ -37,6 +37,11 @@
     </properties>
 
 	<dependencies>
+        <dependency>
+            <groupId>org.apache.sedona</groupId>
+            <artifactId>sedona-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
         <dependency>
             <groupId>org.apache.sedona</groupId>
             <artifactId>sedona-core-${spark.compat.version}_${scala.compat.version}</artifactId>