diff --git a/examples/README.md b/examples/README.md
index cde819ba3f0..85bdd35bd17 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -29,6 +29,7 @@ The folder structure of this repository is as follows.
* spark-sql: a Scala template shows how to use Sedona RDD, DataFrame and SQL API
* flink-sql: a Java template show how to use Sedona SQL via Flink Table APIs
+* java-spark-sql: a pure Java template implementation and use case for Sedona RDD, DataFrame and SQL API
## Compile and package
@@ -59,6 +60,8 @@ To run the jar in this way, you need to:
We highly suggest you use IDEs to run template projects on your local machine. For Scala, we recommend IntelliJ IDEA with Scala plug-in. For Java, we recommend IntelliJ IDEA and Eclipse. With the help of IDEs, **you don't have to prepare anything** (even don't need to download and set up Spark!). As long as you have Scala and Java, everything works properly!
+* In the case of the java-spark-sql template, execute `mvn test` to run JUnit tests using sample GeoParquet file.
+
### Scala
Import the Scala template project as SBT project. Then run the Main file in this project.
diff --git a/examples/java-spark-sql/.gitignore b/examples/java-spark-sql/.gitignore
new file mode 100644
index 00000000000..b14e5ffe4de
--- /dev/null
+++ b/examples/java-spark-sql/.gitignore
@@ -0,0 +1,20 @@
+# 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.
+
+/.idea/
+/target/
+dependency-reduced-pom.xml
diff --git a/examples/java-spark-sql/pom.xml b/examples/java-spark-sql/pom.xml
new file mode 100644
index 00000000000..640cf400f25
--- /dev/null
+++ b/examples/java-spark-sql/pom.xml
@@ -0,0 +1,184 @@
+
+
+
+ 4.0.0
+
+ org.apache.sedona
+ sedona-java-spark-example
+ 1.6.1
+ Sedona : Examples : Java Spark SQL
+ Example project for Apache Sedona with Java and Spark.
+
+
+
+ provided
+ test
+
+ 1.6.1
+ 1.8.0-33.1
+ 3.5.7
+ 4.0.1
+ 3.0.0
+
+
+
+
+ org.datasyslab
+ geotools-wrapper
+ ${geotools.version}
+
+
+ org.apache.sedona
+ sedona-spark-shaded-3.5_2.13
+ ${sedona.version}
+
+
+ org.apache.spark
+ spark-core_2.13
+ ${spark.version}
+
+
+ org.apache.spark
+ spark-sql_2.13
+ ${spark.version}
+ ${spark.scope}
+
+
+ javax.servlet
+ javax.servlet-api
+ ${javax.servlet.version}
+ ${javax.scope}
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.2.0-M1
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.2.5
+
+
+ --add-opens=java.base/sun.nio.ch=ALL-UNNAMED
+ --add-opens=java.base/java.nio=ALL-UNNAMED
+ --add-opens=java.base/java.lang=ALL-UNNAMED
+ --add-opens=java.base/java.lang.invoke=ALL-UNNAMED
+ --add-opens=java.base/java.util=ALL-UNNAMED
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.1
+
+
+ package
+
+ shade
+
+
+
+
+
+
+ reference.conf
+
+
+
+ Java Advanced Imaging Image I/O Tools
+ 1.1
+ Sun Microsystems, Inc.
+ com.sun.media.imageio
+ 1.1
+ Sun Microsystems, Inc.
+ com.sun.media.imageio
+
+
+
+ spark.SedonaGeoParquetMain
+
+
+
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+
+
+
+
+ --add-opens=java.base/sun.nio.ch=ALL-UNNAMED
+ --add-opens=java.base/java.nio=ALL-UNNAMED
+ --add-opens=java.base/java.lang=ALL-UNNAMED
+ --add-opens=java.base/java.util=ALL-UNNAMED
+
+
+
+
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+ ${spotless.version}
+
+
+
+
+
+
+ .gitattributes
+ .gitignore
+
+
+
+
+
+ true
+ 4
+
+
+
+
+
+
+ 1.10
+
+ true
+ false
+
+
+
+
+
+
+
diff --git a/examples/java-spark-sql/src/main/java/spark/GeoParquetAccessor.java b/examples/java-spark-sql/src/main/java/spark/GeoParquetAccessor.java
new file mode 100644
index 00000000000..ba8e6a1f659
--- /dev/null
+++ b/examples/java-spark-sql/src/main/java/spark/GeoParquetAccessor.java
@@ -0,0 +1,90 @@
+/*
+ * 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 spark;
+
+import org.apache.sedona.core.spatialOperator.RangeQuery;
+import org.apache.sedona.core.spatialOperator.SpatialPredicate;
+import org.apache.sedona.core.spatialRDD.SpatialRDD;
+import org.apache.sedona.sql.utils.Adapter;
+import org.apache.spark.sql.Dataset;
+import org.apache.spark.sql.Row;
+import org.apache.spark.sql.SparkSession;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.Polygon;
+
+import java.util.List;
+
+
+public class GeoParquetAccessor {
+
+ private final SparkSession session;
+ private String parquetPath;
+
+ public GeoParquetAccessor() {
+ this.session = new SedonaSparkSession().getSession();
+ this.parquetPath = "";
+ }
+
+ //Overload with constructor that has Spark session provided
+ //Use to avoid error - can't have two SparkContext objects on one JVM
+ public GeoParquetAccessor(SparkSession session, String parquetPath) {
+ this.session = session;
+ this.parquetPath = parquetPath;
+ }
+
+ public List selectFeaturesByPolygon(double xmin, double ymax,
+ double xmax, double ymin,
+ String geometryColumn) {
+
+ //Read the GeoParquet file into a DataFrame
+ Dataset insarDF = session.read().format("geoparquet").load(parquetPath);
+
+ //Convert the DataFrame to a SpatialRDD
+ //The second argument to toSpatialRdd is the name of the geometry column.
+ SpatialRDD insarRDD = Adapter.toSpatialRdd(insarDF, geometryColumn);
+
+ // Define the polygon for the spatial query
+ GeometryFactory geometryFactory = new GeometryFactory();
+ Coordinate[] coordinates = new Coordinate[] {
+ new Coordinate(xmin, ymin),
+ new Coordinate(xmax, ymin),
+ new Coordinate(xmax, ymax),
+ new Coordinate(xmin, ymax),
+ new Coordinate(xmin, ymin) // A closed polygon has the same start and end coordinate
+ };
+ Polygon queryPolygon = geometryFactory.createPolygon(coordinates);
+
+ // Perform the spatial range query
+ // This will return all geometries that intersect with the query polygon.
+ // Alternatives are SpatialPredicate.CONTAINS or SpatialPredicate.WITHIN
+ SpatialRDD resultRDD = new SpatialRDD<>();
+ try {
+ resultRDD.rawSpatialRDD = RangeQuery.SpatialRangeQuery(insarRDD, queryPolygon, SpatialPredicate.INTERSECTS, false);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ // Collect the results back to the driver
+ return resultRDD.getRawSpatialRDD().collect();
+ }
+
+}
diff --git a/examples/java-spark-sql/src/main/java/spark/SedonaGeoParquetMain.java b/examples/java-spark-sql/src/main/java/spark/SedonaGeoParquetMain.java
new file mode 100644
index 00000000000..4a114372830
--- /dev/null
+++ b/examples/java-spark-sql/src/main/java/spark/SedonaGeoParquetMain.java
@@ -0,0 +1,61 @@
+/*
+ * 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 spark;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Properties;
+
+public class SedonaGeoParquetMain {
+
+ protected static Properties properties;
+ protected static String parquetPath;
+ protected static SedonaSparkSession session;
+
+ public static void main(String args[]) {
+
+ session = new SedonaSparkSession();
+ //Get parquetPath and any other application.properties
+ try {
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ Properties properties = new Properties();
+ InputStream is = loader.getResourceAsStream("application.properties");
+ properties.load(is);
+ parquetPath = properties.getProperty("parquet.path");
+ } catch (IOException e) {
+ e.printStackTrace();
+ parquetPath = "";
+ }
+ GeoParquetAccessor accessor = new GeoParquetAccessor(session.session, parquetPath);
+ //Test parquet happens to be in New Zealand Transverse Mercator (EPSG:2193) (meters)
+ List geoms = accessor.selectFeaturesByPolygon(1155850, 4819840, 1252000, 4748100, "geometry");
+ System.out.println("Coordinates of convex hull of points in boundary:");
+ for (Geometry geom : geoms) {
+ Coordinate[] convexHullCoordinates = geom.convexHull().getCoordinates();
+ for (Coordinate coord : convexHullCoordinates) {
+ System.out.println(String.format("\t%s", coord.toString()));
+ }
+ }
+ }
+}
diff --git a/examples/java-spark-sql/src/main/java/spark/SedonaSparkSession.java b/examples/java-spark-sql/src/main/java/spark/SedonaSparkSession.java
new file mode 100644
index 00000000000..6be6c995855
--- /dev/null
+++ b/examples/java-spark-sql/src/main/java/spark/SedonaSparkSession.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package spark;
+
+import org.apache.sedona.spark.SedonaContext;
+import org.apache.spark.sql.SparkSession;
+
+
+public class SedonaSparkSession {
+
+ public SparkSession session;
+
+ public SedonaSparkSession() {
+
+ //Set configuration for localhost spark cluster. Intended to be run from IDE or similar.
+ //Use SedonaContext builder to create SparkSession with Sedona extensions
+ SparkSession config = SedonaContext.builder()
+ .appName(this.getClass().getSimpleName())
+ .master("local[*]")
+ .config("spark.ui.enabled", "false")
+ .config("spark.driver.extraJavaOptions",
+ "--add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED")
+ .getOrCreate();
+
+ //Create Sedona-enabled SparkSession
+ this.session = SedonaContext.create(config);
+ }
+
+ public SparkSession getSession() {
+ // Access SparkSession object
+ return this.session;
+ }
+
+}
diff --git a/examples/java-spark-sql/src/main/resources/application.properties b/examples/java-spark-sql/src/main/resources/application.properties
new file mode 100644
index 00000000000..2c96c734a6e
--- /dev/null
+++ b/examples/java-spark-sql/src/main/resources/application.properties
@@ -0,0 +1,18 @@
+# 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.
+
+parquet.path=src/test/resources/TestPoints.parquet
diff --git a/examples/java-spark-sql/src/test/java/spark/SedonaParquetTest.java b/examples/java-spark-sql/src/test/java/spark/SedonaParquetTest.java
new file mode 100644
index 00000000000..036cdda9567
--- /dev/null
+++ b/examples/java-spark-sql/src/test/java/spark/SedonaParquetTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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 spark;
+
+import org.apache.spark.sql.Dataset;
+import org.apache.spark.sql.Row;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+
+public class SedonaParquetTest {
+
+
+ protected static Properties properties;
+ protected static String parquetPath;
+ protected static SedonaSparkSession session;
+
+ public SedonaParquetTest() {
+ }
+
+ @BeforeAll
+ public static void setUpClass() throws IOException {
+
+ session = new SedonaSparkSession();
+ //Get parquetPath and any other application.properties
+ try {
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ Properties properties = new Properties();
+ InputStream is = loader.getResourceAsStream("application.properties");
+ properties.load(is);
+ parquetPath = properties.getProperty("parquet.path");
+ } catch (IOException e) {
+ e.printStackTrace();
+ parquetPath = "";
+ }
+
+ }
+
+ @AfterAll
+ public static void tearDownClass() {
+ }
+
+ @BeforeEach
+ public void setUp() {
+ }
+
+ @AfterEach
+ public void tearDown() {
+ }
+
+ @Test
+ public void connects() {
+ assertNotNull(session, "SparkSedonaSession not initialized correctly.");
+ assertNotNull(session.session, "Spark session not initialized inside SparkSedonaSession.");
+ }
+
+ @Test
+ public void parquetAccessible() {
+ File file = new File(parquetPath);
+ assertTrue(file.exists(), "Parquet file does not exist.");
+ assertTrue(file.canRead(), "Can't read geoparquet file on record.");
+ }
+
+ @Test
+ public void canLoadRDD() {
+ assertNotNull(session, "Session is null.");
+ Dataset insarDF = session.session.read()
+ .format("geoparquet")
+ .load(parquetPath);
+ assertNotNull(insarDF, "Dataset was not created.");
+ assertTrue(insarDF.count() > 0, "Dataset is empty.");
+ }
+
+}
diff --git a/examples/java-spark-sql/src/test/resources/TestPoints.parquet b/examples/java-spark-sql/src/test/resources/TestPoints.parquet
new file mode 100644
index 00000000000..8038b545c74
Binary files /dev/null and b/examples/java-spark-sql/src/test/resources/TestPoints.parquet differ
diff --git a/examples/java-spark-sql/src/test/resources/application.properties b/examples/java-spark-sql/src/test/resources/application.properties
new file mode 100644
index 00000000000..2c96c734a6e
--- /dev/null
+++ b/examples/java-spark-sql/src/test/resources/application.properties
@@ -0,0 +1,18 @@
+# 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.
+
+parquet.path=src/test/resources/TestPoints.parquet