This is an automated email from the ASF dual-hosted git repository.
bchapuis pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-baremaps.git
The following commit(s) were added to refs/heads/main by this push:
new cd2018dc Add support for nested types, geoparquet groups, and postgres
jsonb in data table (#860)
cd2018dc is described below
commit cd2018dcbc62a45f0c5fb830f082aa21feb41cc5
Author: Bertil Chapuis <[email protected]>
AuthorDate: Mon Jun 3 21:06:45 2024 +0200
Add support for nested types, geoparquet groups, and postgres jsonb in data
table (#860)
* Add support for nested types in the DataTable
* Add a JsonbHandler that serializes Objects
* Add an EnvelopeField to the GeoParquet parser
* Save the EnvelopeField as geometry in Postgis
* Add a writeEnvelope method to the CopyWriter
* BBox use float values in GeoParquet
* Create Envelope from Double and Float values
* Use the default CRS when the crs field is null in Geoparquet (#861)
---------
Co-authored-by: Antoine Drabble <[email protected]>
---
.../apache/baremaps/database/copy/CopyWriter.java | 14 ++++
...ValueHandler.java => EnvelopeValueHandler.java} | 20 +++--
.../database/copy/GeometryValueHandler.java | 8 +-
.../baremaps/database/copy/JsonbValueHandler.java | 80 ++++++++++++++++++++
.../database/metadata/DatabaseMetadata.java | 36 ++++++---
.../storage/flatgeobuf/FlatGeoBufDataTable.java | 7 +-
.../flatgeobuf/FlatGeoBufTypeConversion.java | 6 +-
.../storage/geopackage/GeoPackageDataTable.java | 5 +-
.../geoparquet/GeoParquetTypeConversion.java | 75 ++++++++++++++-----
.../storage/postgres/PostgresDataStore.java | 22 +++++-
.../storage/postgres/PostgresTypeConversion.java | 5 +-
.../shapefile/internal/ShapefileByteReader.java | 7 +-
.../org/apache/baremaps/calcite/CalciteTest.java | 11 +--
.../database/postgres/NodeRepositoryTest.java | 4 +-
.../org/apache/baremaps/storage/MockDataTable.java | 11 +--
.../geoparquet/GeoParquetToPostgresTest.java | 7 +-
.../baremaps/data/calcite/SqlTypeConversion.java | 14 ----
.../apache/baremaps/data/storage/DataColumn.java | 22 ++++--
.../{DataColumnImpl.java => DataColumnFixed.java} | 3 +-
.../{DataColumnImpl.java => DataColumnNested.java} | 12 ++-
.../baremaps/data/type/DataTypeProvider.java | 35 ++++-----
.../baremaps/geoparquet/data/GeoParquetGroup.java | 26 +++++++
.../geoparquet/data/GeoParquetGroupFactory.java | 51 +++++++++----
.../geoparquet/data/GeoParquetGroupImpl.java | 85 +++++++++++++++++++---
.../geoparquet/data/GeoParquetMetadata.java | 21 +++---
25 files changed, 446 insertions(+), 141 deletions(-)
diff --git
a/baremaps-core/src/main/java/org/apache/baremaps/database/copy/CopyWriter.java
b/baremaps-core/src/main/java/org/apache/baremaps/database/copy/CopyWriter.java
index 0e5b5de0..c71b752f 100644
---
a/baremaps-core/src/main/java/org/apache/baremaps/database/copy/CopyWriter.java
+++
b/baremaps-core/src/main/java/org/apache/baremaps/database/copy/CopyWriter.java
@@ -30,6 +30,7 @@ import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.postgresql.copy.PGCopyOutputStream;
import org.postgresql.core.Oid;
@@ -106,6 +107,9 @@ public class CopyWriter implements AutoCloseable {
public static final GeometryValueHandler GEOMETRY_HANDLER =
new GeometryValueHandler();
+ public static final EnvelopeValueHandler ENVELOPE_HANDLER =
+ new EnvelopeValueHandler();
+
private final DataOutputStream data;
/**
@@ -397,6 +401,16 @@ public class CopyWriter implements AutoCloseable {
GEOMETRY_HANDLER.handle(data, value);
}
+ /**
+ * Writes an envelope value.
+ *
+ * @param value
+ * @throws IOException
+ */
+ public void writeEnvelope(Envelope value) throws IOException {
+ ENVELOPE_HANDLER.handle(data, value);
+ }
+
/** Close the writer. */
@Override
public void close() throws IOException {
diff --git
a/baremaps-core/src/main/java/org/apache/baremaps/database/copy/GeometryValueHandler.java
b/baremaps-core/src/main/java/org/apache/baremaps/database/copy/EnvelopeValueHandler.java
similarity index 65%
copy from
baremaps-core/src/main/java/org/apache/baremaps/database/copy/GeometryValueHandler.java
copy to
baremaps-core/src/main/java/org/apache/baremaps/database/copy/EnvelopeValueHandler.java
index 87d09264..804bae1f 100644
---
a/baremaps-core/src/main/java/org/apache/baremaps/database/copy/GeometryValueHandler.java
+++
b/baremaps-core/src/main/java/org/apache/baremaps/database/copy/EnvelopeValueHandler.java
@@ -21,21 +21,29 @@ import static org.locationtech.jts.io.WKBConstants.wkbNDR;
import de.bytefish.pgbulkinsert.pgsql.handlers.BaseValueHandler;
import java.io.DataOutputStream;
-import java.io.IOException;
+import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.io.WKBWriter;
-public class GeometryValueHandler extends BaseValueHandler<Geometry> {
+public class EnvelopeValueHandler extends BaseValueHandler<Envelope> {
+
+ private static final GeometryFactory geometryFactory = new GeometryFactory();
+
+ private static byte[] asWKB(Envelope value) {
+ Geometry geometry = geometryFactory.toGeometry(value);
+ return new WKBWriter(2, wkbNDR, true).write(geometry);
+ }
@Override
- protected void internalHandle(DataOutputStream buffer, Geometry value)
throws IOException {
- byte[] wkb = new WKBWriter(2, wkbNDR, true).write(value);
+ protected void internalHandle(DataOutputStream buffer, Envelope value)
throws Exception {
+ byte[] wkb = asWKB(value);
buffer.writeInt(wkb.length);
buffer.write(wkb, 0, wkb.length);
}
@Override
- public int getLength(Geometry geometry) {
- throw new UnsupportedOperationException();
+ public int getLength(Envelope value) {
+ return asWKB(value).length + 4;
}
}
diff --git
a/baremaps-core/src/main/java/org/apache/baremaps/database/copy/GeometryValueHandler.java
b/baremaps-core/src/main/java/org/apache/baremaps/database/copy/GeometryValueHandler.java
index 87d09264..4d0a6614 100644
---
a/baremaps-core/src/main/java/org/apache/baremaps/database/copy/GeometryValueHandler.java
+++
b/baremaps-core/src/main/java/org/apache/baremaps/database/copy/GeometryValueHandler.java
@@ -27,15 +27,19 @@ import org.locationtech.jts.io.WKBWriter;
public class GeometryValueHandler extends BaseValueHandler<Geometry> {
+ private static byte[] asWKB(Geometry geometry) {
+ return new WKBWriter(2, wkbNDR, true).write(geometry);
+ }
+
@Override
protected void internalHandle(DataOutputStream buffer, Geometry value)
throws IOException {
- byte[] wkb = new WKBWriter(2, wkbNDR, true).write(value);
+ byte[] wkb = asWKB(value);
buffer.writeInt(wkb.length);
buffer.write(wkb, 0, wkb.length);
}
@Override
public int getLength(Geometry geometry) {
- throw new UnsupportedOperationException();
+ return asWKB(geometry).length + 4;
}
}
diff --git
a/baremaps-core/src/main/java/org/apache/baremaps/database/copy/JsonbValueHandler.java
b/baremaps-core/src/main/java/org/apache/baremaps/database/copy/JsonbValueHandler.java
new file mode 100644
index 00000000..7a101e95
--- /dev/null
+++
b/baremaps-core/src/main/java/org/apache/baremaps/database/copy/JsonbValueHandler.java
@@ -0,0 +1,80 @@
+/*
+ * 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.baremaps.database.copy;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import de.bytefish.pgbulkinsert.pgsql.handlers.BaseValueHandler;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public class JsonbValueHandler extends BaseValueHandler<Object> {
+
+ private static final ObjectMapper objectMapper;
+
+ static {
+ objectMapper = new ObjectMapper();
+ SimpleModule module = new SimpleModule();
+ module.addSerializer(String.class, new NoQuotesStringSerializer());
+ objectMapper.registerModule(module);
+ }
+
+ static class NoQuotesStringSerializer extends JsonSerializer<String> {
+ @Override
+ public void serialize(String value, JsonGenerator gen, SerializerProvider
serializers)
+ throws IOException {
+ gen.writeRawValue(value);
+ }
+ }
+
+ private final int jsonbProtocolVersion;
+
+ public JsonbValueHandler() {
+ this(1);
+ }
+
+ public JsonbValueHandler(int jsonbProtocolVersion) {
+ this.jsonbProtocolVersion = jsonbProtocolVersion;
+ }
+
+ private static byte[] asJson(Object object) {
+ try {
+ String value = objectMapper.writeValueAsString(object);
+ return value.getBytes("UTF-8");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected void internalHandle(DataOutputStream buffer, Object value) throws
Exception {
+ byte[] utf8Bytes = asJson(value);
+ buffer.writeInt(utf8Bytes.length + 1);
+ buffer.writeByte(jsonbProtocolVersion);
+ buffer.write(utf8Bytes);
+ }
+
+ @Override
+ public int getLength(Object value) {
+ byte[] utf8Bytes = asJson(value);
+ return utf8Bytes.length;
+ }
+}
diff --git
a/baremaps-core/src/main/java/org/apache/baremaps/database/metadata/DatabaseMetadata.java
b/baremaps-core/src/main/java/org/apache/baremaps/database/metadata/DatabaseMetadata.java
index ff726ed3..c3b67575 100644
---
a/baremaps-core/src/main/java/org/apache/baremaps/database/metadata/DatabaseMetadata.java
+++
b/baremaps-core/src/main/java/org/apache/baremaps/database/metadata/DatabaseMetadata.java
@@ -82,18 +82,30 @@ public class DatabaseMetadata {
var resultSet = connection.getMetaData().getColumns(catalog,
schemaPattern,
tableNamePattern, columnNamePattern)) {
while (resultSet.next()) {
- tableColumns.add(new ColumnResult(resultSet.getString("TABLE_CAT"),
- resultSet.getString("TABLE_SCHEM"),
resultSet.getString("TABLE_NAME"),
- resultSet.getString("COLUMN_NAME"), resultSet.getInt("DATA_TYPE"),
- resultSet.getString("TYPE_NAME"), resultSet.getInt("COLUMN_SIZE"),
- resultSet.getInt("DECIMAL_DIGITS"),
resultSet.getInt("NUM_PREC_RADIX"),
- resultSet.getInt("NULLABLE"), resultSet.getString("REMARKS"),
- resultSet.getString("COLUMN_DEF"),
resultSet.getInt("SQL_DATA_TYPE"),
- resultSet.getInt("SQL_DATETIME_SUB"),
resultSet.getInt("CHAR_OCTET_LENGTH"),
- resultSet.getInt("ORDINAL_POSITION"),
resultSet.getString("IS_NULLABLE"),
- resultSet.getString("SCOPE_CATALOG"),
resultSet.getString("SCOPE_SCHEMA"),
- resultSet.getString("SCOPE_TABLE"),
resultSet.getShort("SOURCE_DATA_TYPE"),
- resultSet.getString("IS_AUTOINCREMENT"),
resultSet.getString("IS_GENERATEDCOLUMN")));
+ tableColumns.add(new ColumnResult(
+ resultSet.getString("TABLE_CAT"),
+ resultSet.getString("TABLE_SCHEM"),
+ resultSet.getString("TABLE_NAME"),
+ resultSet.getString("COLUMN_NAME"),
+ resultSet.getInt("DATA_TYPE"),
+ resultSet.getString("TYPE_NAME"),
+ resultSet.getInt("COLUMN_SIZE"),
+ resultSet.getInt("DECIMAL_DIGITS"),
+ resultSet.getInt("NUM_PREC_RADIX"),
+ resultSet.getInt("NULLABLE"),
+ resultSet.getString("REMARKS"),
+ resultSet.getString("COLUMN_DEF"),
+ resultSet.getInt("SQL_DATA_TYPE"),
+ resultSet.getInt("SQL_DATETIME_SUB"),
+ resultSet.getInt("CHAR_OCTET_LENGTH"),
+ resultSet.getInt("ORDINAL_POSITION"),
+ resultSet.getString("IS_NULLABLE"),
+ resultSet.getString("SCOPE_CATALOG"),
+ resultSet.getString("SCOPE_SCHEMA"),
+ resultSet.getString("SCOPE_TABLE"),
+ resultSet.getShort("SOURCE_DATA_TYPE"),
+ resultSet.getString("IS_AUTOINCREMENT"),
+ resultSet.getString("IS_GENERATEDCOLUMN")));
}
} catch (SQLException e) {
throw new RuntimeException(e);
diff --git
a/baremaps-core/src/main/java/org/apache/baremaps/storage/flatgeobuf/FlatGeoBufDataTable.java
b/baremaps-core/src/main/java/org/apache/baremaps/storage/flatgeobuf/FlatGeoBufDataTable.java
index a7c9c93c..f3598d28 100644
---
a/baremaps-core/src/main/java/org/apache/baremaps/storage/flatgeobuf/FlatGeoBufDataTable.java
+++
b/baremaps-core/src/main/java/org/apache/baremaps/storage/flatgeobuf/FlatGeoBufDataTable.java
@@ -57,7 +57,12 @@ public class FlatGeoBufDataTable implements DataTable {
this.schema = readSchema(file);
}
-
+ /**
+ * Reads the schema from a flatgeobuf file.
+ *
+ * @param file the path to the flatgeobuf file
+ * @return the schema of the table
+ */
private static DataSchema readSchema(Path file) {
try (var channel = FileChannel.open(file, StandardOpenOption.READ)) {
// try to read the schema from the file
diff --git
a/baremaps-core/src/main/java/org/apache/baremaps/storage/flatgeobuf/FlatGeoBufTypeConversion.java
b/baremaps-core/src/main/java/org/apache/baremaps/storage/flatgeobuf/FlatGeoBufTypeConversion.java
index 58c1074c..1a42148c 100644
---
a/baremaps-core/src/main/java/org/apache/baremaps/storage/flatgeobuf/FlatGeoBufTypeConversion.java
+++
b/baremaps-core/src/main/java/org/apache/baremaps/storage/flatgeobuf/FlatGeoBufTypeConversion.java
@@ -26,6 +26,7 @@ import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import org.apache.baremaps.data.storage.*;
+import org.apache.baremaps.data.storage.DataColumn.Cardinality;
import org.apache.baremaps.data.storage.DataColumn.Type;
import org.wololo.flatgeobuf.ColumnMeta;
import org.wololo.flatgeobuf.GeometryConversions;
@@ -53,7 +54,10 @@ public class FlatGeoBufTypeConversion {
public static DataSchema asSchema(HeaderMeta headerMeta) {
var name = headerMeta.name;
var columns = headerMeta.columns.stream()
- .map(column -> new DataColumnImpl(column.name,
Type.fromBinding(column.getBinding())))
+ .map(column -> new DataColumnFixed(
+ column.name,
+ column.nullable ? Cardinality.OPTIONAL : Cardinality.REQUIRED,
+ Type.fromBinding(column.getBinding())))
.map(DataColumn.class::cast)
.toList();
return new DataSchemaImpl(name, columns);
diff --git
a/baremaps-core/src/main/java/org/apache/baremaps/storage/geopackage/GeoPackageDataTable.java
b/baremaps-core/src/main/java/org/apache/baremaps/storage/geopackage/GeoPackageDataTable.java
index ed6bc89c..5518cb4b 100644
---
a/baremaps-core/src/main/java/org/apache/baremaps/storage/geopackage/GeoPackageDataTable.java
+++
b/baremaps-core/src/main/java/org/apache/baremaps/storage/geopackage/GeoPackageDataTable.java
@@ -24,6 +24,7 @@ import mil.nga.geopackage.features.user.FeatureDao;
import mil.nga.geopackage.features.user.FeatureResultSet;
import mil.nga.geopackage.geom.GeoPackageGeometryData;
import org.apache.baremaps.data.storage.*;
+import org.apache.baremaps.data.storage.DataColumn.Cardinality;
import org.apache.baremaps.data.storage.DataColumn.Type;
import org.locationtech.jts.geom.*;
@@ -50,7 +51,9 @@ public class GeoPackageDataTable implements DataTable {
for (FeatureColumn column : featureDao.getColumns()) {
var propertyName = column.getName();
var propertyType = classType(column);
- columns.add(new DataColumnImpl(propertyName, propertyType));
+ var propertyCardinality = column.isNotNull() ? Cardinality.REQUIRED :
Cardinality.OPTIONAL;
+ columns.add(new DataColumnFixed(
+ propertyName, propertyCardinality, propertyType));
}
schema = new DataSchemaImpl(name, columns);
geometryFactory = new GeometryFactory(new PrecisionModel(), (int)
featureDao.getSrs().getId());
diff --git
a/baremaps-core/src/main/java/org/apache/baremaps/storage/geoparquet/GeoParquetTypeConversion.java
b/baremaps-core/src/main/java/org/apache/baremaps/storage/geoparquet/GeoParquetTypeConversion.java
index 19c09e41..78be7062 100644
---
a/baremaps-core/src/main/java/org/apache/baremaps/storage/geoparquet/GeoParquetTypeConversion.java
+++
b/baremaps-core/src/main/java/org/apache/baremaps/storage/geoparquet/GeoParquetTypeConversion.java
@@ -18,14 +18,15 @@
package org.apache.baremaps.storage.geoparquet;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
-import org.apache.baremaps.data.storage.DataColumn;
+import java.util.Map;
+import org.apache.baremaps.data.storage.*;
+import org.apache.baremaps.data.storage.DataColumn.Cardinality;
import org.apache.baremaps.data.storage.DataColumn.Type;
-import org.apache.baremaps.data.storage.DataColumnImpl;
-import org.apache.baremaps.data.storage.DataSchema;
-import org.apache.baremaps.data.storage.DataSchemaImpl;
import org.apache.baremaps.geoparquet.data.GeoParquetGroup;
import org.apache.baremaps.geoparquet.data.GeoParquetGroup.Field;
+import org.apache.baremaps.geoparquet.data.GeoParquetGroup.GroupField;
import org.apache.baremaps.geoparquet.data.GeoParquetGroup.Schema;
public class GeoParquetTypeConversion {
@@ -33,23 +34,34 @@ public class GeoParquetTypeConversion {
private GeoParquetTypeConversion() {}
public static DataSchema asSchema(String table, Schema schema) {
- List<DataColumn> columns = schema.fields().stream()
- .map(field -> (DataColumn) new DataColumnImpl(field.name(),
asSchema(field.type())))
- .toList();
+ List<DataColumn> columns = asDataColumns(schema);
return new DataSchemaImpl(table, columns);
}
- public static Type asSchema(GeoParquetGroup.Type type) {
- return switch (type) {
- case BINARY -> Type.BYTE_ARRAY;
- case BOOLEAN -> Type.BOOLEAN;
- case INTEGER -> Type.INTEGER;
- case INT96, LONG -> Type.LONG;
- case FLOAT -> Type.FLOAT;
- case DOUBLE -> Type.DOUBLE;
- case STRING -> Type.STRING;
- case GEOMETRY -> Type.GEOMETRY;
- case GROUP -> null;
+ private static List<DataColumn> asDataColumns(Schema field) {
+ return field.fields().stream()
+ .map(GeoParquetTypeConversion::asDataColumn)
+ .toList();
+ }
+
+ private static DataColumn asDataColumn(Field field) {
+ Cardinality cardinality = switch (field.cardinality()) {
+ case REQUIRED -> Cardinality.REQUIRED;
+ case OPTIONAL -> Cardinality.OPTIONAL;
+ case REPEATED -> Cardinality.REPEATED;
+ };
+ return switch (field.type()) {
+ case BINARY -> new DataColumnFixed(field.name(), cardinality,
Type.BINARY);
+ case BOOLEAN -> new DataColumnFixed(field.name(), cardinality,
Type.BOOLEAN);
+ case INTEGER -> new DataColumnFixed(field.name(), cardinality,
Type.INTEGER);
+ case INT96, LONG -> new DataColumnFixed(field.name(), cardinality,
Type.LONG);
+ case FLOAT -> new DataColumnFixed(field.name(), cardinality, Type.FLOAT);
+ case DOUBLE -> new DataColumnFixed(field.name(), cardinality,
Type.DOUBLE);
+ case STRING -> new DataColumnFixed(field.name(), cardinality,
Type.STRING);
+ case GEOMETRY -> new DataColumnFixed(field.name(), cardinality,
Type.GEOMETRY);
+ case ENVELOPE -> new DataColumnFixed(field.name(), cardinality,
Type.ENVELOPE);
+ case GROUP -> new DataColumnNested(field.name(), cardinality,
+ asDataColumns(((GroupField) field).schema()));
};
}
@@ -59,7 +71,6 @@ public class GeoParquetTypeConversion {
List<Field> fields = schema.fields();
for (int i = 0; i < fields.size(); i++) {
Field field = fields.get(i);
- field.type();
switch (field.type()) {
case BINARY -> values.add(group.getBinaryValue(i).getBytes());
case BOOLEAN -> values.add(group.getBooleanValue(i));
@@ -69,9 +80,33 @@ public class GeoParquetTypeConversion {
case DOUBLE -> values.add(group.getDoubleValue(i));
case STRING -> values.add(group.getStringValue(i));
case GEOMETRY -> values.add(group.getGeometryValue(i));
- case GROUP -> values.add(null); // TODO:
values.add(asDataRow(group.getGroupValue(i)));
+ case ENVELOPE -> values.add(group.getEnvelopeValue(i));
+ case GROUP -> values.add(asNested(group.getGroupValue(i)));
}
}
return values;
}
+
+ public static Map<String, Object> asNested(GeoParquetGroup group) {
+ Map<String, Object> nested = new HashMap<>();
+ Schema schema = group.getSchema();
+ List<Field> fields = schema.fields();
+ for (int i = 0; i < fields.size(); i++) {
+ Field field = fields.get(i);
+ nested.put(field.name(), switch (field.type()) {
+ case BINARY -> group.getBinaryValue(i).getBytes();
+ case BOOLEAN -> group.getBooleanValue(i);
+ case INTEGER -> group.getIntegerValue(i);
+ case INT96, LONG -> group.getLongValue(i);
+ case FLOAT -> group.getFloatValue(i);
+ case DOUBLE -> group.getDoubleValue(i);
+ case STRING -> group.getStringValue(i);
+ case GEOMETRY -> group.getGeometryValue(i);
+ case ENVELOPE -> group.getEnvelopeValue(i);
+ case GROUP -> asNested(group.getGroupValue(i));
+ });
+ }
+ return nested;
+ }
+
}
diff --git
a/baremaps-core/src/main/java/org/apache/baremaps/storage/postgres/PostgresDataStore.java
b/baremaps-core/src/main/java/org/apache/baremaps/storage/postgres/PostgresDataStore.java
index eabd5c59..de05ebe6 100644
---
a/baremaps-core/src/main/java/org/apache/baremaps/storage/postgres/PostgresDataStore.java
+++
b/baremaps-core/src/main/java/org/apache/baremaps/storage/postgres/PostgresDataStore.java
@@ -28,7 +28,9 @@ import javax.sql.DataSource;
import org.apache.baremaps.data.storage.*;
import org.apache.baremaps.data.storage.DataColumn.Type;
import org.apache.baremaps.database.copy.CopyWriter;
+import org.apache.baremaps.database.copy.EnvelopeValueHandler;
import org.apache.baremaps.database.copy.GeometryValueHandler;
+import org.apache.baremaps.database.copy.JsonbValueHandler;
import org.apache.baremaps.database.metadata.DatabaseMetadata;
import org.apache.baremaps.database.metadata.TableMetadata;
import org.postgresql.PGConnection;
@@ -106,7 +108,7 @@ public class PostgresDataStore implements DataStore {
if (PostgresTypeConversion.typeToName.containsKey(column.type())) {
var columnName = column.name().replaceAll(REGEX, "_").toLowerCase();
mapping.put(columnName, column.name());
- properties.add(new DataColumnImpl(columnName, column.type()));
+ properties.add(new DataColumnFixed(columnName, column.cardinality(),
column.type()));
}
}
@@ -177,7 +179,10 @@ public class PostgresDataStore implements DataStore {
protected static DataSchema createSchema(TableMetadata tableMetadata) {
var name = tableMetadata.table().tableName();
var columns = tableMetadata.columns().stream()
- .map(column -> new DataColumnImpl(column.columnName(),
+ .map(column -> new DataColumnFixed(
+ column.columnName(),
+ column.isNullable().equals("NO") ? DataColumn.Cardinality.REQUIRED
+ : DataColumn.Cardinality.OPTIONAL,
PostgresTypeConversion.nameToType.get(column.typeName())))
.map(DataColumn.class::cast)
.toList();
@@ -206,13 +211,20 @@ public class PostgresDataStore implements DataStore {
builder.append(schema.name());
builder.append("\" (");
builder.append(schema.columns().stream()
- .map(column -> "\"" + column.name()
- + "\" " + PostgresTypeConversion.typeToName.get(column.type()))
+ .map(PostgresDataStore::getColumnType)
.collect(Collectors.joining(", ")));
builder.append(")");
return builder.toString();
}
+ private static String getColumnType(DataColumn column) {
+ String columnName = column.name();
+ String columnType = PostgresTypeConversion.typeToName.get(column.type());
+ String columnArray = column.cardinality() ==
DataColumn.Cardinality.REPEATED ? "[]" : "";
+ String columnNull = column.cardinality() ==
DataColumn.Cardinality.REQUIRED ? "NOT NULL" : "";
+ return String.format("\"%s\" %s%s %s", columnName, columnType,
columnArray, columnNull).strip();
+ }
+
/**
* Generate a copy query.
*
@@ -276,6 +288,8 @@ public class PostgresDataStore implements DataStore {
case LOCAL_TIME -> new LocalTimeValueHandler();
case LOCAL_DATE_TIME -> new LocalDateTimeValueHandler();
case GEOMETRY, POINT, MULTIPOINT, LINESTRING, MULTILINESTRING, POLYGON,
MULTIPOLYGON, GEOMETRYCOLLECTION -> new GeometryValueHandler();
+ case ENVELOPE -> new EnvelopeValueHandler();
+ case NESTED -> new JsonbValueHandler();
default -> throw new IllegalArgumentException("Unsupported type: " +
type);
};
}
diff --git
a/baremaps-core/src/main/java/org/apache/baremaps/storage/postgres/PostgresTypeConversion.java
b/baremaps-core/src/main/java/org/apache/baremaps/storage/postgres/PostgresTypeConversion.java
index 4188ade5..9c14fd22 100644
---
a/baremaps-core/src/main/java/org/apache/baremaps/storage/postgres/PostgresTypeConversion.java
+++
b/baremaps-core/src/main/java/org/apache/baremaps/storage/postgres/PostgresTypeConversion.java
@@ -40,12 +40,14 @@ public class PostgresTypeConversion {
typeToName.put(Type.POLYGON, "geometry");
typeToName.put(Type.MULTIPOLYGON, "geometry");
typeToName.put(Type.GEOMETRYCOLLECTION, "geometry");
+ typeToName.put(Type.ENVELOPE, "geometry");
typeToName.put(Type.INET_ADDRESS, "inet");
typeToName.put(Type.INET4_ADDRESS, "inet");
typeToName.put(Type.INET6_ADDRESS, "inet");
typeToName.put(Type.LOCAL_DATE, "date");
typeToName.put(Type.LOCAL_TIME, "time");
typeToName.put(Type.LOCAL_DATE_TIME, "timestamp");
+ typeToName.put(Type.NESTED, "jsonb");
}
protected static final Map<String, Type> nameToType = Map.ofEntries(
@@ -59,6 +61,7 @@ public class PostgresTypeConversion {
Map.entry("inet", Type.INET6_ADDRESS),
Map.entry("date", Type.LOCAL_DATE),
Map.entry("time", Type.LOCAL_TIME),
- Map.entry("timestamp", Type.LOCAL_DATE_TIME));
+ Map.entry("timestamp", Type.LOCAL_DATE_TIME),
+ Map.entry("jsonb", Type.NESTED));
}
diff --git
a/baremaps-core/src/main/java/org/apache/baremaps/storage/shapefile/internal/ShapefileByteReader.java
b/baremaps-core/src/main/java/org/apache/baremaps/storage/shapefile/internal/ShapefileByteReader.java
index d733067b..841c2d3e 100644
---
a/baremaps-core/src/main/java/org/apache/baremaps/storage/shapefile/internal/ShapefileByteReader.java
+++
b/baremaps-core/src/main/java/org/apache/baremaps/storage/shapefile/internal/ShapefileByteReader.java
@@ -25,6 +25,7 @@ import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.*;
import org.apache.baremaps.data.storage.*;
+import org.apache.baremaps.data.storage.DataColumn.Cardinality;
import org.apache.baremaps.data.storage.DataColumn.Type;
import org.locationtech.jts.algorithm.Orientation;
import org.locationtech.jts.geom.Coordinate;
@@ -90,7 +91,7 @@ public class ShapefileByteReader extends CommonByteReader {
}
/**
- * Returns the DBase 3 fields descriptors.
+ * Returns the DBase 3 columns descriptors.
*
* @return Fields descriptors.
*/
@@ -148,11 +149,11 @@ public class ShapefileByteReader extends CommonByteReader
{
case TimeStamp -> Type.STRING;
case DateTime -> Type.STRING;
};
- columns.add(new DataColumnImpl(columnName, columnType));
+ columns.add(new DataColumnFixed(columnName, Cardinality.OPTIONAL,
columnType));
}
// Add geometry column.
- columns.add(new DataColumnImpl(GEOMETRY_NAME, Type.GEOMETRY));
+ columns.add(new DataColumnFixed(GEOMETRY_NAME, Cardinality.OPTIONAL,
Type.GEOMETRY));
return new DataSchemaImpl(name, columns);
}
diff --git
a/baremaps-core/src/test/java/org/apache/baremaps/calcite/CalciteTest.java
b/baremaps-core/src/test/java/org/apache/baremaps/calcite/CalciteTest.java
index d59123ae..13cf44d0 100644
--- a/baremaps-core/src/test/java/org/apache/baremaps/calcite/CalciteTest.java
+++ b/baremaps-core/src/test/java/org/apache/baremaps/calcite/CalciteTest.java
@@ -26,6 +26,7 @@ import org.apache.baremaps.data.calcite.SqlDataTable;
import org.apache.baremaps.data.collection.AppendOnlyLog;
import org.apache.baremaps.data.collection.IndexedDataList;
import org.apache.baremaps.data.storage.*;
+import org.apache.baremaps.data.storage.DataColumn.Cardinality;
import org.apache.baremaps.data.storage.DataColumn.Type;
import org.apache.baremaps.data.type.RowDataType;
import org.apache.baremaps.maplibre.vectortile.VectorTileFunctions;
@@ -74,9 +75,9 @@ public class CalciteTest {
// Create the city table
DataSchema cityRowType = new DataSchemaImpl("city", List.of(
- new DataColumnImpl("id", Type.INTEGER),
- new DataColumnImpl("name", Type.STRING),
- new DataColumnImpl("geometry", Type.GEOMETRY)));
+ new DataColumnFixed("id", Cardinality.OPTIONAL, Type.INTEGER),
+ new DataColumnFixed("name", Cardinality.OPTIONAL, Type.STRING),
+ new DataColumnFixed("geometry", Cardinality.OPTIONAL,
Type.GEOMETRY)));
DataTable cityDataTable = new DataTableImpl(
cityRowType,
new IndexedDataList<>(new AppendOnlyLog<>(new
RowDataType(cityRowType))));
@@ -89,8 +90,8 @@ public class CalciteTest {
// Create the population table
DataSchema populationRowType = new DataSchemaImpl("population", List.of(
- new DataColumnImpl("city_id", Type.INTEGER),
- new DataColumnImpl("population", Type.INTEGER)));
+ new DataColumnFixed("city_id", Cardinality.OPTIONAL, Type.INTEGER),
+ new DataColumnFixed("population", Cardinality.OPTIONAL,
Type.INTEGER)));
DataTable populationDataTable = new DataTableImpl(
populationRowType,
new IndexedDataList<>(new AppendOnlyLog<>(new
RowDataType(populationRowType))));
diff --git
a/baremaps-core/src/test/java/org/apache/baremaps/database/postgres/NodeRepositoryTest.java
b/baremaps-core/src/test/java/org/apache/baremaps/database/postgres/NodeRepositoryTest.java
index e2709fa5..ef42773d 100644
---
a/baremaps-core/src/test/java/org/apache/baremaps/database/postgres/NodeRepositoryTest.java
+++
b/baremaps-core/src/test/java/org/apache/baremaps/database/postgres/NodeRepositoryTest.java
@@ -21,8 +21,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
-import java.io.IOException;
-import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@@ -37,7 +35,7 @@ class NodeRepositoryTest extends PostgresRepositoryTest {
NodeRepository nodeRepository;
@BeforeEach
- void beforeEach() throws SQLException, IOException {
+ void beforeEach() {
nodeRepository = new NodeRepository(dataSource());
}
diff --git
a/baremaps-core/src/test/java/org/apache/baremaps/storage/MockDataTable.java
b/baremaps-core/src/test/java/org/apache/baremaps/storage/MockDataTable.java
index f21bdd7f..86bcf176 100644
--- a/baremaps-core/src/test/java/org/apache/baremaps/storage/MockDataTable.java
+++ b/baremaps-core/src/test/java/org/apache/baremaps/storage/MockDataTable.java
@@ -22,6 +22,7 @@ import static
org.apache.baremaps.database.repository.Constants.GEOMETRY_FACTORY
import java.util.Iterator;
import java.util.List;
import org.apache.baremaps.data.storage.*;
+import org.apache.baremaps.data.storage.DataColumn.Cardinality;
import org.apache.baremaps.data.storage.DataColumn.Type;
import org.locationtech.jts.geom.Coordinate;
@@ -33,11 +34,11 @@ public class MockDataTable implements DataTable {
public MockDataTable() {
this.rowType = new DataSchemaImpl("mock", List.of(
- new DataColumnImpl("string", Type.STRING),
- new DataColumnImpl("integer", Type.INTEGER),
- new DataColumnImpl("double", Type.DOUBLE),
- new DataColumnImpl("float", Type.FLOAT),
- new DataColumnImpl("geometry", Type.GEOMETRY)));
+ new DataColumnFixed("string", Cardinality.OPTIONAL, Type.STRING),
+ new DataColumnFixed("integer", Cardinality.OPTIONAL, Type.INTEGER),
+ new DataColumnFixed("double", Cardinality.OPTIONAL, Type.DOUBLE),
+ new DataColumnFixed("float", Cardinality.OPTIONAL, Type.FLOAT),
+ new DataColumnFixed("geometry", Cardinality.OPTIONAL, Type.GEOMETRY)));
this.rows = List.of(
new DataRowImpl(rowType,
List.of("string", 1, 1.0, 1.0f, GEOMETRY_FACTORY.createPoint(new
Coordinate(1, 1)))),
diff --git
a/baremaps-core/src/test/java/org/apache/baremaps/storage/geoparquet/GeoParquetToPostgresTest.java
b/baremaps-core/src/test/java/org/apache/baremaps/storage/geoparquet/GeoParquetToPostgresTest.java
index 198a63d2..f96001da 100644
---
a/baremaps-core/src/test/java/org/apache/baremaps/storage/geoparquet/GeoParquetToPostgresTest.java
+++
b/baremaps-core/src/test/java/org/apache/baremaps/storage/geoparquet/GeoParquetToPostgresTest.java
@@ -42,8 +42,13 @@ class GeoParquetToPostgresTest extends PostgresContainerTest
{
// Check the table in Postgres
var postgresTable = postgresStore.get("geoparquet");
+
+ for (var row : postgresTable) {
+ System.out.println(row);
+ }
+
assertEquals("geoparquet", postgresTable.schema().name());
- assertEquals(3, postgresTable.schema().columns().size());
+ assertEquals(4, postgresTable.schema().columns().size());
assertEquals(5L, postgresTable.size());
assertEquals(5L, postgresTable.stream().count());
}
diff --git
a/baremaps-data/src/main/java/org/apache/baremaps/data/calcite/SqlTypeConversion.java
b/baremaps-data/src/main/java/org/apache/baremaps/data/calcite/SqlTypeConversion.java
index e0116e59..78a6ee25 100644
---
a/baremaps-data/src/main/java/org/apache/baremaps/data/calcite/SqlTypeConversion.java
+++
b/baremaps-data/src/main/java/org/apache/baremaps/data/calcite/SqlTypeConversion.java
@@ -31,32 +31,18 @@ public class SqlTypeConversion {
static {
types.put(Type.BYTE, new JavaTypeFactoryImpl()
.createSqlType(SqlTypeName.TINYINT));
- types.put(Type.BYTE_ARRAY, new JavaTypeFactoryImpl()
- .createArrayType(new
JavaTypeFactoryImpl().createSqlType(SqlTypeName.TINYINT), -1));
types.put(Type.BOOLEAN, new JavaTypeFactoryImpl()
.createSqlType(SqlTypeName.BOOLEAN));
- types.put(Type.BOOLEAN_ARRAY, new JavaTypeFactoryImpl()
- .createArrayType(new
JavaTypeFactoryImpl().createSqlType(SqlTypeName.BOOLEAN), -1));
types.put(Type.SHORT, new JavaTypeFactoryImpl()
.createSqlType(SqlTypeName.SMALLINT));
- types.put(Type.SHORT_ARRAY, new JavaTypeFactoryImpl()
- .createArrayType(new
JavaTypeFactoryImpl().createSqlType(SqlTypeName.SMALLINT), -1));
types.put(Type.INTEGER, new JavaTypeFactoryImpl()
.createSqlType(SqlTypeName.INTEGER));
- types.put(Type.INTEGER_ARRAY, new JavaTypeFactoryImpl()
- .createArrayType(new
JavaTypeFactoryImpl().createSqlType(SqlTypeName.INTEGER), -1));
types.put(Type.LONG, new JavaTypeFactoryImpl()
.createSqlType(SqlTypeName.BIGINT));
- types.put(Type.LONG_ARRAY, new JavaTypeFactoryImpl()
- .createArrayType(new
JavaTypeFactoryImpl().createSqlType(SqlTypeName.BIGINT), -1));
types.put(Type.FLOAT, new JavaTypeFactoryImpl()
.createSqlType(SqlTypeName.FLOAT));
- types.put(Type.FLOAT_ARRAY, new JavaTypeFactoryImpl()
- .createArrayType(new
JavaTypeFactoryImpl().createSqlType(SqlTypeName.FLOAT), -1));
types.put(Type.DOUBLE, new JavaTypeFactoryImpl()
.createSqlType(SqlTypeName.DOUBLE));
- types.put(Type.DOUBLE_ARRAY, new JavaTypeFactoryImpl()
- .createArrayType(new
JavaTypeFactoryImpl().createSqlType(SqlTypeName.DOUBLE), -1));
types.put(Type.STRING, new JavaTypeFactoryImpl()
.createSqlType(SqlTypeName.VARCHAR));
types.put(Type.GEOMETRY, new JavaTypeFactoryImpl()
diff --git
a/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataColumn.java
b/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataColumn.java
index 9c70bd34..1f86773c 100644
---
a/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataColumn.java
+++
b/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataColumn.java
@@ -23,6 +23,7 @@ import java.net.InetAddress;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
+import java.util.Map;
import org.locationtech.jts.geom.*;
/**
@@ -37,6 +38,15 @@ public interface DataColumn {
*/
String name();
+
+ Cardinality cardinality();
+
+ enum Cardinality {
+ REQUIRED,
+ OPTIONAL,
+ REPEATED
+ }
+
/**
* Returns the type of the column.
*
@@ -48,20 +58,14 @@ public interface DataColumn {
* An enumeration of the supported data column types.
*/
enum Type {
+ BINARY(byte[].class),
BYTE(Byte.class),
- BYTE_ARRAY(byte[].class),
BOOLEAN(Boolean.class),
- BOOLEAN_ARRAY(boolean[].class),
SHORT(Short.class),
- SHORT_ARRAY(short[].class),
INTEGER(Integer.class),
- INTEGER_ARRAY(int[].class),
LONG(Long.class),
- LONG_ARRAY(long[].class),
FLOAT(Float.class),
- FLOAT_ARRAY(float[].class),
DOUBLE(Double.class),
- DOUBLE_ARRAY(double[].class),
STRING(String.class),
COORDINATE(Coordinate.class),
GEOMETRY(Geometry.class),
@@ -72,12 +76,14 @@ public interface DataColumn {
MULTILINESTRING(MultiLineString.class),
MULTIPOLYGON(MultiPolygon.class),
GEOMETRYCOLLECTION(GeometryCollection.class),
+ ENVELOPE(Envelope.class),
INET_ADDRESS(InetAddress.class),
INET4_ADDRESS(Inet4Address.class),
INET6_ADDRESS(Inet6Address.class),
LOCAL_DATE(LocalDate.class),
LOCAL_TIME(LocalTime.class),
- LOCAL_DATE_TIME(LocalDateTime.class),;
+ LOCAL_DATE_TIME(LocalDateTime.class),
+ NESTED(Map.class);
private final Class<?> binding;
diff --git
a/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataColumnImpl.java
b/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataColumnFixed.java
similarity index 89%
copy from
baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataColumnImpl.java
copy to
baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataColumnFixed.java
index 400e6033..9b786e69 100644
---
a/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataColumnImpl.java
+++
b/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataColumnFixed.java
@@ -20,6 +20,7 @@ package org.apache.baremaps.data.storage;
/**
* A column in a table.
*/
-public record DataColumnImpl(String name, Type type) implements DataColumn {
+public record DataColumnFixed(String name, Cardinality cardinality,
+ Type type) implements DataColumn {
}
diff --git
a/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataColumnImpl.java
b/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataColumnNested.java
similarity index 80%
rename from
baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataColumnImpl.java
rename to
baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataColumnNested.java
index 400e6033..179b5c8f 100644
---
a/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataColumnImpl.java
+++
b/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataColumnNested.java
@@ -17,9 +17,13 @@
package org.apache.baremaps.data.storage;
-/**
- * A column in a table.
- */
-public record DataColumnImpl(String name, Type type) implements DataColumn {
+import java.util.List;
+
+public record DataColumnNested(String name, Cardinality cardinality,
+ List<DataColumn> columns) implements DataColumn {
+ @Override
+ public Type type() {
+ return Type.NESTED;
+ }
}
diff --git
a/baremaps-data/src/test/java/org/apache/baremaps/data/type/DataTypeProvider.java
b/baremaps-data/src/test/java/org/apache/baremaps/data/type/DataTypeProvider.java
index 0b2f98cf..eb0ec04d 100644
---
a/baremaps-data/src/test/java/org/apache/baremaps/data/type/DataTypeProvider.java
+++
b/baremaps-data/src/test/java/org/apache/baremaps/data/type/DataTypeProvider.java
@@ -22,6 +22,7 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import org.apache.baremaps.data.storage.*;
+import org.apache.baremaps.data.storage.DataColumn.Cardinality;
import org.apache.baremaps.data.storage.DataColumn.Type;
import org.apache.baremaps.data.storage.DataSchema;
import org.apache.baremaps.data.storage.DataSchemaImpl;
@@ -33,23 +34,23 @@ public class DataTypeProvider {
private static final GeometryFactory geometryFactory = new GeometryFactory();
private static final DataSchema DATA_SCHEMA = new DataSchemaImpl("row",
List.of(
- new DataColumnImpl("byte", Type.BYTE),
- new DataColumnImpl("boolean", Type.BOOLEAN),
- new DataColumnImpl("short", Type.SHORT),
- new DataColumnImpl("integer", Type.INTEGER),
- new DataColumnImpl("long", Type.LONG),
- new DataColumnImpl("float", Type.FLOAT),
- new DataColumnImpl("double", Type.DOUBLE),
- new DataColumnImpl("string", Type.STRING),
- new DataColumnImpl("geometry", Type.GEOMETRY),
- new DataColumnImpl("point", Type.POINT),
- new DataColumnImpl("linestring", Type.LINESTRING),
- new DataColumnImpl("polygon", Type.POLYGON),
- new DataColumnImpl("multipoint", Type.MULTIPOINT),
- new DataColumnImpl("multilinestring", Type.MULTILINESTRING),
- new DataColumnImpl("multipolygon", Type.MULTIPOLYGON),
- new DataColumnImpl("geometrycollection", Type.GEOMETRYCOLLECTION),
- new DataColumnImpl("coordinate", Type.COORDINATE)));
+ new DataColumnFixed("byte", Cardinality.OPTIONAL, Type.BYTE),
+ new DataColumnFixed("boolean", Cardinality.OPTIONAL, Type.BOOLEAN),
+ new DataColumnFixed("short", Cardinality.OPTIONAL, Type.SHORT),
+ new DataColumnFixed("integer", Cardinality.OPTIONAL, Type.INTEGER),
+ new DataColumnFixed("long", Cardinality.OPTIONAL, Type.LONG),
+ new DataColumnFixed("float", Cardinality.OPTIONAL, Type.FLOAT),
+ new DataColumnFixed("double", Cardinality.OPTIONAL, Type.DOUBLE),
+ new DataColumnFixed("string", Cardinality.OPTIONAL, Type.STRING),
+ new DataColumnFixed("geometry", Cardinality.OPTIONAL, Type.GEOMETRY),
+ new DataColumnFixed("point", Cardinality.OPTIONAL, Type.POINT),
+ new DataColumnFixed("linestring", Cardinality.OPTIONAL, Type.LINESTRING),
+ new DataColumnFixed("polygon", Cardinality.OPTIONAL, Type.POLYGON),
+ new DataColumnFixed("multipoint", Cardinality.OPTIONAL, Type.MULTIPOINT),
+ new DataColumnFixed("multilinestring", Cardinality.OPTIONAL,
Type.MULTILINESTRING),
+ new DataColumnFixed("multipolygon", Cardinality.OPTIONAL,
Type.MULTIPOLYGON),
+ new DataColumnFixed("geometrycollection", Cardinality.OPTIONAL,
Type.GEOMETRYCOLLECTION),
+ new DataColumnFixed("coordinate", Cardinality.OPTIONAL,
Type.COORDINATE)));
private static final DataRow DATA_ROW = DATA_SCHEMA.createRow()
.with("byte", Byte.MAX_VALUE)
diff --git
a/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/data/GeoParquetGroup.java
b/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/data/GeoParquetGroup.java
index 755f582f..5a3b3709 100644
---
a/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/data/GeoParquetGroup.java
+++
b/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/data/GeoParquetGroup.java
@@ -20,6 +20,7 @@ package org.apache.baremaps.geoparquet.data;
import java.util.List;
import org.apache.parquet.io.api.Binary;
import org.apache.parquet.schema.GroupType;
+import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
/**
@@ -98,6 +99,10 @@ public interface GeoParquetGroup {
List<Geometry> getGeometryValues(int fieldIndex);
+ Envelope getEnvelopeValue(int fieldIndex);
+
+ List<Envelope> getEnvelopeValues(int fieldIndex);
+
GeoParquetGroup getGroupValue(int fieldIndex);
List<GeoParquetGroup> getGroupValues(int fieldIndex);
@@ -142,6 +147,10 @@ public interface GeoParquetGroup {
List<Geometry> getGeometryValues(String fieldName);
+ Envelope getEnvelopeValue(String fieldName);
+
+ List<Envelope> getEnvelopeValues(String fieldName);
+
GeoParquetGroup getGroupValue(String fieldName);
List<GeoParquetGroup> getGroupValues(String fieldName);
@@ -186,6 +195,10 @@ public interface GeoParquetGroup {
void setGeometryValues(int fieldIndex, List<Geometry> geometryValues);
+ void setEnvelopeValue(int fieldIndex, Envelope envelopeValue);
+
+ void setEnvelopeValues(int fieldIndex, List<Envelope> envelopeValues);
+
void setGroupValue(int fieldIndex, GeoParquetGroup groupValue);
void setGroupValues(int fieldIndex, List<GeoParquetGroup> groupValues);
@@ -230,6 +243,10 @@ public interface GeoParquetGroup {
void setGeometryValues(String fieldName, List<Geometry> geometryValues);
+ void setEnvelopeValue(String fieldName, Envelope envelopeValue);
+
+ void setEnvelopeValues(String fieldName, List<Envelope> envelopeValues);
+
void setGroupValue(String fieldName, GeoParquetGroup groupValue);
void setGroupValues(String fieldName, List<GeoParquetGroup> groupValues);
@@ -331,6 +348,14 @@ public interface GeoParquetGroup {
}
}
+ record EnvelopeField(String name, Cardinality cardinality, Schema schema)
implements Field {
+
+ @Override
+ public Type type() {
+ return Type.ENVELOPE;
+ }
+ }
+
record GroupField(String name, Cardinality cardinality, Schema schema)
implements Field {
@Override
@@ -352,6 +377,7 @@ public interface GeoParquetGroup {
LONG,
STRING,
GEOMETRY,
+ ENVELOPE,
GROUP
}
diff --git
a/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/data/GeoParquetGroupFactory.java
b/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/data/GeoParquetGroupFactory.java
index d89e0a03..f925df50 100644
---
a/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/data/GeoParquetGroupFactory.java
+++
b/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/data/GeoParquetGroupFactory.java
@@ -40,34 +40,57 @@ public class GeoParquetGroupFactory {
public static GeoParquetGroup.Schema createGeoParquetSchema(
GroupType schema,
GeoParquetMetadata metadata) {
+
+ // Map the fields
List<Field> fields = schema.getFields().stream().map(field -> {
+
+ // Map the column cardinality
GeoParquetGroup.Cardinality cardinality = switch (field.getRepetition())
{
case REQUIRED -> GeoParquetGroup.Cardinality.REQUIRED;
case OPTIONAL -> GeoParquetGroup.Cardinality.OPTIONAL;
case REPEATED -> GeoParquetGroup.Cardinality.REPEATED;
};
+
+ // Handle geometry columns
if (field.isPrimitive() && metadata.isGeometryColumn(field.getName())) {
return new GeoParquetGroup.GeometryField(field.getName(), cardinality);
- } else if (field.isPrimitive()) {
+ }
+
+ // Handle envelope columns
+ else if (!field.isPrimitive() && field.getName().equals("bbox")) {
+ GroupType groupType = field.asGroupType();
+ GeoParquetGroup.Schema geoParquetSchema =
createGeoParquetSchema(groupType, metadata);
+ return new GeoParquetGroup.EnvelopeField(field.getName(), cardinality,
geoParquetSchema);
+ }
+
+ // Handle group columns
+ else if (!field.isPrimitive()) {
+ GroupType groupType = field.asGroupType();
+ GeoParquetGroup.Schema geoParquetSchema =
createGeoParquetSchema(groupType, metadata);
+ return (Field) new GeoParquetGroup.GroupField(
+ groupType.getName(),
+ GeoParquetGroup.Cardinality.REQUIRED,
+ geoParquetSchema);
+ }
+
+ // Handle primitive columns
+ else {
PrimitiveType primitiveType = field.asPrimitiveType();
PrimitiveTypeName primitiveTypeName =
primitiveType.getPrimitiveTypeName();
- String name = primitiveType.getName();
+ String columnName = primitiveType.getName();
return switch (primitiveTypeName) {
- case INT32 -> new GeoParquetGroup.IntegerField(name, cardinality);
- case INT64 -> new GeoParquetGroup.LongField(name, cardinality);
- case INT96 -> new GeoParquetGroup.Int96Field(name, cardinality);
- case FLOAT -> new GeoParquetGroup.FloatField(name, cardinality);
- case DOUBLE -> new GeoParquetGroup.DoubleField(name, cardinality);
- case BOOLEAN -> new GeoParquetGroup.BooleanField(name, cardinality);
- case BINARY -> new GeoParquetGroup.BinaryField(name, cardinality);
- case FIXED_LEN_BYTE_ARRAY -> new GeoParquetGroup.BinaryField(name,
cardinality);
+ case INT32 -> new GeoParquetGroup.IntegerField(columnName,
cardinality);
+ case INT64 -> new GeoParquetGroup.LongField(columnName, cardinality);
+ case INT96 -> new GeoParquetGroup.Int96Field(columnName,
cardinality);
+ case FLOAT -> new GeoParquetGroup.FloatField(columnName,
cardinality);
+ case DOUBLE -> new GeoParquetGroup.DoubleField(columnName,
cardinality);
+ case BOOLEAN -> new GeoParquetGroup.BooleanField(columnName,
cardinality);
+ case BINARY -> new GeoParquetGroup.BinaryField(columnName,
cardinality);
+ case FIXED_LEN_BYTE_ARRAY -> new
GeoParquetGroup.BinaryField(columnName, cardinality);
};
- } else {
- GroupType groupType = field.asGroupType();
- return (Field) new GeoParquetGroup.GroupField(groupType.getName(),
- GeoParquetGroup.Cardinality.REQUIRED,
createGeoParquetSchema(groupType, metadata));
}
}).toList();
+
return new GeoParquetGroup.Schema(schema.getName(), fields);
}
diff --git
a/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/data/GeoParquetGroupImpl.java
b/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/data/GeoParquetGroupImpl.java
index 2cd49058..9d959ca4 100644
---
a/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/data/GeoParquetGroupImpl.java
+++
b/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/data/GeoParquetGroupImpl.java
@@ -23,6 +23,7 @@ import org.apache.baremaps.geoparquet.GeoParquetException;
import org.apache.parquet.io.api.Binary;
import org.apache.parquet.io.api.RecordConsumer;
import org.apache.parquet.schema.GroupType;
+import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKBReader;
@@ -38,7 +39,9 @@ public class GeoParquetGroupImpl implements GeoParquetGroup {
private final List<?>[] data;
- public GeoParquetGroupImpl(GroupType schema, GeoParquetMetadata metadata,
+ public GeoParquetGroupImpl(
+ GroupType schema,
+ GeoParquetMetadata metadata,
Schema geoParquetSchema) {
this.schema = schema;
this.metadata = metadata;
@@ -50,11 +53,9 @@ public class GeoParquetGroupImpl implements GeoParquetGroup {
}
public GeoParquetGroupImpl addGroup(int fieldIndex) {
- GeoParquetGroupImpl g =
- new GeoParquetGroupImpl(schema.getType(fieldIndex).asGroupType(),
metadata,
- geoParquetSchema);
- add(fieldIndex, g);
- return g;
+ GeoParquetGroupImpl group = createGroup(fieldIndex);
+ add(fieldIndex, group);
+ return group;
}
public GeoParquetGroupImpl addGroup(String field) {
@@ -301,9 +302,21 @@ public class GeoParquetGroupImpl implements
GeoParquetGroup {
}
@Override
- public GeoParquetGroup createGroup(int fieldIndex) {
- return new GeoParquetGroupImpl(schema.getType(fieldIndex).asGroupType(),
metadata,
- geoParquetSchema);
+ public GeoParquetGroupImpl createGroup(int fieldIndex) {
+ if (geoParquetSchema.fields().get(fieldIndex) instanceof EnvelopeField
envelopeField) {
+ return new GeoParquetGroupImpl(schema.getType(fieldIndex).asGroupType(),
metadata,
+ envelopeField.schema());
+ }
+
+ if (geoParquetSchema.fields().get(fieldIndex) instanceof GroupField
groupField) {
+ return new GeoParquetGroupImpl(schema.getType(fieldIndex).asGroupType(),
metadata,
+ groupField.schema());
+ }
+
+ GroupField field = ((GroupField)
geoParquetSchema.fields().get(fieldIndex));
+ GeoParquetGroupImpl group =
+ new GeoParquetGroupImpl(schema.getType(fieldIndex).asGroupType(),
metadata, field.schema());
+ return group;
}
@Override
@@ -414,6 +427,30 @@ public class GeoParquetGroupImpl implements
GeoParquetGroup {
return geometries;
}
+ @Override
+ public Envelope getEnvelopeValue(int fieldIndex) {
+ return getEnvelopeValues(fieldIndex).get(0);
+ }
+
+ @Override
+ public List<Envelope> getEnvelopeValues(int fieldIndex) {
+ return getGroupValues(fieldIndex).stream().map(group -> {
+ double xMin = group.getSchema().fields().get(0).type().equals(Type.FLOAT)
+ ? group.getFloatValue(0)
+ : group.getDoubleValue(0);
+ double yMin = group.getSchema().fields().get(1).type().equals(Type.FLOAT)
+ ? group.getFloatValue(1)
+ : group.getDoubleValue(1);
+ double xMax = group.getSchema().fields().get(2).type().equals(Type.FLOAT)
+ ? group.getFloatValue(2)
+ : group.getDoubleValue(2);
+ double yMax = group.getSchema().fields().get(0).type().equals(Type.FLOAT)
+ ? group.getFloatValue(3)
+ : group.getDoubleValue(3);
+ return new Envelope(xMin, xMax, yMin, yMax);
+ }).toList();
+ }
+
@Override
public GeoParquetGroup getGroupValue(int fieldIndex) {
return getGroupValues(fieldIndex).get(0);
@@ -524,6 +561,16 @@ public class GeoParquetGroupImpl implements
GeoParquetGroup {
return getGeometryValues(schema.getFieldIndex(fieldName));
}
+ @Override
+ public Envelope getEnvelopeValue(String fieldName) {
+ return getEnvelopeValues(fieldName).get(0);
+ }
+
+ @Override
+ public List<Envelope> getEnvelopeValues(String fieldName) {
+ return getEnvelopeValues(schema.getFieldIndex(fieldName));
+ }
+
@Override
public GeoParquetGroup getGroupValue(String fieldName) {
return getGroupValues(fieldName).get(0);
@@ -634,6 +681,16 @@ public class GeoParquetGroupImpl implements
GeoParquetGroup {
throw new UnsupportedOperationException();
}
+ @Override
+ public void setEnvelopeValue(int fieldIndex, Envelope envelopeValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setEnvelopeValues(int fieldIndex, List<Envelope> envelopeValues)
{
+ throw new UnsupportedOperationException();
+ }
+
@Override
public void setGroupValue(int fieldIndex, GeoParquetGroup groupValue) {
throw new UnsupportedOperationException();
@@ -744,6 +801,16 @@ public class GeoParquetGroupImpl implements
GeoParquetGroup {
throw new UnsupportedOperationException();
}
+ @Override
+ public void setEnvelopeValue(String fieldName, Envelope envelopeValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setEnvelopeValues(String fieldName, List<Envelope>
envelopeValues) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public void setGroupValue(String fieldName, GeoParquetGroup groupValue) {
throw new UnsupportedOperationException();
diff --git
a/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/data/GeoParquetMetadata.java
b/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/data/GeoParquetMetadata.java
index 43c9f032..fe3955d7 100644
---
a/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/data/GeoParquetMetadata.java
+++
b/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/data/GeoParquetMetadata.java
@@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Objects;
import java.util.Map;
+import java.util.Optional;
public class GeoParquetMetadata {
@@ -58,15 +59,17 @@ public class GeoParquetMetadata {
}
public int getSrid(String column) {
- JsonNode crsId = getColumns().get(column).getCrs().get("id");
- return switch (crsId.get("authority").asText()) {
- case "OGC" -> switch (crsId.get("code").asText()) {
- case "CRS84" -> 4326;
- default -> 0;
- };
- case "EPSG" -> crsId.get("code").asInt();
- default -> 0;
- };
+ return Optional.ofNullable(getColumns().get(column).getCrs()).map(crs -> {
+ JsonNode id = crs.get("id");
+ return switch (id.get("authority").asText()) {
+ case "OGC" -> switch (id.get("code").asText()) {
+ case "CRS84" -> 4326;
+ default -> 0;
+ };
+ case "EPSG" -> id.get("code").asInt();
+ default -> 0;
+ };
+ }).orElse(4326);
}
public boolean isGeometryColumn(String column) {