From 8165284abd1a2da1ca75c19e1924261750f12f8b Mon Sep 17 00:00:00 2001 From: Uros Bojanic Date: Mon, 20 Oct 2025 14:46:55 +0200 Subject: [PATCH 1/5] Initial commit --- .../types/SpatialReferenceSystemMapper.java | 36 ++++++++++-- .../spark/sql/types/GeographyType.scala | 48 +++++----------- .../apache/spark/sql/types/GeometryType.scala | 9 ++- .../SpatialReferenceSystemMapperSuite.java | 57 ++++++++++++++----- .../spark/sql/types/GeographyTypeSuite.scala | 8 +-- 5 files changed, 99 insertions(+), 59 deletions(-) diff --git a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemMapper.java b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemMapper.java index 16106b552a6d2..275fe55479a15 100644 --- a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemMapper.java +++ b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemMapper.java @@ -18,6 +18,7 @@ package org.apache.spark.sql.internal.types; import java.util.HashMap; +import java.util.HashSet; /* * Class for maintaining mappings between supported SRID values and the string ID of the @@ -35,20 +36,38 @@ public static SpatialReferenceSystemMapper get() { return Instance; } - // Hash maps defining the mappings to/from SRID and string ID for a CRS. + // The `SpatialReferenceSystemMapper` class can be used for both GEOGRAPHY and GEOMETRY types. + // This enumeration defines the two spatial type options, in the context of SRS mapping. + public enum Type { + GEOGRAPHY, + GEOMETRY + } + + // Hash maps for defining the mappings to/from SRID and string ID for a CRS. private static final HashMap sridToStringId = buildSridToStringIdMap(); private static final HashMap stringIdToSrid = buildStringIdToSridMap(); + // Hash set for keeping track of the supported SRID values for geographic CRS. + public static final HashSet geographicSrids = buildGeographicSridSet(); + // Returns the string ID corresponding to the input SRID. If the input SRID is not supported, // `null` is returned. - public String getStringId(int srid) { - return sridToStringId.get(srid); + public String getStringId(int srid, Type type) { + String stringId = sridToStringId.get(srid); + return switch (type) { + case GEOGRAPHY -> geographicSrids.contains(srid) ? stringId : null; + case GEOMETRY -> stringId; + }; } // Returns the SRID corresponding to the input string ID. If the input string ID is not // supported, `null` is returned. - public Integer getSrid(String stringId) { - return stringIdToSrid.get(stringId); + public Integer getSrid(String stringId, Type type) { + Integer srid = stringIdToSrid.get(stringId); + return switch (type) { + case GEOGRAPHY -> geographicSrids.contains(srid) ? srid : null; + case GEOMETRY -> srid; + }; } // Currently, we only support a limited set of SRID / CRS mappings. However, we will soon extend @@ -72,4 +91,11 @@ private static HashMap buildStringIdToSridMap() { map.put("OGC:CRS84", 4326); // WGS84 return map; } + + // Helper method for building the set of supported geographic SRID values. + private static HashSet buildGeographicSridSet() { + HashSet set = new HashSet<>(); + set.add(4326); // WGS84 + return set; + } } diff --git a/sql/api/src/main/scala/org/apache/spark/sql/types/GeographyType.scala b/sql/api/src/main/scala/org/apache/spark/sql/types/GeographyType.scala index b5a6517425a01..11e43d0690335 100644 --- a/sql/api/src/main/scala/org/apache/spark/sql/types/GeographyType.scala +++ b/sql/api/src/main/scala/org/apache/spark/sql/types/GeographyType.scala @@ -21,6 +21,7 @@ import org.json4s.JsonAST.{JString, JValue} import org.apache.spark.SparkIllegalArgumentException import org.apache.spark.annotation.Experimental +import org.apache.spark.sql.internal.types.SpatialReferenceSystemMapper /** * The data type representing GEOGRAPHY values which are spatial objects, as defined in the Open @@ -156,12 +157,17 @@ object GeographyType extends SpatialType { private final val GEOGRAPHY_MIXED_TYPE: GeographyType = GeographyType(MIXED_CRS, GEOGRAPHY_DEFAULT_ALGORITHM) + /** + * Type enum alias for geographic reference system mapping. + */ + private final val GEOG = SpatialReferenceSystemMapper.Type.GEOGRAPHY + /** * Constructors for GeographyType. */ def apply(srid: Int): GeographyType = { - if (!isValidSrid(srid)) { - // Limited geographic SRID values are allowed. + val crs = SpatialReferenceSystemMapper.get().getStringId(srid, GEOG) + if (crs == null) { throw new SparkIllegalArgumentException( errorClass = "ST_INVALID_SRID_VALUE", messageParameters = Map("srid" -> srid.toString)) @@ -193,35 +199,9 @@ object GeographyType extends SpatialType { } def apply(crs: String, algorithm: EdgeInterpolationAlgorithm): GeographyType = { - if (!isValidCrs(crs)) { - // Limited geographic CRS values are allowed. - throw new SparkIllegalArgumentException( - errorClass = "ST_INVALID_CRS_VALUE", - messageParameters = Map("crs" -> crs)) - } new GeographyType(crs, algorithm) } - /** - * Helper method to validate the CRS value. Limited geographic CRS values are allowed. - */ - private def isValidCrs(crs: String): Boolean = { - // Currently, we only support "OGC:CRS84" / "EPSG:4326" / "SRID:ANY". - // In the future, we may support others. - crs.equalsIgnoreCase(GEOGRAPHY_DEFAULT_CRS) || - crs.equalsIgnoreCase(GEOGRAPHY_DEFAULT_EPSG_CRS) || - crs.equalsIgnoreCase(MIXED_CRS) - } - - /** - * Helper method to validate the SRID value. Only geographic SRID values are allowed. - */ - - private def isValidSrid(srid: Int): Boolean = { - // Currently, we only support 4326. In the future, we may support others. - srid == GEOGRAPHY_DEFAULT_SRID - } - override private[sql] def defaultConcreteType: DataType = GEOGRAPHY_MIXED_TYPE override private[sql] def acceptsType(other: DataType): Boolean = @@ -235,17 +215,17 @@ object GeographyType extends SpatialType { private[types] def toSrid(crs: String): Int = { // The special value "SRID:ANY" is used to represent mixed SRID values. if (crs.equalsIgnoreCase(GeographyType.MIXED_CRS)) { - GeographyType.MIXED_SRID + return GeographyType.MIXED_SRID } - // As for other valid CRS values, we currently offer limited support. - else if (crs.equalsIgnoreCase(GeographyType.GEOGRAPHY_DEFAULT_CRS) || - crs.equalsIgnoreCase(GeographyType.GEOGRAPHY_DEFAULT_EPSG_CRS)) { - GeographyType.GEOGRAPHY_DEFAULT_SRID - } else { + // For all other CRS values, we need to look up the corresponding SRID. + val srid = SpatialReferenceSystemMapper.get().getSrid(crs, GEOG) + if (srid == null) { + // If the CRS value is not recognized, we throw an exception. throw new SparkIllegalArgumentException( errorClass = "ST_INVALID_CRS_VALUE", messageParameters = Map("crs" -> crs)) } + srid } } diff --git a/sql/api/src/main/scala/org/apache/spark/sql/types/GeometryType.scala b/sql/api/src/main/scala/org/apache/spark/sql/types/GeometryType.scala index 5685bd19ca89a..ee60aa8db05ff 100644 --- a/sql/api/src/main/scala/org/apache/spark/sql/types/GeometryType.scala +++ b/sql/api/src/main/scala/org/apache/spark/sql/types/GeometryType.scala @@ -149,11 +149,16 @@ object GeometryType extends SpatialType { private final val GEOMETRY_MIXED_TYPE: GeometryType = GeometryType(MIXED_CRS) + /** + * Type enum alias for geometric reference system mapping. + */ + private final val GEOM = SpatialReferenceSystemMapper.Type.GEOMETRY + /** * Constructors for GeometryType. */ def apply(srid: Int): GeometryType = { - val crs = SpatialReferenceSystemMapper.get().getStringId(srid) + val crs = SpatialReferenceSystemMapper.get().getStringId(srid, GEOM) if (crs == null) { throw new SparkIllegalArgumentException( errorClass = "ST_INVALID_SRID_VALUE", @@ -191,7 +196,7 @@ object GeometryType extends SpatialType { return GeometryType.MIXED_SRID } // For all other CRS values, we need to look up the corresponding SRID. - val srid = SpatialReferenceSystemMapper.get().getSrid(crs) + val srid = SpatialReferenceSystemMapper.get().getSrid(crs, GEOM) if (srid == null) { // If the CRS value is not recognized, we throw an exception. throw new SparkIllegalArgumentException( diff --git a/sql/core/src/test/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemMapperSuite.java b/sql/core/src/test/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemMapperSuite.java index 69c803097ab5f..e0c9d8945cb61 100644 --- a/sql/core/src/test/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemMapperSuite.java +++ b/sql/core/src/test/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemMapperSuite.java @@ -22,33 +22,62 @@ public class SpatialReferenceSystemMapperSuite { + // Singleton instance of the SpatialReferenceSystemMapper to use across tests. + private final SpatialReferenceSystemMapper srsMapper = SpatialReferenceSystemMapper.get(); + + // Spatial types for GEOMETRY and GEOGRAPHY reference system mapping. + private final SpatialReferenceSystemMapper.Type GEOM = + SpatialReferenceSystemMapper.Type.GEOMETRY; + private final SpatialReferenceSystemMapper.Type GEOG = + SpatialReferenceSystemMapper.Type.GEOGRAPHY; + @Test public void getStringIdReturnsCorrectStringIdForValidSrid() { - SpatialReferenceSystemMapper srMapper = SpatialReferenceSystemMapper.get(); - Assertions.assertEquals("SRID:0", srMapper.getStringId(0)); - Assertions.assertEquals("EPSG:3857", srMapper.getStringId(3857)); - Assertions.assertEquals("OGC:CRS84", srMapper.getStringId(4326)); + // GEOMETRY. + Assertions.assertEquals("SRID:0", srsMapper.getStringId(0, GEOM)); + Assertions.assertEquals("EPSG:3857", srsMapper.getStringId(3857, GEOM)); + Assertions.assertEquals("OGC:CRS84", srsMapper.getStringId(4326, GEOM)); + // GEOGRAPHY. + Assertions.assertEquals("OGC:CRS84", srsMapper.getStringId(4326, GEOG)); } @Test public void getStringIdReturnsNullForInvalidSrid() { - SpatialReferenceSystemMapper srMapper = SpatialReferenceSystemMapper.get(); - Assertions.assertNull(srMapper.getStringId(-1)); - Assertions.assertNull(srMapper.getStringId(9999)); + // GEOMETRY. + Assertions.assertNull(srsMapper.getStringId(-2, GEOM)); + Assertions.assertNull(srsMapper.getStringId(-1, GEOM)); + Assertions.assertNull(srsMapper.getStringId(1, GEOM)); + Assertions.assertNull(srsMapper.getStringId(2, GEOM)); + Assertions.assertNull(srsMapper.getStringId(9999, GEOM)); + // GEOGRAPHY. + Assertions.assertNull(srsMapper.getStringId(-2, GEOG)); + Assertions.assertNull(srsMapper.getStringId(-1, GEOG)); + Assertions.assertNull(srsMapper.getStringId(0, GEOG)); + Assertions.assertNull(srsMapper.getStringId(1, GEOG)); + Assertions.assertNull(srsMapper.getStringId(2, GEOG)); + Assertions.assertNull(srsMapper.getStringId(3857, GEOG)); + Assertions.assertNull(srsMapper.getStringId(9999, GEOG)); } @Test public void getSridReturnsCorrectSridForValidStringId() { - SpatialReferenceSystemMapper srMapper = SpatialReferenceSystemMapper.get(); - Assertions.assertEquals(0, srMapper.getSrid("SRID:0")); - Assertions.assertEquals(3857, srMapper.getSrid("EPSG:3857")); - Assertions.assertEquals(4326, srMapper.getSrid("OGC:CRS84")); + // GEOMETRY. + Assertions.assertEquals(0, srsMapper.getSrid("SRID:0", GEOM)); + Assertions.assertEquals(3857, srsMapper.getSrid("EPSG:3857", GEOM)); + Assertions.assertEquals(4326, srsMapper.getSrid("OGC:CRS84", GEOM)); + // GEOGRAPHY. + Assertions.assertEquals(4326, srsMapper.getSrid("OGC:CRS84", GEOG)); } @Test public void getSridReturnsNullForInvalidStringId() { - SpatialReferenceSystemMapper srMapper = SpatialReferenceSystemMapper.get(); - Assertions.assertNull(srMapper.getSrid("INVALID:ID")); - Assertions.assertNull(srMapper.getSrid("EPSG:9999")); + // GEOMETRY. + Assertions.assertNull(srsMapper.getSrid("INVALID:ID", GEOM)); + Assertions.assertNull(srsMapper.getSrid("EPSG:9999", GEOM)); + // GEOGRAPHY. + Assertions.assertNull(srsMapper.getSrid("INVALID:ID", GEOG)); + Assertions.assertNull(srsMapper.getSrid("EPSG:9999", GEOG)); + Assertions.assertNull(srsMapper.getSrid("SRID:0", GEOG)); + Assertions.assertNull(srsMapper.getSrid("EPSG:3857", GEOG)); } } diff --git a/sql/core/src/test/scala/org/apache/spark/sql/types/GeographyTypeSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/types/GeographyTypeSuite.scala index 51de95826f81e..0a4988409f549 100644 --- a/sql/core/src/test/scala/org/apache/spark/sql/types/GeographyTypeSuite.scala +++ b/sql/core/src/test/scala/org/apache/spark/sql/types/GeographyTypeSuite.scala @@ -141,13 +141,13 @@ class GeographyTypeSuite extends SparkFunSuite { val validGeographies = Seq( "\"geography\"", "\"geography(OGC:CRS84)\"", - "\"geography(ogc:CRS84)\"", - "\"geography( ogc:CRS84 )\"", - "\"geography(EPSG:4326)\"", + "\"geography( OGC:CRS84 )\"", "\"geography(spherical)\"", + "\"geography(SPHERICAL)\"", "\"geography( spherical)\"", "\"geography(OGC:CRS84, spherical )\"", - "\"geography( OGC:CRS84 , spherical )\"" + "\"geography( OGC:CRS84 , spherical )\"", + "\"geography( OGC:CRS84 , SPHERICAL )\"" ) validGeographies.foreach { geog => DataType.fromJson(geog).isInstanceOf[GeographyType] From 9b5adfe061222e07e6974f9c6d428af0401f9d56 Mon Sep 17 00:00:00 2001 From: Uros Bojanic Date: Fri, 24 Oct 2025 00:16:50 +0200 Subject: [PATCH 2/5] Refactor SRS mapping --- ...CartesianSpatialReferenceSystemMapper.java | 33 +++++++ ...eographicSpatialReferenceSystemMapper.java | 33 +++++++ .../types/SpatialReferenceSystemCache.java | 98 +++++++++++++++++++ .../SpatialReferenceSystemInformation.java | 55 +++++++++++ .../types/SpatialReferenceSystemMapper.java | 85 +--------------- .../sql/internal/util/SingletonBase.java | 46 +++++++++ .../spark/sql/types/GeographyType.scala | 11 +-- .../apache/spark/sql/types/GeometryType.scala | 11 +-- .../SpatialReferenceSystemMapperSuite.java | 83 +++++++++------- .../sql/types/JavaGeographyTypeSuite.java | 8 +- .../sql/types/JavaGeometryTypeSuite.java | 5 +- 11 files changed, 332 insertions(+), 136 deletions(-) create mode 100644 sql/api/src/main/scala/org/apache/spark/sql/internal/types/CartesianSpatialReferenceSystemMapper.java create mode 100644 sql/api/src/main/scala/org/apache/spark/sql/internal/types/GeographicSpatialReferenceSystemMapper.java create mode 100644 sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemCache.java create mode 100644 sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemInformation.java create mode 100644 sql/api/src/main/scala/org/apache/spark/sql/internal/util/SingletonBase.java diff --git a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/CartesianSpatialReferenceSystemMapper.java b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/CartesianSpatialReferenceSystemMapper.java new file mode 100644 index 0000000000000..9196004b4a9a2 --- /dev/null +++ b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/CartesianSpatialReferenceSystemMapper.java @@ -0,0 +1,33 @@ +/* + * 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.internal.types; + +/** + * Class for providing SRS mappings for cartesian spatial reference systems. + */ +public class CartesianSpatialReferenceSystemMapper extends SpatialReferenceSystemMapper { + // Returns the string ID corresponding to the input SRID. If not supported, returns `null`. + public static String getStringId(int srid) { + return srsCache.isValidSrid(srid) ? srsCache.getSrsInfo(srid).stringId() : null; + } + + // Returns the SRID corresponding to the input string ID. If not supported, returns `null`. + public static Integer getSrid(String stringId) { + return srsCache.isValidStringId(stringId) ? srsCache.getSrsInfo(stringId).srid() : null; + } +} diff --git a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/GeographicSpatialReferenceSystemMapper.java b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/GeographicSpatialReferenceSystemMapper.java new file mode 100644 index 0000000000000..287c8761cfbb8 --- /dev/null +++ b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/GeographicSpatialReferenceSystemMapper.java @@ -0,0 +1,33 @@ +/* + * 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.internal.types; + +/** + * Class for providing SRS mappings for geographic spatial reference systems. + */ +public class GeographicSpatialReferenceSystemMapper extends SpatialReferenceSystemMapper { + // Returns the string ID corresponding to the input SRID. If not supported, returns `null`. + public static String getStringId(int srid) { + return srsCache.isGeographicSrid(srid) ? srsCache.getSrsInfo(srid).stringId() : null; + } + + // Returns the SRID corresponding to the input string ID. If not supported, returns `null`. + public static Integer getSrid(String stringId) { + return srsCache.isGeographicStringId(stringId) ? srsCache.getSrsInfo(stringId).srid() : null; + } +} diff --git a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemCache.java b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemCache.java new file mode 100644 index 0000000000000..242fc1bb10868 --- /dev/null +++ b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemCache.java @@ -0,0 +1,98 @@ +/* + * 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.internal.types; + +import org.apache.spark.sql.internal.util.SingletonBase; + +import java.util.HashMap; +import java.util.List; + +/** + * Class for maintaining the mappings between supported SRID/CRS values and the corresponding SRS. + */ +public class SpatialReferenceSystemCache extends SingletonBase { + + // Private constructor to prevent external instantiation. + private SpatialReferenceSystemCache() { + populateSpatialReferenceSystemInformationMapping(); + } + + @Override + protected SpatialReferenceSystemCache createInstance() { + return new SpatialReferenceSystemCache(); + } + + public static SpatialReferenceSystemCache getInstance() { + return SingletonBase.getInstance(new SpatialReferenceSystemCache()); + } + + // Hash map for defining the mappings from the integer SRID value to the full SRS information. + private final HashMap sridToSrs = + new HashMap<>(); + + // Hash map for defining the mappings from the string ID value to the full SRS information. + private final HashMap stringIdToSrs = + new HashMap<>(); + + // Helper method for building the SRID-to-SRS and stringID-to-SRS mappings. + private void populateSpatialReferenceSystemInformationMapping() { + // Currently, we only support a limited set of SRID / CRS values. However, we will soon extend + // this to support all the SRIDs supported by relevant authorities and libraries. The SRS list + // below will be updated accordingly, and the maps will be populated with more complete data. + List srsInformationList = List.of( + new SpatialReferenceSystemInformation(0, "SRID:0", false), + new SpatialReferenceSystemInformation(3857, "EPSG:3857", false), + new SpatialReferenceSystemInformation(4326, "OGC:CRS84", true) + ); + // Populate the mappings using the same SRS information objects, avoiding any duplication. + for (SpatialReferenceSystemInformation srsInformation: srsInformationList) { + sridToSrs.put(srsInformation.srid(), srsInformation); + stringIdToSrs.put(srsInformation.stringId(), srsInformation); + } + } + + // Utility method that can be used to check whether an SRID value is valid or not. + public boolean isValidSrid(int srid) { + return sridToSrs.containsKey(srid); + } + + // Utility method that can be used to check whether an SRID value is geographic or not. + public boolean isGeographicSrid(int srid) { + return isValidSrid(srid) && sridToSrs.get(srid).isGeographic(); + } + + // Utility method that can be used to check whether a string ID value is valid or not. + public boolean isValidStringId(String stringId) { + return stringIdToSrs.containsKey(stringId); + } + + // Utility method that can be used to check whether a string ID value is geographic or not. + public boolean isGeographicStringId(String stringId) { + return isValidStringId(stringId) && stringIdToSrs.get(stringId).isGeographic(); + } + + // Returns the SRS corresponding to the input SRID. If not supported, returns `null`. + public SpatialReferenceSystemInformation getSrsInfo(int srid) { + return sridToSrs.getOrDefault(srid, null); + } + + // Returns the SRS corresponding to the input string ID. If not supported, returns `null`. + public SpatialReferenceSystemInformation getSrsInfo(String stringId) { + return stringIdToSrs.getOrDefault(stringId, null); + } +} diff --git a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemInformation.java b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemInformation.java new file mode 100644 index 0000000000000..5304ebeddec03 --- /dev/null +++ b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemInformation.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.spark.sql.internal.types; + +/** + * Class for maintaining information about a spatial reference system (SRS). + */ +public class SpatialReferenceSystemInformation { + + // Field storing the spatial reference identifier (SRID) value of this SRS. + private final int srid; + + // Field storing the string ID of the corresponding coordinate reference system (CRS). + private final String stringId; + + // Field indicating whether the spatial reference system (SRS) is geographic or not. + private final boolean isGeographic; + + // Constructor for creating an instance of the spatial reference system information class. + public SpatialReferenceSystemInformation(int srid, String stringId, boolean isGeographic) { + this.srid = srid; + this.stringId = stringId; + this.isGeographic = isGeographic; + } + + // Specialized getters for each of the spatial reference system (SRS) information fields. + + public int srid() { + return srid; + } + + public String stringId() { + return stringId; + } + + public boolean isGeographic() { + return isGeographic; + } + +} diff --git a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemMapper.java b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemMapper.java index 275fe55479a15..2993ba05b76f8 100644 --- a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemMapper.java +++ b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemMapper.java @@ -17,85 +17,10 @@ package org.apache.spark.sql.internal.types; -import java.util.HashMap; -import java.util.HashSet; - -/* - * Class for maintaining mappings between supported SRID values and the string ID of the - * corresponding CRS. +/** + * Abstract class for providing SRS mappings for spatial reference systems. */ -public class SpatialReferenceSystemMapper { - - // We implement this class as a singleton (we disallow construction). - private SpatialReferenceSystemMapper() {} - - private static final SpatialReferenceSystemMapper Instance = new SpatialReferenceSystemMapper(); - - // Returns the unique instance of this class. - public static SpatialReferenceSystemMapper get() { - return Instance; - } - - // The `SpatialReferenceSystemMapper` class can be used for both GEOGRAPHY and GEOMETRY types. - // This enumeration defines the two spatial type options, in the context of SRS mapping. - public enum Type { - GEOGRAPHY, - GEOMETRY - } - - // Hash maps for defining the mappings to/from SRID and string ID for a CRS. - private static final HashMap sridToStringId = buildSridToStringIdMap(); - private static final HashMap stringIdToSrid = buildStringIdToSridMap(); - - // Hash set for keeping track of the supported SRID values for geographic CRS. - public static final HashSet geographicSrids = buildGeographicSridSet(); - - // Returns the string ID corresponding to the input SRID. If the input SRID is not supported, - // `null` is returned. - public String getStringId(int srid, Type type) { - String stringId = sridToStringId.get(srid); - return switch (type) { - case GEOGRAPHY -> geographicSrids.contains(srid) ? stringId : null; - case GEOMETRY -> stringId; - }; - } - - // Returns the SRID corresponding to the input string ID. If the input string ID is not - // supported, `null` is returned. - public Integer getSrid(String stringId, Type type) { - Integer srid = stringIdToSrid.get(stringId); - return switch (type) { - case GEOGRAPHY -> geographicSrids.contains(srid) ? srid : null; - case GEOMETRY -> srid; - }; - } - - // Currently, we only support a limited set of SRID / CRS mappings. However, we will soon extend - // this to support all the SRIDs supported by relevant authorities and libraries. The methods - // below will be updated accordingly, in order to populate the mappings with more complete data. - - // Helper method for building the SRID-to-string-ID mapping. - private static HashMap buildSridToStringIdMap() { - HashMap map = new HashMap<>(); - map.put(0, "SRID:0"); // Unspecified - map.put(3857, "EPSG:3857"); // Web Mercator - map.put(4326, "OGC:CRS84"); // WGS84 - return map; - } - - // Helper method for building the string-ID-to-SRID mapping. - private static HashMap buildStringIdToSridMap() { - HashMap map = new HashMap<>(); - map.put("SRID:0", 0); // Unspecified - map.put("EPSG:3857", 3857); // Web Mercator - map.put("OGC:CRS84", 4326); // WGS84 - return map; - } - - // Helper method for building the set of supported geographic SRID values. - private static HashSet buildGeographicSridSet() { - HashSet set = new HashSet<>(); - set.add(4326); // WGS84 - return set; - } +public abstract class SpatialReferenceSystemMapper { + protected static final SpatialReferenceSystemCache srsCache = + SpatialReferenceSystemCache.getInstance(); } diff --git a/sql/api/src/main/scala/org/apache/spark/sql/internal/util/SingletonBase.java b/sql/api/src/main/scala/org/apache/spark/sql/internal/util/SingletonBase.java new file mode 100644 index 0000000000000..7928d5f37790d --- /dev/null +++ b/sql/api/src/main/scala/org/apache/spark/sql/internal/util/SingletonBase.java @@ -0,0 +1,46 @@ +/* + * 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.internal.util; + +/** + * Generic abstract base class for implementing the singleton design pattern. This class is lazily + * initialized, thread-safe, and generic (it can be used to create singletons for any type `T`). + */ +public abstract class SingletonBase { + // The singleton `instance` is created lazily, meaning that it is not instantiated until the + // `getInstance()` method is called for the first time. Note that this solution is thread-safe. + private static volatile Object instance = null; + + // The `createInstance` method is abstract, so all subclasses must implement it. Note that + // this method defines how the singleton instance is created for the specific subclass. + protected abstract T createInstance(); + + // The `getInstance` method uses double-checked locking to ensure efficient and safe instance + // creation. The singleton instance is created only once, even in a multithreaded environment. + @SuppressWarnings("unchecked") + public static T getInstance(SingletonBase singletonBase) { + if (instance == null) { + synchronized (SingletonBase.class) { + if (instance == null) { + instance = singletonBase.createInstance(); + } + } + } + return (T) instance; + } +} diff --git a/sql/api/src/main/scala/org/apache/spark/sql/types/GeographyType.scala b/sql/api/src/main/scala/org/apache/spark/sql/types/GeographyType.scala index 11e43d0690335..638ae79351846 100644 --- a/sql/api/src/main/scala/org/apache/spark/sql/types/GeographyType.scala +++ b/sql/api/src/main/scala/org/apache/spark/sql/types/GeographyType.scala @@ -21,7 +21,7 @@ import org.json4s.JsonAST.{JString, JValue} import org.apache.spark.SparkIllegalArgumentException import org.apache.spark.annotation.Experimental -import org.apache.spark.sql.internal.types.SpatialReferenceSystemMapper +import org.apache.spark.sql.internal.types.GeographicSpatialReferenceSystemMapper /** * The data type representing GEOGRAPHY values which are spatial objects, as defined in the Open @@ -157,16 +157,11 @@ object GeographyType extends SpatialType { private final val GEOGRAPHY_MIXED_TYPE: GeographyType = GeographyType(MIXED_CRS, GEOGRAPHY_DEFAULT_ALGORITHM) - /** - * Type enum alias for geographic reference system mapping. - */ - private final val GEOG = SpatialReferenceSystemMapper.Type.GEOGRAPHY - /** * Constructors for GeographyType. */ def apply(srid: Int): GeographyType = { - val crs = SpatialReferenceSystemMapper.get().getStringId(srid, GEOG) + val crs = GeographicSpatialReferenceSystemMapper.getStringId(srid) if (crs == null) { throw new SparkIllegalArgumentException( errorClass = "ST_INVALID_SRID_VALUE", @@ -218,7 +213,7 @@ object GeographyType extends SpatialType { return GeographyType.MIXED_SRID } // For all other CRS values, we need to look up the corresponding SRID. - val srid = SpatialReferenceSystemMapper.get().getSrid(crs, GEOG) + val srid = GeographicSpatialReferenceSystemMapper.getSrid(crs) if (srid == null) { // If the CRS value is not recognized, we throw an exception. throw new SparkIllegalArgumentException( diff --git a/sql/api/src/main/scala/org/apache/spark/sql/types/GeometryType.scala b/sql/api/src/main/scala/org/apache/spark/sql/types/GeometryType.scala index ee60aa8db05ff..77a6b365c042a 100644 --- a/sql/api/src/main/scala/org/apache/spark/sql/types/GeometryType.scala +++ b/sql/api/src/main/scala/org/apache/spark/sql/types/GeometryType.scala @@ -21,7 +21,7 @@ import org.json4s.JsonAST.{JString, JValue} import org.apache.spark.SparkIllegalArgumentException import org.apache.spark.annotation.Experimental -import org.apache.spark.sql.internal.types.SpatialReferenceSystemMapper +import org.apache.spark.sql.internal.types.CartesianSpatialReferenceSystemMapper /** * The data type representing GEOMETRY values which are spatial objects, as defined in the Open @@ -149,16 +149,11 @@ object GeometryType extends SpatialType { private final val GEOMETRY_MIXED_TYPE: GeometryType = GeometryType(MIXED_CRS) - /** - * Type enum alias for geometric reference system mapping. - */ - private final val GEOM = SpatialReferenceSystemMapper.Type.GEOMETRY - /** * Constructors for GeometryType. */ def apply(srid: Int): GeometryType = { - val crs = SpatialReferenceSystemMapper.get().getStringId(srid, GEOM) + val crs = CartesianSpatialReferenceSystemMapper.getStringId(srid) if (crs == null) { throw new SparkIllegalArgumentException( errorClass = "ST_INVALID_SRID_VALUE", @@ -196,7 +191,7 @@ object GeometryType extends SpatialType { return GeometryType.MIXED_SRID } // For all other CRS values, we need to look up the corresponding SRID. - val srid = SpatialReferenceSystemMapper.get().getSrid(crs, GEOM) + val srid = CartesianSpatialReferenceSystemMapper.getSrid(crs) if (srid == null) { // If the CRS value is not recognized, we throw an exception. throw new SparkIllegalArgumentException( diff --git a/sql/core/src/test/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemMapperSuite.java b/sql/core/src/test/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemMapperSuite.java index e0c9d8945cb61..5f31cf023d7c4 100644 --- a/sql/core/src/test/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemMapperSuite.java +++ b/sql/core/src/test/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemMapperSuite.java @@ -20,64 +20,81 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.util.List; + public class SpatialReferenceSystemMapperSuite { - // Singleton instance of the SpatialReferenceSystemMapper to use across tests. - private final SpatialReferenceSystemMapper srsMapper = SpatialReferenceSystemMapper.get(); + // Tests for the SpatialReferenceSystemInformation class. + + @Test + void testSpatialReferenceSystemInformation() { + // Test case schema. + record TestCase(int srid, String stringId, boolean isGeographic) {} + // Define test cases. + List testCases = List.of( + new TestCase(0, "SRID:0", false), + new TestCase(3857, "EPSG:3857", false), + new TestCase(4326, "OGC:CRS84", true) + ); + // Execute test cases. + for (TestCase tc : testCases) { + SpatialReferenceSystemInformation srsInfo = + new SpatialReferenceSystemInformation(tc.srid(), tc.stringId(), tc.isGeographic()); + Assertions.assertEquals(tc.srid(), srsInfo.srid()); + Assertions.assertEquals(tc.stringId(), srsInfo.stringId()); + Assertions.assertEquals(tc.isGeographic(), srsInfo.isGeographic()); + } + } - // Spatial types for GEOMETRY and GEOGRAPHY reference system mapping. - private final SpatialReferenceSystemMapper.Type GEOM = - SpatialReferenceSystemMapper.Type.GEOMETRY; - private final SpatialReferenceSystemMapper.Type GEOG = - SpatialReferenceSystemMapper.Type.GEOGRAPHY; + // Tests for the SpatialReferenceSystemMapper classes. @Test public void getStringIdReturnsCorrectStringIdForValidSrid() { // GEOMETRY. - Assertions.assertEquals("SRID:0", srsMapper.getStringId(0, GEOM)); - Assertions.assertEquals("EPSG:3857", srsMapper.getStringId(3857, GEOM)); - Assertions.assertEquals("OGC:CRS84", srsMapper.getStringId(4326, GEOM)); + Assertions.assertEquals("SRID:0", CartesianSpatialReferenceSystemMapper.getStringId(0)); + Assertions.assertEquals("EPSG:3857", CartesianSpatialReferenceSystemMapper.getStringId(3857)); + Assertions.assertEquals("OGC:CRS84", CartesianSpatialReferenceSystemMapper.getStringId(4326)); // GEOGRAPHY. - Assertions.assertEquals("OGC:CRS84", srsMapper.getStringId(4326, GEOG)); + Assertions.assertEquals("OGC:CRS84", GeographicSpatialReferenceSystemMapper.getStringId(4326)); } @Test public void getStringIdReturnsNullForInvalidSrid() { // GEOMETRY. - Assertions.assertNull(srsMapper.getStringId(-2, GEOM)); - Assertions.assertNull(srsMapper.getStringId(-1, GEOM)); - Assertions.assertNull(srsMapper.getStringId(1, GEOM)); - Assertions.assertNull(srsMapper.getStringId(2, GEOM)); - Assertions.assertNull(srsMapper.getStringId(9999, GEOM)); + Assertions.assertNull(CartesianSpatialReferenceSystemMapper.getStringId(-2)); + Assertions.assertNull(CartesianSpatialReferenceSystemMapper.getStringId(-1)); + Assertions.assertNull(CartesianSpatialReferenceSystemMapper.getStringId(1)); + Assertions.assertNull(CartesianSpatialReferenceSystemMapper.getStringId(2)); + Assertions.assertNull(CartesianSpatialReferenceSystemMapper.getStringId(9999)); // GEOGRAPHY. - Assertions.assertNull(srsMapper.getStringId(-2, GEOG)); - Assertions.assertNull(srsMapper.getStringId(-1, GEOG)); - Assertions.assertNull(srsMapper.getStringId(0, GEOG)); - Assertions.assertNull(srsMapper.getStringId(1, GEOG)); - Assertions.assertNull(srsMapper.getStringId(2, GEOG)); - Assertions.assertNull(srsMapper.getStringId(3857, GEOG)); - Assertions.assertNull(srsMapper.getStringId(9999, GEOG)); + Assertions.assertNull(GeographicSpatialReferenceSystemMapper.getStringId(-2)); + Assertions.assertNull(GeographicSpatialReferenceSystemMapper.getStringId(-1)); + Assertions.assertNull(GeographicSpatialReferenceSystemMapper.getStringId(0)); + Assertions.assertNull(GeographicSpatialReferenceSystemMapper.getStringId(1)); + Assertions.assertNull(GeographicSpatialReferenceSystemMapper.getStringId(2)); + Assertions.assertNull(GeographicSpatialReferenceSystemMapper.getStringId(3857)); + Assertions.assertNull(GeographicSpatialReferenceSystemMapper.getStringId(9999)); } @Test public void getSridReturnsCorrectSridForValidStringId() { // GEOMETRY. - Assertions.assertEquals(0, srsMapper.getSrid("SRID:0", GEOM)); - Assertions.assertEquals(3857, srsMapper.getSrid("EPSG:3857", GEOM)); - Assertions.assertEquals(4326, srsMapper.getSrid("OGC:CRS84", GEOM)); + Assertions.assertEquals(0, CartesianSpatialReferenceSystemMapper.getSrid("SRID:0")); + Assertions.assertEquals(3857, CartesianSpatialReferenceSystemMapper.getSrid("EPSG:3857")); + Assertions.assertEquals(4326, CartesianSpatialReferenceSystemMapper.getSrid("OGC:CRS84")); // GEOGRAPHY. - Assertions.assertEquals(4326, srsMapper.getSrid("OGC:CRS84", GEOG)); + Assertions.assertEquals(4326, GeographicSpatialReferenceSystemMapper.getSrid("OGC:CRS84")); } @Test public void getSridReturnsNullForInvalidStringId() { // GEOMETRY. - Assertions.assertNull(srsMapper.getSrid("INVALID:ID", GEOM)); - Assertions.assertNull(srsMapper.getSrid("EPSG:9999", GEOM)); + Assertions.assertNull(CartesianSpatialReferenceSystemMapper.getSrid("INVALID:ID")); + Assertions.assertNull(CartesianSpatialReferenceSystemMapper.getSrid("EPSG:9999")); // GEOGRAPHY. - Assertions.assertNull(srsMapper.getSrid("INVALID:ID", GEOG)); - Assertions.assertNull(srsMapper.getSrid("EPSG:9999", GEOG)); - Assertions.assertNull(srsMapper.getSrid("SRID:0", GEOG)); - Assertions.assertNull(srsMapper.getSrid("EPSG:3857", GEOG)); + Assertions.assertNull(GeographicSpatialReferenceSystemMapper.getSrid("INVALID:ID")); + Assertions.assertNull(GeographicSpatialReferenceSystemMapper.getSrid("EPSG:9999")); + Assertions.assertNull(GeographicSpatialReferenceSystemMapper.getSrid("SRID:0")); + Assertions.assertNull(GeographicSpatialReferenceSystemMapper.getSrid("EPSG:3857")); } } diff --git a/sql/core/src/test/scala/org/apache/spark/sql/types/JavaGeographyTypeSuite.java b/sql/core/src/test/scala/org/apache/spark/sql/types/JavaGeographyTypeSuite.java index 39ecd3893c7f0..738c253f33679 100644 --- a/sql/core/src/test/scala/org/apache/spark/sql/types/JavaGeographyTypeSuite.java +++ b/sql/core/src/test/scala/org/apache/spark/sql/types/JavaGeographyTypeSuite.java @@ -17,7 +17,7 @@ package org.apache.spark.sql.types; -import org.apache.spark.sql.internal.types.SpatialReferenceSystemMapper; +import org.apache.spark.sql.internal.types.GeographicSpatialReferenceSystemMapper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -62,9 +62,9 @@ public void geographyTypeWithSpecifiedInvalidSridTest() { @Test public void geographyTypeWithSpecifiedValidCrsTest() { - // Valid CRS values for GEOGRAPHY. Note that only 4326 is supported for now. - int srid = GeographyType.GEOGRAPHY_DEFAULT_SRID(); - Stream.of("OGC:CRS84", "EPSG:4326").forEach(crs -> { + // Valid CRS values for GEOGRAPHY. Note that only "OGC:CRS84" is supported for now. + Stream.of("OGC:CRS84").forEach(crs -> { + Integer srid = GeographicSpatialReferenceSystemMapper.getSrid(crs); DataType geographyType = DataTypes.createGeographyType(crs); Assertions.assertEquals("GEOGRAPHY(" + srid + ")", geographyType.sql()); Assertions.assertEquals("geography(" + srid + ")", geographyType.typeName()); diff --git a/sql/core/src/test/scala/org/apache/spark/sql/types/JavaGeometryTypeSuite.java b/sql/core/src/test/scala/org/apache/spark/sql/types/JavaGeometryTypeSuite.java index 03485feeb3f91..dd19de0f7c32d 100644 --- a/sql/core/src/test/scala/org/apache/spark/sql/types/JavaGeometryTypeSuite.java +++ b/sql/core/src/test/scala/org/apache/spark/sql/types/JavaGeometryTypeSuite.java @@ -17,7 +17,7 @@ package org.apache.spark.sql.types; -import org.apache.spark.sql.internal.types.SpatialReferenceSystemMapper; +import org.apache.spark.sql.internal.types.CartesianSpatialReferenceSystemMapper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -63,9 +63,8 @@ public void geometryTypeWithSpecifiedInvalidSridTest() { @Test public void geometryTypeWithSpecifiedValidCrsTest() { // Valid CRS values for GEOMETRY. - SpatialReferenceSystemMapper srsMapper = SpatialReferenceSystemMapper.get(); Stream.of("SRID:0", "EPSG:3857", "OGC:CRS84").forEach(crs -> { - int srid = srsMapper.getSrid(crs); + Integer srid = CartesianSpatialReferenceSystemMapper.getSrid(crs); DataType geometryType = DataTypes.createGeometryType(crs); Assertions.assertEquals("GEOMETRY(" + srid + ")", geometryType.sql()); Assertions.assertEquals("geometry(" + srid + ")", geometryType.typeName()); From 2b17eb81448a16b8e280343d691f205662c625dd Mon Sep 17 00:00:00 2001 From: Uros Bojanic Date: Fri, 24 Oct 2025 19:43:26 +0200 Subject: [PATCH 3/5] Avoid double lookup --- ...CartesianSpatialReferenceSystemMapper.java | 6 ++++-- ...eographicSpatialReferenceSystemMapper.java | 6 ++++-- .../types/SpatialReferenceSystemCache.java | 20 ------------------- 3 files changed, 8 insertions(+), 24 deletions(-) diff --git a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/CartesianSpatialReferenceSystemMapper.java b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/CartesianSpatialReferenceSystemMapper.java index 9196004b4a9a2..d7729e88a3331 100644 --- a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/CartesianSpatialReferenceSystemMapper.java +++ b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/CartesianSpatialReferenceSystemMapper.java @@ -23,11 +23,13 @@ public class CartesianSpatialReferenceSystemMapper extends SpatialReferenceSystemMapper { // Returns the string ID corresponding to the input SRID. If not supported, returns `null`. public static String getStringId(int srid) { - return srsCache.isValidSrid(srid) ? srsCache.getSrsInfo(srid).stringId() : null; + SpatialReferenceSystemInformation srsInfo = srsCache.getSrsInfo(srid); + return srsInfo != null ? srsInfo.stringId() : null; } // Returns the SRID corresponding to the input string ID. If not supported, returns `null`. public static Integer getSrid(String stringId) { - return srsCache.isValidStringId(stringId) ? srsCache.getSrsInfo(stringId).srid() : null; + SpatialReferenceSystemInformation srsInfo = srsCache.getSrsInfo(stringId); + return srsInfo != null ? srsInfo.srid() : null; } } diff --git a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/GeographicSpatialReferenceSystemMapper.java b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/GeographicSpatialReferenceSystemMapper.java index 287c8761cfbb8..85e0cc53658c6 100644 --- a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/GeographicSpatialReferenceSystemMapper.java +++ b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/GeographicSpatialReferenceSystemMapper.java @@ -23,11 +23,13 @@ public class GeographicSpatialReferenceSystemMapper extends SpatialReferenceSystemMapper { // Returns the string ID corresponding to the input SRID. If not supported, returns `null`. public static String getStringId(int srid) { - return srsCache.isGeographicSrid(srid) ? srsCache.getSrsInfo(srid).stringId() : null; + SpatialReferenceSystemInformation srsInfo = srsCache.getSrsInfo(srid); + return srsInfo != null && srsInfo.isGeographic() ? srsInfo.stringId() : null; } // Returns the SRID corresponding to the input string ID. If not supported, returns `null`. public static Integer getSrid(String stringId) { - return srsCache.isGeographicStringId(stringId) ? srsCache.getSrsInfo(stringId).srid() : null; + SpatialReferenceSystemInformation srsInfo = srsCache.getSrsInfo(stringId); + return srsInfo != null && srsInfo.isGeographic() ? srsInfo.srid() : null; } } diff --git a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemCache.java b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemCache.java index 242fc1bb10868..a7dc1813cf6c4 100644 --- a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemCache.java +++ b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemCache.java @@ -66,26 +66,6 @@ private void populateSpatialReferenceSystemInformationMapping() { } } - // Utility method that can be used to check whether an SRID value is valid or not. - public boolean isValidSrid(int srid) { - return sridToSrs.containsKey(srid); - } - - // Utility method that can be used to check whether an SRID value is geographic or not. - public boolean isGeographicSrid(int srid) { - return isValidSrid(srid) && sridToSrs.get(srid).isGeographic(); - } - - // Utility method that can be used to check whether a string ID value is valid or not. - public boolean isValidStringId(String stringId) { - return stringIdToSrs.containsKey(stringId); - } - - // Utility method that can be used to check whether a string ID value is geographic or not. - public boolean isGeographicStringId(String stringId) { - return isValidStringId(stringId) && stringIdToSrs.get(stringId).isGeographic(); - } - // Returns the SRS corresponding to the input SRID. If not supported, returns `null`. public SpatialReferenceSystemInformation getSrsInfo(int srid) { return sridToSrs.getOrDefault(srid, null); From 12451f05f763439636d10dd96e67add384612345 Mon Sep 17 00:00:00 2001 From: Uros Bojanic Date: Tue, 28 Oct 2025 17:31:20 +0100 Subject: [PATCH 4/5] Remove SingletonBase --- .../types/SpatialReferenceSystemCache.java | 24 ++++++---- .../sql/internal/util/SingletonBase.java | 46 ------------------- 2 files changed, 15 insertions(+), 55 deletions(-) delete mode 100644 sql/api/src/main/scala/org/apache/spark/sql/internal/util/SingletonBase.java diff --git a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemCache.java b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemCache.java index a7dc1813cf6c4..40094785600fb 100644 --- a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemCache.java +++ b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemCache.java @@ -17,28 +17,34 @@ package org.apache.spark.sql.internal.types; -import org.apache.spark.sql.internal.util.SingletonBase; - import java.util.HashMap; import java.util.List; /** * Class for maintaining the mappings between supported SRID/CRS values and the corresponding SRS. */ -public class SpatialReferenceSystemCache extends SingletonBase { +public class SpatialReferenceSystemCache { - // Private constructor to prevent external instantiation. + // Private constructor to prevent external instantiation of this singleton class. private SpatialReferenceSystemCache() { populateSpatialReferenceSystemInformationMapping(); } - @Override - protected SpatialReferenceSystemCache createInstance() { - return new SpatialReferenceSystemCache(); - } + // The singleton `instance` is created lazily, meaning that it is not instantiated until the + // `getInstance()` method is called for the first time. Note that this solution is thread-safe. + private static volatile SpatialReferenceSystemCache instance = null; + // The `getInstance` method uses double-checked locking to ensure efficient and safe instance + // creation. The singleton instance is created only once, even in a multithreaded environment. public static SpatialReferenceSystemCache getInstance() { - return SingletonBase.getInstance(new SpatialReferenceSystemCache()); + if (instance == null) { + synchronized (SpatialReferenceSystemCache.class) { + if (instance == null) { + instance = new SpatialReferenceSystemCache(); + } + } + } + return instance; } // Hash map for defining the mappings from the integer SRID value to the full SRS information. diff --git a/sql/api/src/main/scala/org/apache/spark/sql/internal/util/SingletonBase.java b/sql/api/src/main/scala/org/apache/spark/sql/internal/util/SingletonBase.java deleted file mode 100644 index 7928d5f37790d..0000000000000 --- a/sql/api/src/main/scala/org/apache/spark/sql/internal/util/SingletonBase.java +++ /dev/null @@ -1,46 +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.internal.util; - -/** - * Generic abstract base class for implementing the singleton design pattern. This class is lazily - * initialized, thread-safe, and generic (it can be used to create singletons for any type `T`). - */ -public abstract class SingletonBase { - // The singleton `instance` is created lazily, meaning that it is not instantiated until the - // `getInstance()` method is called for the first time. Note that this solution is thread-safe. - private static volatile Object instance = null; - - // The `createInstance` method is abstract, so all subclasses must implement it. Note that - // this method defines how the singleton instance is created for the specific subclass. - protected abstract T createInstance(); - - // The `getInstance` method uses double-checked locking to ensure efficient and safe instance - // creation. The singleton instance is created only once, even in a multithreaded environment. - @SuppressWarnings("unchecked") - public static T getInstance(SingletonBase singletonBase) { - if (instance == null) { - synchronized (SingletonBase.class) { - if (instance == null) { - instance = singletonBase.createInstance(); - } - } - } - return (T) instance; - } -} From 5f0f5406e6c01d4b2d96a4642f2f49527ad77b4d Mon Sep 17 00:00:00 2001 From: Uros Bojanic Date: Wed, 29 Oct 2025 01:46:19 +0100 Subject: [PATCH 5/5] Use Java record --- .../SpatialReferenceSystemInformation.java | 35 +++---------------- 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemInformation.java b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemInformation.java index 5304ebeddec03..46fbfd9e9ac17 100644 --- a/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemInformation.java +++ b/sql/api/src/main/scala/org/apache/spark/sql/internal/types/SpatialReferenceSystemInformation.java @@ -20,36 +20,11 @@ /** * Class for maintaining information about a spatial reference system (SRS). */ -public class SpatialReferenceSystemInformation { - +public record SpatialReferenceSystemInformation( // Field storing the spatial reference identifier (SRID) value of this SRS. - private final int srid; - + int srid, // Field storing the string ID of the corresponding coordinate reference system (CRS). - private final String stringId; - + String stringId, // Field indicating whether the spatial reference system (SRS) is geographic or not. - private final boolean isGeographic; - - // Constructor for creating an instance of the spatial reference system information class. - public SpatialReferenceSystemInformation(int srid, String stringId, boolean isGeographic) { - this.srid = srid; - this.stringId = stringId; - this.isGeographic = isGeographic; - } - - // Specialized getters for each of the spatial reference system (SRS) information fields. - - public int srid() { - return srid; - } - - public String stringId() { - return stringId; - } - - public boolean isGeographic() { - return isGeographic; - } - -} + boolean isGeographic +) {}