From b9060ae7f7d6ba3ce7e58d2ea24bfbb0cb076a1d Mon Sep 17 00:00:00 2001 From: jesspav Date: Thu, 12 Jun 2025 10:20:14 -0700 Subject: [PATCH 1/2] Use ApacheSIS for ST_TRANSFORM --- common/pom.xml | 4 + .../sedona/common/FunctionsApacheSIS.java | 419 ++++++++++++++++++ .../sedona/common/FunctionsGeoTools.java | 1 + .../apache/sedona/common/FunctionsTest.java | 155 +++++-- docs/setup/maven-coordinates.md | 32 +- .../java/org/apache/sedona/flink/Catalog.java | 2 +- ...sGeoTools.java => FunctionsApacheSIS.java} | 8 +- .../org/apache/sedona/flink/FunctionTest.java | 14 +- mkdocs.yml | 5 +- pom.xml | 9 +- .../snowflake/snowsql/GeoToolsWrapper.java | 16 +- .../apache/sedona/snowflake/snowsql/UDFs.java | 9 +- .../sedona/snowflake/snowsql/UDFsV2.java | 5 +- .../sedona/core/spatialOperator/KNNQuery.java | 20 +- .../core/spatialOperator/RangeQuery.java | 4 +- .../sedona/core/spatialRDD/SpatialRDD.java | 6 +- .../sedona_sql/expressions/Functions.scala | 14 +- .../apache/sedona/sql/functionTestScala.scala | 8 +- 18 files changed, 606 insertions(+), 125 deletions(-) create mode 100644 common/src/main/java/org/apache/sedona/common/FunctionsApacheSIS.java rename flink/src/main/java/org/apache/sedona/flink/expressions/{FunctionsGeoTools.java => FunctionsApacheSIS.java} (89%) diff --git a/common/pom.xml b/common/pom.xml index b5127e0e007..67d15f0dd19 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -113,6 +113,10 @@ edu.ucar cdm-core + + org.apache.sis.core + sis-referencing + src/main/java diff --git a/common/src/main/java/org/apache/sedona/common/FunctionsApacheSIS.java b/common/src/main/java/org/apache/sedona/common/FunctionsApacheSIS.java new file mode 100644 index 00000000000..af6e41f2317 --- /dev/null +++ b/common/src/main/java/org/apache/sedona/common/FunctionsApacheSIS.java @@ -0,0 +1,419 @@ +/* + * 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; + +import static org.apache.sis.referencing.IdentifiedObjects.lookupEPSG; + +import org.apache.sis.referencing.CRS; +import org.apache.sis.referencing.crs.AbstractCRS; +import org.apache.sis.referencing.cs.AxesConvention; +import org.apache.sis.util.Classes; +import org.locationtech.jts.geom.*; +import org.locationtech.jts.operation.buffer.BufferOp; +import org.locationtech.jts.operation.buffer.BufferParameters; +import org.opengis.referencing.NoSuchAuthorityCodeException; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.CoordinateOperation; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.TransformException; +import org.opengis.util.FactoryException; + +public class FunctionsApacheSIS { + + private static final String EPSG = "EPSG"; + + /** + * Transforms the given geometry to the specified Coordinate Reference System (CRS). If the given + * CRS or the given geometry is null or is the same as current CRS, then the geometry is returned + * unchanged. If the geometry has no Coordinate Reference System, an exception is thrown. + * + * @param geometry the geometry to transform, or {@code null}. + * @param targetCRS the target coordinate reference system, or {@code null}. + * @return the transformed geometry, or the same geometry if it is already in target CRS. + */ + public static Geometry transform(Geometry geometry, String targetCRS) { + return transformToGivenTarget(geometry, null, targetCRS, true); + } + + /** + * Transforms the given geometry to the specified Coordinate Reference System (CRS). If the given + * CRS or the given geometry is null or is the same as current CRS, then the geometry is returned + * unchanged. If the geometry has no Coordinate Reference System, an exception is thrown. + * + * @param geometry the geometry to transform, or {@code null}. + * @param sourceCRS the source coordinate reference system, or {@code null}. + * @param targetCRS the target coordinate reference system, or {@code null}. + * @return the transformed geometry, or the same geometry if it is already in target CRS. + */ + public static Geometry transform(Geometry geometry, String sourceCRS, String targetCRS) { + return transformToGivenTarget(geometry, sourceCRS, targetCRS, true); + } + + /** + * Transforms the given geometry to the specified Coordinate Reference System (CRS). If the given + * + * @param geometry the geometry to transform, or {@code null}. + * @param sourceCRScode the source coordinate reference system code, or {@code null}. + * @param targetCRScode the target coordinate reference system code, or {@code null}. + * @param lenient whether to be lenient in case of failure to find the operation. + * @return the transformed geometry, or the same geometry if it is already in target CRS. + */ + public static Geometry transform( + Geometry geometry, String sourceCRScode, String targetCRScode, boolean lenient) { + return transformToGivenTarget(geometry, sourceCRScode, targetCRScode, lenient); + } + + public static Geometry transformToGivenTarget( + Geometry geometry, String sourceCRScode, String targetCRScode, boolean lenient) { + if (sourceCRScode == null && geometry.getSRID() == 0) { + throw new IllegalArgumentException( + "Source CRS must be specified. No SRID found on geometry."); + } + + // If sourceCRS is not specified, try to get it from the geometry + if (sourceCRScode == null) { + int srid = geometry.getSRID(); + sourceCRScode = EPSG + ":" + srid; + } + + CoordinateReferenceSystem sourceCRS, targetCRS; + try { + sourceCRS = parseCRSString(sourceCRScode); + targetCRS = parseCRSString(targetCRScode); + } catch (FactoryException e) { + throw new RuntimeException( + String.format("Failed to parse CRS from code %s or %s", sourceCRScode, targetCRScode), e); + } + return transformToGivenTarget(geometry, sourceCRS, targetCRS, lenient); + } + + /** + * Transforms the given geometry to the specified Coordinate Reference System (CRS). If the given + * CRS or the given geometry is null or is the same as current CRS, then the geometry is returned + * unchanged. If the geometry has no Coordinate Reference System, then the geometry is returned + * unchanged. + * + * @param geometry the geometry to transform, or {@code null}. + * @param targetCRS the target coordinate reference system, or {@code null}. + * @return the transformed geometry, or the same geometry if it is already in target CRS. + */ + public static Geometry transformToGivenTarget( + Geometry geometry, + CoordinateReferenceSystem sourceCRS, + CoordinateReferenceSystem targetCRS, + boolean lenient) + throws RuntimeException { + if (geometry == null || targetCRS == null) { + // nothing to do, return the geometry unchanged. + return geometry; + } + + MathTransform transform; + try { + /* Force coordinates to Longitude/Latitude (X,Y) order for transformation. Doing this during + * CRS parsing loses information about the CRS (including the EPSG code), but it is necessary + * for the transformation. + * + *

Note: The area of interest is not currently used in the function. There are cases where + * it can improve accuracy. + */ + CoordinateOperation operation = + CRS.findOperation(asLonLat(sourceCRS), asLonLat(targetCRS), null); + + transform = operation.getMathTransform(); + } catch (FactoryException e) { + throw new RuntimeException( + String.format( + "Failed to find operation for transformation from %s to %s", + sourceCRS.getName(), targetCRS.getName()), + e); + } + + if (transform.isIdentity()) { + try { + int srid = lookupEPSG(targetCRS); + return Functions.setSRID(geometry, srid); + } catch (FactoryException e) { + throw new RuntimeException( + "Failed to find EPSG code during transform for CRS" + targetCRS.getName(), e); + } + } + + Geometry targetGeometry; + try { + + final var gct = new GeometryCoordinateTransform(transform, geometry.getFactory()); + targetGeometry = gct.transform(geometry); + targetGeometry = Functions.setSRID(targetGeometry, lookupEPSG(targetCRS)); + targetGeometry.setUserData(geometry.getUserData()); + + } catch (TransformException e) { + if (lenient) { + // If lenient, return the original geometry + return geometry; + } + throw new RuntimeException( + String.format( + "Failed transform from %s to %s for geometry %s", + sourceCRS.getName(), targetCRS.getName(), geometry), + e); + } catch (FactoryException e) { + throw new RuntimeException( + "Failed to find EPSG code during transform for CRS" + targetCRS, e); + } + return targetGeometry; + } + + public static CoordinateReferenceSystem parseCRSString(String CRSString) throws FactoryException { + CoordinateReferenceSystem crs; + try { + crs = CRS.forCode(CRSString); + } catch (NoSuchAuthorityCodeException e) { + try { + crs = CRS.fromWKT(CRSString); + } catch (FactoryException ex) { + throw new IllegalArgumentException( + "First failed to read as a well-known CRS code: \n" + + e.getMessage() + + "\nThen failed to read as a WKT CRS string: \n" + + ex.getMessage()); + } + } + // Do not force lon/lat ordering here, as the CRS information (including EPSG code) may be lost. + return crs; + } + + private static CoordinateReferenceSystem asLonLat(CoordinateReferenceSystem crs) { + if (crs == null) { + return null; + } + return AbstractCRS.castOrCopy(crs).forConvention(AxesConvention.RIGHT_HANDED); + } + + /** + * This is replicated from the Apache SIS project, as it's not available in the public API. + * + *

An operation transforming a geometry into another geometry. This class decomposes the + * geometry into it's most primitive elements, the {@link CoordinateSequence}, applies an + * operation, then rebuilds the geometry. The operation may change coordinate values (for example + * a map projection), but not necessarily. An operation could also be a clipping for example. + */ + public static class GeometryCoordinateTransform { + /** The factory to use for creating geometries. */ + private final GeometryFactory geometryFactory; + + /** The factory to use for creating sequences of coordinate tuples. */ + protected final CoordinateSequenceFactory coordinateFactory; + + /** The transform to apply on coordinate values. */ + private final MathTransform transform; + + /** + * A temporary buffer holding coordinates to transform. Created when first needed in order to + * have an estimation of size needed. + */ + private double[] coordinates; + + /** + * Creates a new geometry transformer using the given coordinate transform. It is caller's + * responsibility to ensure that the number of source and target dimensions of the given + * transform are equal to the number of dimensions of the geometries to transform. + * + * @param transform the transform to apply on coordinate values. + * @param factory the factory to use for creating geometries. Shall not be null. + */ + public GeometryCoordinateTransform( + final MathTransform transform, final GeometryFactory factory) { + this.geometryFactory = factory; + this.coordinateFactory = factory.getCoordinateSequenceFactory(); + this.transform = transform; + } + + public CoordinateSequence transform(final CoordinateSequence sequence, final int minPoints) + throws TransformException { + final int srcDim = transform.getSourceDimensions(); + final int tgtDim = transform.getTargetDimensions(); + final int maxDim = Math.max(srcDim, tgtDim); + final int count = sequence.size(); + final int capacity = Math.max(4, Math.min(100, count)); + final CoordinateSequence out = coordinateFactory.create(count, tgtDim); + if (coordinates == null || coordinates.length / maxDim < capacity) { + coordinates = new double[capacity * maxDim]; + } + for (int base = 0, n; (n = Math.min(count - base, capacity)) > 0; base += n) { + int batch = n * srcDim; + for (int i = 0; i < batch; i++) { + coordinates[i] = sequence.getOrdinate(base + i / srcDim, i % srcDim); + } + transform.transform(coordinates, 0, coordinates, 0, n); + batch = n * tgtDim; + for (int i = 0; i < batch; i++) { + out.setOrdinate(base + i / tgtDim, i % tgtDim, coordinates[i]); + } + } + return out; + } + + /** + * Transforms the given geometry. This method delegates to one of the {@code transform(…)} + * methods based on the type of the given geometry. + * + * @param geom the geometry to transform. + * @return the transformed geometry. + * @throws TransformException if an error occurred while transforming the geometry. + */ + public Geometry transform(final Geometry geom) throws TransformException { + if (geom instanceof Point) return transform((Point) geom); + if (geom instanceof MultiPoint) return transform((MultiPoint) geom); + if (geom instanceof LinearRing) + return transform((LinearRing) geom); // Must be tested before LineString. + if (geom instanceof LineString) return transform((LineString) geom); + if (geom instanceof MultiLineString) return transform((MultiLineString) geom); + if (geom instanceof Polygon) return transform((Polygon) geom); + if (geom instanceof MultiPolygon) return transform((MultiPolygon) geom); + if (geom instanceof GeometryCollection) return transform((GeometryCollection) geom); + throw new IllegalArgumentException( + "Unsupported geometry type: " + Classes.getShortClassName(geom)); + } + + /** + * Transforms the given point. Can be invoked directly if the type is known at compile-time, or + * indirectly through a call to the more generic {@link #transform(Geometry)} method. + * + * @param geom the point to transform. + * @return the transformed point. + * @throws TransformException if an error occurred while transforming the geometry. + */ + public Point transform(final Point geom) throws TransformException { + final CoordinateSequence coord = geom.getCoordinateSequence(); + return geometryFactory.createPoint(transform(coord, 1)); + } + + /** + * Transforms the given points. Can be invoked directly if the type is known at compile-time, or + * indirectly through a call to the more generic {@link #transform(Geometry)} method. + * + * @param geom the points to transform. + * @return the transformed points. + * @throws TransformException if an error occurred while transforming a geometry. + */ + public MultiPoint transform(final MultiPoint geom) throws TransformException { + final var subs = new Point[geom.getNumGeometries()]; + for (int i = 0; i < subs.length; i++) { + subs[i] = transform((Point) geom.getGeometryN(i)); + } + return geometryFactory.createMultiPoint(subs); + } + + /** + * Transforms the given line string. Can be invoked directly if the type is known at + * compile-time, or indirectly through a call to the more generic {@link #transform(Geometry)} + * method. + * + * @param geom the line string to transform. + * @return the transformed line string. + * @throws TransformException if an error occurred while transforming the geometry. + */ + public LineString transform(final LineString geom) throws TransformException { + final CoordinateSequence seq = transform(geom.getCoordinateSequence(), 2); + return geometryFactory.createLineString(seq); + } + + /** + * Transforms the given line strings. Can be invoked directly if the type is known at + * compile-time, or indirectly through a call to the more generic {@link #transform(Geometry)} + * method. + * + * @param geom the line strings to transform. + * @return the transformed line strings. + * @throws TransformException if an error occurred while transforming a geometry. + */ + public MultiLineString transform(final MultiLineString geom) throws TransformException { + final var subs = new LineString[geom.getNumGeometries()]; + for (int i = 0; i < subs.length; i++) { + subs[i] = transform((LineString) geom.getGeometryN(i)); + } + return geometryFactory.createMultiLineString(subs); + } + + /** + * Transforms the given linear ring. Can be invoked directly if the type is known at + * compile-time, or indirectly through a call to the more generic {@link #transform(Geometry)} + * method. + * + * @param geom the linear ring to transform. + * @return the transformed linear ring. + * @throws TransformException if an error occurred while transforming the geometry. + */ + public LinearRing transform(final LinearRing geom) throws TransformException { + final CoordinateSequence seq = transform(geom.getCoordinateSequence(), 4); + return geometryFactory.createLinearRing(seq); + } + + /** + * Transforms the given polygon. Can be invoked directly if the type is known at compile-time, + * or indirectly through a call to the more generic {@link #transform(Geometry)} method. + * + * @param geom the polygon to transform. + * @return the transformed polygon. + * @throws TransformException if an error occurred while transforming the geometry. + */ + public Polygon transform(final Polygon geom) throws TransformException { + final LinearRing exterior = transform(geom.getExteriorRing()); + final var holes = new LinearRing[geom.getNumInteriorRing()]; + for (int i = 0; i < holes.length; i++) { + holes[i] = transform(geom.getInteriorRingN(i)); + } + return geometryFactory.createPolygon(exterior, holes); + } + + /** + * Transforms the given polygons. Can be invoked directly if the type is known at compile-time, + * or indirectly through a call to the more generic {@link #transform(Geometry)} method. + * + * @param geom the polygons to transform. + * @return the transformed polygons. + * @throws TransformException if an error occurred while transforming a geometry. + */ + public MultiPolygon transform(final MultiPolygon geom) throws TransformException { + final var subs = new Polygon[geom.getNumGeometries()]; + for (int i = 0; i < subs.length; i++) { + subs[i] = transform((Polygon) geom.getGeometryN(i)); + } + return geometryFactory.createMultiPolygon(subs); + } + + /** + * Transforms the given geometries. Can be invoked directly if the type is known at + * compile-time, or indirectly through a call to the more generic {@link #transform(Geometry)} + * method. + * + * @param geom the geometries to transform. + * @return the transformed geometries. + * @throws TransformException if an error occurred while transforming a geometry. + */ + public GeometryCollection transform(final GeometryCollection geom) throws TransformException { + final var subs = new Geometry[geom.getNumGeometries()]; + for (int i = 0; i < subs.length; i++) { + subs[i] = transform(geom.getGeometryN(i)); + } + return geometryFactory.createGeometryCollection(subs); + } + } +} diff --git a/common/src/main/java/org/apache/sedona/common/FunctionsGeoTools.java b/common/src/main/java/org/apache/sedona/common/FunctionsGeoTools.java index 3ae6f5b1051..6143813cc67 100644 --- a/common/src/main/java/org/apache/sedona/common/FunctionsGeoTools.java +++ b/common/src/main/java/org/apache/sedona/common/FunctionsGeoTools.java @@ -72,6 +72,7 @@ public static Geometry transformToGivenTarget( throws FactoryException, TransformException { // If sourceCRS is not specified, try to get it from the geometry if (sourceCRScode == null) { + int srid = geometry.getSRID(); if (srid != 0) { sourceCRScode = "epsg:" + srid; diff --git a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java index 65a294ad313..17cf7705e36 100644 --- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java +++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java @@ -29,10 +29,7 @@ import org.apache.sedona.common.sphere.Haversine; import org.apache.sedona.common.sphere.Spheroid; import org.apache.sedona.common.utils.*; -import org.geotools.api.referencing.FactoryException; import org.geotools.api.referencing.operation.TransformException; -import org.geotools.referencing.CRS; -import org.geotools.referencing.operation.projection.ProjectionException; import org.junit.Test; import org.locationtech.jts.geom.*; import org.locationtech.jts.geom.prep.PreparedGeometry; @@ -749,11 +746,11 @@ public void convexAndConcaveHullSRID() throws ParseException { @Test public void envelopeAndCentroidSRID() throws ParseException { - Geometry geom = Constructors.geomFromWKT("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))", 3857); + Geometry geom = Constructors.geomFromWKT("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))", 4979); Geometry envelope = Functions.envelope(geom); - assertEquals(3857, envelope.getSRID()); + assertEquals(4979, envelope.getSRID()); Geometry centroid = Functions.getCentroid(geom); - assertEquals(3857, centroid.getSRID()); + assertEquals(4979, centroid.getSRID()); } @Test @@ -2867,10 +2864,10 @@ public void testBuffer() { @Test public void testBufferSRID() throws ParseException { - Geometry geom = geomFromWKT("POINT (10 20)", 3857); + Geometry geom = geomFromWKT("POINT (10 20)", 4979); Geometry buffered = Functions.buffer(geom, 1); - assertEquals(3857, buffered.getSRID()); - assertEquals(3857, buffered.getFactory().getSRID()); + assertEquals(4979, buffered.getSRID()); + assertEquals(4979, buffered.getFactory().getSRID()); geom = geomFromWKT("POINT (10 20)", 4326); buffered = Functions.buffer(geom, 1, true); @@ -3936,57 +3933,127 @@ public void hausdorffDistanceDefaultEmptyGeom() throws Exception { } @Test - public void transform() throws FactoryException, TransformException { + public void transform() throws org.opengis.util.FactoryException, TransformException { + /** + * Validating without SIS EPSG dataset. The following CRS codes are currently available as + * Apache SIS Common codes: WGS84 (EPSG:4326) WGS72 (EPSG:4322) NAD83 (EPSG:4269) NAD27 + * (EPSG:4267) ETRS89 (EPSG:4258) ED50 (EPSG:4230) GRS1980 (EPSG:4019) SPHERE (EPSG:4047) + */ + final String WGS84 = "EPSG:4326"; + final String NAD27 = "EPSG:4267"; + final String NAD83 = "EPSG:4269"; + final String ED50 = "EPSG:4230"; + final int nad27Srid = 4267; + final int nad83Srid = 4269; // The source and target CRS are the same - Point geomExpected = GEOMETRY_FACTORY.createPoint(new Coordinate(120, 60)); - Geometry geomActual = FunctionsGeoTools.transform(geomExpected, "EPSG:4326", "EPSG:4326"); - assertEquals(geomExpected.getCoordinate().x, geomActual.getCoordinate().x, FP_TOLERANCE); - assertEquals(geomExpected.getCoordinate().y, geomActual.getCoordinate().y, FP_TOLERANCE); - assertEquals(4326, geomActual.getSRID()); - assertEquals(4326, geomActual.getFactory().getSRID()); - - // The source and target CRS are different - geomActual = FunctionsGeoTools.transform(geomExpected, "EPSG:4326", "EPSG:3857"); - assertEquals(1.3358338895192828E7, geomActual.getCoordinate().x, FP_TOLERANCE); - assertEquals(8399737.889818355, geomActual.getCoordinate().y, FP_TOLERANCE); - assertEquals(3857, geomActual.getSRID()); - assertEquals(3857, geomActual.getFactory().getSRID()); + final Point geomExpected = GEOMETRY_FACTORY.createPoint(new Coordinate(120, 60)); + Geometry geomActual = FunctionsApacheSIS.transform(geomExpected, NAD27, NAD27); + // Zero delta on these coordinate checks since the source and target CRS are the same + assertEquals(geomExpected.getCoordinate().x, geomActual.getCoordinate().x, 0); + assertEquals(geomExpected.getCoordinate().y, geomActual.getCoordinate().y, 0); + assertEquals(nad27Srid, geomActual.getSRID()); + assertEquals(nad27Srid, geomActual.getFactory().getSRID()); + + // The source and target CRS are different. + // Using verification points from NOAA's EPSG transformation service but + Point geomInput = GEOMETRY_FACTORY.createPoint(new Coordinate(-120, 48)); + geomActual = FunctionsApacheSIS.transform(geomInput, NAD83, NAD27); + // large delta on these coordinate checks since validating without EPSG dataset + assertEquals(-119.9988138773, geomActual.getCoordinate().x, 1e-2); + assertEquals(48.0001387082, geomActual.getCoordinate().y, 1e-2); + assertEquals(nad27Srid, geomActual.getSRID()); + assertEquals(nad27Srid, geomActual.getFactory().getSRID()); // The source CRS is not specified and the geometry has no SRID Exception e = assertThrows( IllegalArgumentException.class, - () -> FunctionsGeoTools.transform(geomExpected, "EPSG:3857")); + () -> FunctionsApacheSIS.transform(geomExpected, NAD27)); assertEquals("Source CRS must be specified. No SRID found on geometry.", e.getMessage()); // The source CRS is an invalid SRID e = assertThrows( - FactoryException.class, - () -> FunctionsGeoTools.transform(geomExpected, "abcde", "EPSG:3857")); + IllegalArgumentException.class, + () -> FunctionsApacheSIS.transform(geomExpected, "abcde", NAD83)); assertTrue(e.getMessage().contains("First failed to read as a well-known CRS code")); // The source CRS is a WKT CRS string - String crsWkt = CRS.decode("EPSG:4326", true).toWKT(); - geomActual = FunctionsGeoTools.transform(geomExpected, crsWkt, "EPSG:3857"); - assertEquals(1.3358338895192828E7, geomActual.getCoordinate().x, FP_TOLERANCE); - assertEquals(8399737.889818355, geomActual.getCoordinate().y, FP_TOLERANCE); - assertEquals(3857, geomActual.getSRID()); - assertEquals(3857, geomActual.getFactory().getSRID()); + String crsWkt = FunctionsApacheSIS.parseCRSString(NAD27).toWKT(); + geomInput = GEOMETRY_FACTORY.createPoint(new Coordinate(-110, 45)); + geomActual = FunctionsApacheSIS.transform(geomInput, crsWkt, NAD83); + assertEquals(-110.0007450884, geomActual.getCoordinate().x, 1E-2); + assertEquals(44.9999435027, geomActual.getCoordinate().y, 1E-2); + assertEquals(nad83Srid, geomActual.getSRID()); + assertEquals(nad83Srid, geomActual.getFactory().getSRID()); // The source CRS is not specified but the geometry has a valid SRID - geomExpected.setSRID(4326); - geomActual = FunctionsGeoTools.transform(geomExpected, "EPSG:3857"); - assertEquals(1.3358338895192828E7, geomActual.getCoordinate().x, FP_TOLERANCE); - assertEquals(8399737.889818355, geomActual.getCoordinate().y, FP_TOLERANCE); - assertEquals(3857, geomActual.getSRID()); - assertEquals(3857, geomActual.getFactory().getSRID()); - - // The source and target CRS are different, and latitude is out of range - Point geometryWrong = GEOMETRY_FACTORY.createPoint(new Coordinate(60, 120)); - assertThrows( - ProjectionException.class, - () -> FunctionsGeoTools.transform(geometryWrong, "EPSG:4326", "EPSG:3857")); + geomInput.setSRID(nad27Srid); + geomActual = FunctionsApacheSIS.transform(geomInput, NAD83); + assertEquals(-110.0007450884, geomActual.getCoordinate().x, 1E-2); + assertEquals(44.99994350267, geomActual.getCoordinate().y, 1E-2); + assertEquals(nad83Srid, geomActual.getSRID()); + assertEquals(nad83Srid, geomActual.getFactory().getSRID()); + } + + @Test + public void transformGeometries() { + // Basic validation that the transformed geometries have the same shape as the original + Point oP = GEOMETRY_FACTORY.createPoint(new Coordinate(0, 0)); + Geometry tranP = FunctionsApacheSIS.transform(oP, "EPSG:4326", "EPSG:4979"); + assert (tranP instanceof Point); + + MultiPoint origMP = GEOMETRY_FACTORY.createMultiPointFromCoords(coordArray(0, 0, 2, 2)); + Geometry tranMP = FunctionsApacheSIS.transform(origMP, "EPSG:4326", "EPSG:4979"); + assert (tranMP instanceof MultiPoint); + + LinearRing origLR = GEOMETRY_FACTORY.createLinearRing(coordArray(0, 0, 2, 2, 0, 0)); + Geometry tranLR = FunctionsApacheSIS.transform(origLR, "EPSG:4326", "EPSG:4979"); + assert (tranLR instanceof LinearRing); + + LineString origLS = GEOMETRY_FACTORY.createLineString(coordArray(0, 0, 2, 2)); + Geometry tranLS = FunctionsApacheSIS.transform(origLS, "EPSG:4326", "EPSG:4979"); + assert (tranLS instanceof LineString); + + MultiLineString origMLS = + GEOMETRY_FACTORY.createMultiLineString( + new LineString[] {origLS, GEOMETRY_FACTORY.createLineString(coordArray(3, 3, 5, 5))}); + Geometry tranMLS = FunctionsApacheSIS.transform(origMLS, "EPSG:4326", "EPSG:4979"); + assert (tranMLS instanceof MultiLineString); + + Polygon origPoly = GEOMETRY_FACTORY.createPolygon(coordArray(0, 0, 2, 2, 2, 0, 0, 0)); + Geometry tranPoly = FunctionsApacheSIS.transform(origPoly, "EPSG:4326", "EPSG:4979"); + assert (tranPoly instanceof Polygon); + + MultiPolygon origMPoly = + GEOMETRY_FACTORY.createMultiPolygon( + new Polygon[] { + GEOMETRY_FACTORY.createPolygon(coordArray(0, 0, 2, 2, 2, 0, 0, 0)), + GEOMETRY_FACTORY.createPolygon(coordArray(3, 3, 5, 5, 5, 3, 3, 3)) + }); + Geometry tranMPoly = FunctionsApacheSIS.transform(origMPoly, "EPSG:4326", "EPSG:4979"); + assert (tranMPoly instanceof MultiPolygon); + assert (tranMPoly.getNumGeometries() == origMPoly.getNumGeometries()); + assert (tranMPoly.getGeometryN(0) instanceof Polygon); + assert (tranMPoly.getGeometryN(1) instanceof Polygon); + + GeometryCollection origGC = + GEOMETRY_FACTORY.createGeometryCollection( + new Geometry[] {origMP, origLR, origLS, origPoly, origMPoly}); + Geometry tranGC = FunctionsApacheSIS.transform(origGC, "EPSG:4326", "EPSG:4979"); + assert (tranGC instanceof GeometryCollection); + assert (tranGC.getNumGeometries() == origGC.getNumGeometries()); + assert (tranGC.getGeometryN(0) instanceof MultiPoint); + assert (tranGC.getGeometryN(0).getNumGeometries() == origMP.getNumGeometries()); + assert (tranGC.getGeometryN(1) instanceof LinearRing); + assert (tranGC.getGeometryN(2) instanceof LineString); + assert (tranGC.getGeometryN(3) instanceof Polygon); + assert (tranGC.getGeometryN(4) instanceof MultiPolygon); + assert (tranGC.getGeometryN(4).getGeometryN(0) instanceof Polygon); + assert (tranGC.getGeometryN(4).getGeometryN(1) instanceof Polygon); + for (var i = 0; i < tranGC.getNumGeometries(); i++) { + assertEquals(4979, tranGC.getGeometryN(i).getSRID()); + } } @Test diff --git a/docs/setup/maven-coordinates.md b/docs/setup/maven-coordinates.md index e54870be7b1..5eeafce4f5a 100644 --- a/docs/setup/maven-coordinates.md +++ b/docs/setup/maven-coordinates.md @@ -33,25 +33,11 @@ Please use the artifact with Spark major.minor version in the artifact name. For If you are using the Scala 2.13 builds of Spark, please use the corresponding packages for Scala 2.13, which are suffixed by `_2.13`. -The optional GeoTools library is required if you want to use CRS transformation, ShapefileReader or GeoTiff reader. This wrapper library is a re-distribution of GeoTools official jars. The only purpose of this library is to bring GeoTools jars from OSGEO repository to Maven Central. This library is under GNU Lesser General Public License (LGPL) license so we cannot package it in Sedona official release. +The optional GeoTools library is required if you want to use ShapefileReader or GeoTiff reader. This wrapper library is a re-distribution of GeoTools official jars. The only purpose of this library is to bring GeoTools jars from OSGEO repository to Maven Central. This library is under GNU Lesser General Public License (LGPL) license so we cannot package it in Sedona official release. -!!! abstract "Sedona with Apache Spark and Scala 2.12" - - === "Spark 3.3 and Scala 2.12" +The optional EPSG database is required if you want to use CRS transformation. This library is a re-distribution of EPSG official jars. The only purpose of this library is to bring EPSG jars from OSGEO repository to Maven Central. This library is under GNU Lesser General Public License (LGPL) license so we cannot package it in Sedona official release. - ```xml - - org.apache.sedona - sedona-spark-shaded-3.3_2.12 - {{ sedona.current_version }} - - - - org.datasyslab - geotools-wrapper - {{ sedona.current_geotools }} - - ``` +!!! abstract "Sedona with Apache Spark and Scala 2.12" === "Spark 3.4 and Scala 2.12" @@ -169,6 +155,18 @@ The optional GeoTools library is required if you want to use CRS transformation, ``` +!!! abstract "Sedona with EPSG database" + + === "EPSG database 9.8.1+" + + ```xml + + org.apache.sedona + sis-embedded-data + {{ sedona.apachesis_version }} + + ``` + ## Use Sedona unshaded jars !!!warning 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 8654e3bf2ad..a52c44fd39c 100644 --- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java +++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java @@ -98,7 +98,7 @@ public static UserDefinedFunction[] getFuncs() { new Functions.ST_LineLocatePoint(), new Functions.ST_LocateAlong(), new Functions.ST_LongestLine(), - new FunctionsGeoTools.ST_Transform(), + new FunctionsApacheSIS.ST_Transform(), new Functions.ST_FlipCoordinates(), new Functions.ST_GeoHash(), new Functions.ST_Perimeter(), diff --git a/flink/src/main/java/org/apache/sedona/flink/expressions/FunctionsGeoTools.java b/flink/src/main/java/org/apache/sedona/flink/expressions/FunctionsApacheSIS.java similarity index 89% rename from flink/src/main/java/org/apache/sedona/flink/expressions/FunctionsGeoTools.java rename to flink/src/main/java/org/apache/sedona/flink/expressions/FunctionsApacheSIS.java index 1085bc6656f..1378613fdab 100644 --- a/flink/src/main/java/org/apache/sedona/flink/expressions/FunctionsGeoTools.java +++ b/flink/src/main/java/org/apache/sedona/flink/expressions/FunctionsApacheSIS.java @@ -24,7 +24,7 @@ import org.geotools.api.referencing.operation.TransformException; import org.locationtech.jts.geom.Geometry; -public class FunctionsGeoTools { +public class FunctionsApacheSIS { public static class ST_Transform extends ScalarFunction { @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) public Geometry eval( @@ -32,7 +32,7 @@ public Geometry eval( @DataTypeHint("String") String targetCRS) throws FactoryException, TransformException { Geometry geom = (Geometry) o; - return org.apache.sedona.common.FunctionsGeoTools.transform(geom, targetCRS); + return org.apache.sedona.common.FunctionsApacheSIS.transform(geom, targetCRS); } @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) @@ -42,7 +42,7 @@ public Geometry eval( @DataTypeHint("String") String targetCRS) throws FactoryException, TransformException { Geometry geom = (Geometry) o; - return org.apache.sedona.common.FunctionsGeoTools.transform(geom, sourceCRS, targetCRS); + return org.apache.sedona.common.FunctionsApacheSIS.transform(geom, sourceCRS, targetCRS); } @DataTypeHint(value = "RAW", bridgedTo = Geometry.class) @@ -53,7 +53,7 @@ public Geometry eval( @DataTypeHint("Boolean") Boolean lenient) throws FactoryException, TransformException { Geometry geom = (Geometry) o; - return org.apache.sedona.common.FunctionsGeoTools.transform( + return org.apache.sedona.common.FunctionsApacheSIS.transform( geom, sourceCRS, targetCRS, lenient); } } diff --git a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java index 0c9cae9920d..9a55aeb439b 100644 --- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java +++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java @@ -30,7 +30,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.apache.flink.table.api.Table; import org.apache.sedona.flink.expressions.Functions; -import org.apache.sedona.flink.expressions.FunctionsGeoTools; +import org.apache.sedona.flink.expressions.FunctionsApacheSIS; import org.geotools.api.referencing.FactoryException; import org.geotools.api.referencing.crs.CoordinateReferenceSystem; import org.geotools.referencing.CRS; @@ -472,7 +472,7 @@ public void testTransform() { Table transformedTable = pointTable.select( call( - FunctionsGeoTools.ST_Transform.class.getSimpleName(), + FunctionsApacheSIS.ST_Transform.class.getSimpleName(), $(pointColNames[0]), "epsg:4326", "epsg:3857")); @@ -487,7 +487,7 @@ public void testTransform() { pointTable .select( call( - FunctionsGeoTools.ST_Transform.class.getSimpleName(), + FunctionsApacheSIS.ST_Transform.class.getSimpleName(), $(pointColNames[0]), "epsg:3857")) .as(pointColNames[0]) @@ -549,7 +549,7 @@ public void testTransformWKT() throws FactoryException { Table transformedTable_SRC = pointTable.select( call( - FunctionsGeoTools.ST_Transform.class.getSimpleName(), + FunctionsApacheSIS.ST_Transform.class.getSimpleName(), $(pointColNames[0]), SRC_WKT, "epsg:3857")); @@ -559,7 +559,7 @@ public void testTransformWKT() throws FactoryException { Table transformedTable_TGT = pointTable.select( call( - FunctionsGeoTools.ST_Transform.class.getSimpleName(), + FunctionsApacheSIS.ST_Transform.class.getSimpleName(), $(pointColNames[0]), "epsg:4326", TGT_WKT)); @@ -569,7 +569,7 @@ public void testTransformWKT() throws FactoryException { Table transformedTable_SRC_TGT = pointTable.select( call( - FunctionsGeoTools.ST_Transform.class.getSimpleName(), + FunctionsApacheSIS.ST_Transform.class.getSimpleName(), $(pointColNames[0]), SRC_WKT, TGT_WKT)); @@ -579,7 +579,7 @@ public void testTransformWKT() throws FactoryException { Table transformedTable_SRC_TGT_lenient = pointTable.select( call( - FunctionsGeoTools.ST_Transform.class.getSimpleName(), + FunctionsApacheSIS.ST_Transform.class.getSimpleName(), $(pointColNames[0]), SRC_WKT, TGT_WKT, diff --git a/mkdocs.yml b/mkdocs.yml index d86ed8f82da..52813a8a8a7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -196,8 +196,9 @@ extra: - icon: fontawesome/brands/discord link: 'https://discord.gg/9A3k5dEBsY' sedona: - current_version: 1.7.2 - current_geotools: 1.7.2-28.5 + current_version: 1.7.1 + current_geotools: 1.8.0-33.1-rc1 + current_apachesis: 1.4 sedona_create_release: current_version: 1.7.1 current_git_tag: sedona-1.7.1-rc1 diff --git a/pom.xml b/pom.xml index fe1a092267c..990423513fd 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,7 @@ 1.20.0 0.16.1 0.8 + 1.4 1.1.24 3.1.9 @@ -283,7 +284,12 @@ - + + org.apache.sis.core + sis-referencing + ${sis.version} + + org.apache.hadoop hadoop-client ${hadoop.version} @@ -565,6 +571,7 @@ + diff --git a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/GeoToolsWrapper.java b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/GeoToolsWrapper.java index fbb7963b612..30fc88d4b08 100644 --- a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/GeoToolsWrapper.java +++ b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/GeoToolsWrapper.java @@ -18,26 +18,16 @@ */ package org.apache.sedona.snowflake.snowsql; -import org.apache.sedona.common.FunctionsGeoTools; -import org.geotools.api.referencing.FactoryException; -import org.geotools.api.referencing.operation.TransformException; +import org.apache.sedona.common.FunctionsApacheSIS; import org.locationtech.jts.geom.Geometry; public class GeoToolsWrapper { public static Geometry transform( Geometry geometry, String sourceCRS, String targetCRS, boolean lenient) { - try { - return FunctionsGeoTools.transform(geometry, sourceCRS, targetCRS, lenient); - } catch (FactoryException | TransformException e) { - throw new RuntimeException(e); - } + return FunctionsApacheSIS.transform(geometry, sourceCRS, targetCRS, lenient); } public static Geometry transform(Geometry geometry, String sourceCRS, String targetCRS) { - try { - return FunctionsGeoTools.transform(geometry, sourceCRS, targetCRS); - } catch (FactoryException | TransformException e) { - throw new RuntimeException(e); - } + return FunctionsApacheSIS.transform(geometry, sourceCRS, targetCRS); } } diff --git a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java index 98fdbb0608b..e2e175d984d 100644 --- a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java +++ b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java @@ -20,10 +20,7 @@ import java.io.IOException; import javax.xml.parsers.ParserConfigurationException; -import org.apache.sedona.common.Constructors; -import org.apache.sedona.common.Functions; -import org.apache.sedona.common.FunctionsGeoTools; -import org.apache.sedona.common.Predicates; +import org.apache.sedona.common.*; import org.apache.sedona.common.enums.FileDataSplitter; import org.apache.sedona.common.sphere.Haversine; import org.apache.sedona.common.sphere.Spheroid; @@ -1102,14 +1099,14 @@ public static Boolean ST_RelateMatch(String matrix1, String matrix2) { @UDFAnnotations.ParamMeta(argNames = {"geometry", "sourceCRS", "targetCRS"}) public static byte[] ST_Transform(byte[] geometry, String sourceCRS, String targetCRS) { return GeometrySerde.serialize( - GeoToolsWrapper.transform(GeometrySerde.deserialize(geometry), sourceCRS, targetCRS)); + FunctionsApacheSIS.transform(GeometrySerde.deserialize(geometry), sourceCRS, targetCRS)); } @UDFAnnotations.ParamMeta(argNames = {"geometry", "sourceCRS", "targetCRS", "lenient"}) public static byte[] ST_Transform( byte[] geometry, String sourceCRS, String targetCRS, boolean lenient) { return GeometrySerde.serialize( - GeoToolsWrapper.transform( + FunctionsApacheSIS.transform( GeometrySerde.deserialize(geometry), sourceCRS, targetCRS, lenient)); } diff --git a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java index 2d9b349b187..28f02f98325 100644 --- a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java +++ b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java @@ -20,6 +20,7 @@ import java.io.IOException; import org.apache.sedona.common.Functions; +import org.apache.sedona.common.FunctionsApacheSIS; import org.apache.sedona.common.FunctionsGeoTools; import org.apache.sedona.common.Predicates; import org.apache.sedona.common.sphere.Haversine; @@ -1295,7 +1296,7 @@ public static boolean ST_RelateMatch(String matrix1, String matrix2) { returnTypes = "Geometry") public static String ST_Transform(String geometry, String sourceCRS, String targetCRS) { return GeometrySerde.serGeoJson( - GeoToolsWrapper.transform(GeometrySerde.deserGeoJson(geometry), sourceCRS, targetCRS)); + FunctionsApacheSIS.transform(GeometrySerde.deserGeoJson(geometry), sourceCRS, targetCRS)); } @UDFAnnotations.ParamMeta( @@ -1305,7 +1306,7 @@ public static String ST_Transform(String geometry, String sourceCRS, String targ public static String ST_Transform( String geometry, String sourceCRS, String targetCRS, boolean lenient) { return GeometrySerde.serGeoJson( - GeoToolsWrapper.transform( + FunctionsApacheSIS.transform( GeometrySerde.deserGeoJson(geometry), sourceCRS, targetCRS, lenient)); } diff --git a/spark/common/src/main/java/org/apache/sedona/core/spatialOperator/KNNQuery.java b/spark/common/src/main/java/org/apache/sedona/core/spatialOperator/KNNQuery.java index 5b4e0d248ad..402423daf3c 100644 --- a/spark/common/src/main/java/org/apache/sedona/core/spatialOperator/KNNQuery.java +++ b/spark/common/src/main/java/org/apache/sedona/core/spatialOperator/KNNQuery.java @@ -20,14 +20,12 @@ import java.io.Serializable; import java.util.List; -import org.apache.sedona.common.FunctionsGeoTools; +import org.apache.sedona.common.FunctionsApacheSIS; import org.apache.sedona.core.knnJudgement.GeometryDistanceComparator; import org.apache.sedona.core.knnJudgement.KnnJudgement; import org.apache.sedona.core.knnJudgement.KnnJudgementUsingIndex; import org.apache.sedona.core.spatialRDD.SpatialRDD; import org.apache.spark.api.java.JavaRDD; -import org.geotools.api.referencing.FactoryException; -import org.geotools.api.referencing.operation.TransformException; import org.locationtech.jts.geom.Geometry; // TODO: Auto-generated Javadoc @@ -48,16 +46,12 @@ public static List SpatialKnnQuery( SpatialRDD spatialRDD, U originalQueryPoint, Integer k, boolean useIndex) { U queryCenter = originalQueryPoint; if (spatialRDD.getCRStransformation()) { - try { - queryCenter = - (U) - FunctionsGeoTools.transform( - originalQueryPoint, - spatialRDD.getSourceEpsgCode(), - spatialRDD.getTargetEpgsgCode()); - } catch (FactoryException | TransformException e) { - throw new RuntimeException(e); - } + queryCenter = + (U) + FunctionsApacheSIS.transform( + originalQueryPoint, + spatialRDD.getSourceEpsgCode(), + spatialRDD.getTargetEpgsgCode()); } if (useIndex) { diff --git a/spark/common/src/main/java/org/apache/sedona/core/spatialOperator/RangeQuery.java b/spark/common/src/main/java/org/apache/sedona/core/spatialOperator/RangeQuery.java index 6a0aaa54e63..1d1f7f0971f 100644 --- a/spark/common/src/main/java/org/apache/sedona/core/spatialOperator/RangeQuery.java +++ b/spark/common/src/main/java/org/apache/sedona/core/spatialOperator/RangeQuery.java @@ -19,7 +19,7 @@ package org.apache.sedona.core.spatialOperator; import java.io.Serializable; -import org.apache.sedona.common.FunctionsGeoTools; +import org.apache.sedona.common.FunctionsApacheSIS; import org.apache.sedona.core.rangeJudgement.RangeFilter; import org.apache.sedona.core.rangeJudgement.RangeFilterUsingIndex; import org.apache.sedona.core.spatialRDD.SpatialRDD; @@ -56,7 +56,7 @@ public static JavaRDD SpatialRangeQu if (spatialRDD.getCRStransformation()) { queryGeometry = (U) - FunctionsGeoTools.transform( + FunctionsApacheSIS.transform( originalQueryGeometry, spatialRDD.getSourceEpsgCode(), spatialRDD.getTargetEpgsgCode()); diff --git a/spark/common/src/main/java/org/apache/sedona/core/spatialRDD/SpatialRDD.java b/spark/common/src/main/java/org/apache/sedona/core/spatialRDD/SpatialRDD.java index 50d44b9bf42..6beadd71e16 100644 --- a/spark/common/src/main/java/org/apache/sedona/core/spatialRDD/SpatialRDD.java +++ b/spark/common/src/main/java/org/apache/sedona/core/spatialRDD/SpatialRDD.java @@ -27,7 +27,7 @@ import java.util.Map; import org.apache.commons.lang.NullArgumentException; import org.apache.log4j.Logger; -import org.apache.sedona.common.FunctionsGeoTools; +import org.apache.sedona.common.FunctionsApacheSIS; import org.apache.sedona.common.utils.GeomUtils; import org.apache.sedona.core.enums.GridType; import org.apache.sedona.core.enums.IndexType; @@ -141,7 +141,9 @@ public boolean CRSTransform(String sourceEpsgCRSCode, String targetEpsgCRSCode, this.rawSpatialRDD = this.rawSpatialRDD.map( (geom) -> - (T) FunctionsGeoTools.transform(geom, sourceEpsgCRSCode, targetEpgsgCode, lenient)); + (T) + FunctionsApacheSIS.transform( + geom, sourceEpsgCRSCode, targetEpgsgCode, lenient)); return true; } diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala index b594d8a43dd..2723e45a6bb 100644 --- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala +++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala @@ -19,22 +19,22 @@ package org.apache.spark.sql.sedona_sql.expressions import org.apache.sedona.common.geometryObjects.Geography -import org.apache.sedona.common.{Functions, FunctionsGeoTools} import org.apache.sedona.common.sphere.{Haversine, Spheroid} import org.apache.sedona.common.utils.{InscribedCircle, ValidDetail} +import org.apache.sedona.common.{Functions, FunctionsApacheSIS, FunctionsGeoTools} import org.apache.sedona.sql.utils.GeometrySerializer import org.apache.spark.sql.catalyst.InternalRow import org.apache.spark.sql.catalyst.expressions.codegen.CodegenFallback import org.apache.spark.sql.catalyst.expressions.{ExpectsInputTypes, Expression, Generator, Nondeterministic} import org.apache.spark.sql.catalyst.util.ArrayData import org.apache.spark.sql.sedona_sql.UDT.GeometryUDT +import org.apache.spark.sql.sedona_sql.expressions.InferrableFunctionConverter._ import org.apache.spark.sql.sedona_sql.expressions.implicits._ import org.apache.spark.sql.types._ -import org.locationtech.jts.algorithm.MinimumBoundingCircle -import org.locationtech.jts.geom._ -import org.apache.spark.sql.sedona_sql.expressions.InferrableFunctionConverter._ import org.apache.spark.unsafe.types.UTF8String import org.apache.spark.util.Utils +import org.locationtech.jts.algorithm.MinimumBoundingCircle +import org.locationtech.jts.geom._ case class ST_LabelPoint(inputExpressions: Seq[Expression]) extends InferredExpression( @@ -290,9 +290,9 @@ case class ST_Centroid(inputExpressions: Seq[Expression]) */ case class ST_Transform(inputExpressions: Seq[Expression]) extends InferredExpression( - inferrableFunction4(FunctionsGeoTools.transform), - inferrableFunction3(FunctionsGeoTools.transform), - inferrableFunction2(FunctionsGeoTools.transform)) { + inferrableFunction4(FunctionsApacheSIS.transform), + inferrableFunction3(FunctionsApacheSIS.transform), + inferrableFunction2(FunctionsApacheSIS.transform)) { protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = { copy(inputExpressions = newChildren) diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala b/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala index 303966722da..87b7cb35dbc 100644 --- a/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala +++ b/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala @@ -19,11 +19,12 @@ package org.apache.sedona.sql import org.apache.commons.codec.binary.Hex -import org.apache.sedona.common.FunctionsGeoTools +import org.apache.sedona.common.FunctionsApacheSIS import org.apache.sedona.sql.implicits._ import org.apache.spark.sql.catalyst.expressions.GenericRowWithSchema import org.apache.spark.sql.functions._ import org.apache.spark.sql.{DataFrame, Row} +import org.geotools.api.referencing.FactoryException import org.geotools.referencing.CRS import org.junit.Assert.{assertEquals, assertFalse, assertTrue} import org.locationtech.jts.algorithm.MinimumBoundingCircle @@ -31,7 +32,6 @@ import org.locationtech.jts.geom.{Coordinate, Geometry, GeometryFactory, Polygon import org.locationtech.jts.io.WKTWriter import org.locationtech.jts.linearref.LengthIndexedLine import org.locationtech.jts.operation.distance3d.Distance3DOp -import org.geotools.api.referencing.FactoryException import org.scalatest.{GivenWhenThen, Matchers} import org.xml.sax.InputSource @@ -457,11 +457,11 @@ class functionTestScala val polygeom = reader.read(polygon) intercept[FactoryException] { - val d = FunctionsGeoTools.transform(polygeom, epsgString, epsgFactoryErrorString) + val d = FunctionsApacheSIS.transform(polygeom, epsgString, epsgFactoryErrorString) } intercept[FactoryException] { - val d2 = FunctionsGeoTools.transform(polygeom, epsgString, epsgNoSuchAuthorityString) + val d2 = FunctionsApacheSIS.transform(polygeom, epsgString, epsgNoSuchAuthorityString) } } From 752c77ec64fe591b748436d1bab2ca8e2457f2b2 Mon Sep 17 00:00:00 2001 From: jesspav <202656197+jesspav@users.noreply.github.com> Date: Fri, 11 Jul 2025 10:13:04 -0700 Subject: [PATCH 2/2] ran spotless --- .../main/java/org/apache/sedona/common/FunctionsApacheSIS.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/src/main/java/org/apache/sedona/common/FunctionsApacheSIS.java b/common/src/main/java/org/apache/sedona/common/FunctionsApacheSIS.java index af6e41f2317..544fffd0652 100644 --- a/common/src/main/java/org/apache/sedona/common/FunctionsApacheSIS.java +++ b/common/src/main/java/org/apache/sedona/common/FunctionsApacheSIS.java @@ -25,8 +25,6 @@ import org.apache.sis.referencing.cs.AxesConvention; import org.apache.sis.util.Classes; import org.locationtech.jts.geom.*; -import org.locationtech.jts.operation.buffer.BufferOp; -import org.locationtech.jts.operation.buffer.BufferParameters; import org.opengis.referencing.NoSuchAuthorityCodeException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.CoordinateOperation;