You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@baremaps.apache.org by bc...@apache.org on 2025/04/14 15:49:50 UTC
(incubator-baremaps) 04/05: Add schema for shapefiles
This is an automated email from the ASF dual-hosted git repository.
bchapuis pushed a commit to branch calcite-schema-ddl
in repository https://gitbox.apache.org/repos/asf/incubator-baremaps.git
commit dbbeea8d75449272e1bb008bfe9bc096c7930d4d
Author: Bertil Chapuis <bc...@gmail.com>
AuthorDate: Mon Apr 14 17:09:04 2025 +0200
Add schema for shapefiles
---
.../calcite/shapefile/ShapefileSchema.java | 155 +++++++++++++++++++++
.../calcite/shapefile/ShapefileSchemaTest.java | 154 ++++++++++++++++++++
2 files changed, 309 insertions(+)
diff --git a/baremaps-calcite/src/main/java/org/apache/baremaps/calcite/shapefile/ShapefileSchema.java b/baremaps-calcite/src/main/java/org/apache/baremaps/calcite/shapefile/ShapefileSchema.java
new file mode 100644
index 000000000..e66887b74
--- /dev/null
+++ b/baremaps-calcite/src/main/java/org/apache/baremaps/calcite/shapefile/ShapefileSchema.java
@@ -0,0 +1,155 @@
+/*
+ * 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
+ * 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.baremaps.calcite.shapefile;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.schema.Table;
+import org.apache.calcite.schema.impl.AbstractSchema;
+
+/**
+ * A Calcite schema implementation for shapefile data. This schema provides access to shapefiles
+ * through the Apache Calcite framework for SQL querying.
+ */
+public class ShapefileSchema extends AbstractSchema {
+
+ private final File directory;
+ private final Map<String, Table> tableMap;
+ private final RelDataTypeFactory typeFactory;
+
+ /**
+ * Constructs a ShapefileSchema with the specified directory.
+ *
+ * @param directory the directory containing shapefiles
+ * @param typeFactory the type factory to use for creating tables
+ * @throws IOException if an I/O error occurs
+ */
+ public ShapefileSchema(File directory, RelDataTypeFactory typeFactory) throws IOException {
+ this.directory = Objects.requireNonNull(directory, "Directory cannot be null");
+ this.typeFactory = Objects.requireNonNull(typeFactory, "Type factory cannot be null");
+ this.tableMap = new HashMap<>();
+
+ // Process files in the directory
+ File[] files = directory.listFiles((dir, name) ->
+ name.toLowerCase().endsWith(".shp"));
+
+ if (files != null) {
+ for (File file : files) {
+ // Extract the base name without extension (e.g., "countries" from "countries.shp")
+ String fileName = file.getName();
+ String tableName = fileName;
+
+ // Remove all extensions (e.g., "countries.shp" -> "countries")
+ while (tableName.contains(".")) {
+ int lastDotIndex = tableName.lastIndexOf('.');
+ if (lastDotIndex > 0) {
+ tableName = tableName.substring(0, lastDotIndex);
+ } else {
+ break;
+ }
+ }
+
+ // Create the table with the file reference
+ tableMap.put(tableName, createTable(file));
+ }
+ }
+ }
+
+ /**
+ * Constructs a ShapefileSchema with a single file.
+ *
+ * @param file the shapefile
+ * @param typeFactory the type factory to use for creating tables
+ * @throws IOException if an I/O error occurs
+ */
+ public ShapefileSchema(File file, RelDataTypeFactory typeFactory, boolean isDirectory) throws IOException {
+ if (isDirectory) {
+ // If isDirectory is true, treat the file as a directory
+ this.directory = Objects.requireNonNull(file, "Directory cannot be null");
+ this.typeFactory = Objects.requireNonNull(typeFactory, "Type factory cannot be null");
+ this.tableMap = new HashMap<>();
+
+ // Process files in the directory
+ File[] files = file.listFiles((dir, name) ->
+ name.toLowerCase().endsWith(".shp"));
+
+ if (files != null) {
+ for (File shapeFile : files) {
+ // Extract the base name without extension (e.g., "countries" from "countries.shp")
+ String fileName = shapeFile.getName();
+ String tableName = fileName;
+
+ // Remove all extensions (e.g., "countries.shp" -> "countries")
+ while (tableName.contains(".")) {
+ int lastDotIndex = tableName.lastIndexOf('.');
+ if (lastDotIndex > 0) {
+ tableName = tableName.substring(0, lastDotIndex);
+ } else {
+ break;
+ }
+ }
+
+ // Create the table with the file reference
+ tableMap.put(tableName, createTable(shapeFile));
+ }
+ }
+ } else {
+ // If isDirectory is false, treat the file as a single file
+ this.directory = Objects.requireNonNull(file, "File cannot be null");
+ this.typeFactory = Objects.requireNonNull(typeFactory, "Type factory cannot be null");
+ this.tableMap = new HashMap<>();
+
+ // Extract the base name without extension (e.g., "countries" from "countries.shp")
+ String fileName = file.getName();
+ String tableName = fileName;
+
+ // Remove all extensions (e.g., "countries.shp" -> "countries")
+ while (tableName.contains(".")) {
+ int lastDotIndex = tableName.lastIndexOf('.');
+ if (lastDotIndex > 0) {
+ tableName = tableName.substring(0, lastDotIndex);
+ } else {
+ break;
+ }
+ }
+
+ // Create the table with the file reference
+ tableMap.put(tableName, createTable(file));
+ }
+ }
+
+ /**
+ * Creates a table for the given file.
+ *
+ * @param file the shapefile
+ * @return the created table
+ * @throws IOException if an I/O error occurs
+ */
+ private Table createTable(File file) throws IOException {
+ return new ShapefileTable(file);
+ }
+
+ @Override
+ protected Map<String, Table> getTableMap() {
+ return tableMap;
+ }
+}
\ No newline at end of file
diff --git a/baremaps-calcite/src/test/java/org/apache/baremaps/calcite/shapefile/ShapefileSchemaTest.java b/baremaps-calcite/src/test/java/org/apache/baremaps/calcite/shapefile/ShapefileSchemaTest.java
new file mode 100644
index 000000000..fb534dd0e
--- /dev/null
+++ b/baremaps-calcite/src/test/java/org/apache/baremaps/calcite/shapefile/ShapefileSchemaTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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
+ * 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.baremaps.calcite.shapefile;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import org.apache.baremaps.testing.TestFiles;
+import org.apache.calcite.config.CalciteConnectionConfig;
+import org.apache.calcite.config.CalciteConnectionConfigImpl;
+import org.apache.calcite.config.CalciteConnectionProperty;
+import org.apache.calcite.jdbc.CalciteConnection;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.schema.SchemaPlus;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * Test class for the ShapefileSchema implementation.
+ */
+public class ShapefileSchemaTest {
+
+ @TempDir
+ Path tempDir;
+
+ @BeforeEach
+ public void setup() throws SQLException, IOException {
+ // Create a temporary directory for test files
+ Path testDir = tempDir.resolve("shapefile-test");
+ Files.createDirectories(testDir);
+
+ // Copy the sample shapefile to the test directory
+ Path sourceFile = TestFiles.POINT_SHP;
+ Path targetFile = testDir.resolve("point.shp");
+ Files.copy(sourceFile, targetFile, StandardCopyOption.REPLACE_EXISTING);
+
+ // Copy the associated .dbf file
+ Path sourceDbf = sourceFile.resolveSibling("point.dbf");
+ Path targetDbf = targetFile.resolveSibling("point.dbf");
+ Files.copy(sourceDbf, targetDbf, StandardCopyOption.REPLACE_EXISTING);
+
+ // Copy the associated .shx file
+ Path sourceShx = sourceFile.resolveSibling("point.shx");
+ Path targetShx = targetFile.resolveSibling("point.shx");
+ Files.copy(sourceShx, targetShx, StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ @Test
+ public void testSchemaCreation() throws SQLException, IOException {
+ // Create a connection to Calcite
+ Connection connection = DriverManager.getConnection("jdbc:calcite:");
+ CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
+ SchemaPlus rootSchema = calciteConnection.getRootSchema();
+
+ // Create and register the shapefile schema
+ ShapefileSchema schema = new ShapefileSchema(tempDir.resolve("shapefile-test").toFile(), calciteConnection.getTypeFactory());
+ rootSchema.add("shapefile", schema);
+
+ // Verify that the schema contains the expected table
+ assertTrue(rootSchema.getSubSchemaNames().contains("shapefile"));
+ assertTrue(rootSchema.getSubSchema("shapefile").getTableNames().contains("point"));
+
+ connection.close();
+ }
+
+ @Test
+ public void testSqlQuery() throws SQLException, IOException {
+ // Create a connection to Calcite
+ Connection connection = DriverManager.getConnection("jdbc:calcite:");
+ CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
+ SchemaPlus rootSchema = calciteConnection.getRootSchema();
+
+ // Create and register the shapefile schema
+ ShapefileSchema schema = new ShapefileSchema(tempDir.resolve("shapefile-test").toFile(), calciteConnection.getTypeFactory());
+ rootSchema.add("shapefile", schema);
+
+ // Execute a simple SQL query - use lowercase for schema and table names
+ try (Statement statement = connection.createStatement()) {
+ ResultSet resultSet = statement.executeQuery(
+ "SELECT * FROM \"shapefile\".\"point\"");
+
+ // Verify that we get results
+ assertTrue(resultSet.next());
+
+ // Verify that the result set has the expected columns
+ // Note: The actual column names will depend on the sample shapefile
+ assertNotNull(resultSet.getMetaData());
+ assertTrue(resultSet.getMetaData().getColumnCount() > 0);
+ }
+
+ connection.close();
+ }
+
+ @Test
+ public void testSingleFileSchema() throws SQLException, IOException {
+ // Create a connection to Calcite
+ Connection connection = DriverManager.getConnection("jdbc:calcite:");
+ CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
+ SchemaPlus rootSchema = calciteConnection.getRootSchema();
+
+ // Get the sample shapefile
+ File sampleFile = tempDir.resolve("shapefile-test").resolve("point.shp").toFile();
+
+ // Create and register the shapefile schema with a single file
+ ShapefileSchema schema = new ShapefileSchema(sampleFile, calciteConnection.getTypeFactory(), false);
+ rootSchema.add("single", schema);
+
+ // Verify that the schema contains the expected table
+ assertTrue(rootSchema.getSubSchemaNames().contains("single"));
+ assertTrue(rootSchema.getSubSchema("single").getTableNames().contains("point"));
+
+ // Execute a simple SQL query - use lowercase for schema and table names
+ try (Statement statement = connection.createStatement()) {
+ ResultSet resultSet = statement.executeQuery(
+ "SELECT * FROM \"single\".\"point\"");
+
+ // Verify that we get results
+ assertTrue(resultSet.next());
+
+ // Verify that the result set has the expected columns
+ assertNotNull(resultSet.getMetaData());
+ assertTrue(resultSet.getMetaData().getColumnCount() > 0);
+ }
+
+ connection.close();
+ }
+}
\ No newline at end of file