This is an automated email from the ASF dual-hosted git repository. bchapuis pushed a commit to branch flatgeobuf in repository https://gitbox.apache.org/repos/asf/incubator-baremaps.git
commit 1f71d24e5c16a4a65bf532d5873ac28a8f09806d Author: Bertil Chapuis <[email protected]> AuthorDate: Mon Jun 17 13:36:37 2024 +0200 Add support for flatgeobuf --- baremaps-core/pom.xml | 4 + baremaps-flatgeobuf/pom.xml | 29 ++ .../baremaps/flatgeobuf/BoundedInputStream.java | 74 ++++ .../org/apache/baremaps/flatgeobuf/BufferUtil.java | 133 +++++++ .../org/apache/baremaps/flatgeobuf/Constants.java | 37 ++ .../baremaps/flatgeobuf/FeatureMetaIterator.java | 85 ++++ .../org/apache/baremaps/flatgeobuf/FlatGeoBuf.java | 231 +++++++++++ .../baremaps/flatgeobuf/FlatGeoBufReader.java | 131 ++++++ .../baremaps/flatgeobuf/FlatGeoBufWriter.java | 160 ++++++++ .../baremaps/flatgeobuf/GeometryConversions.java | 353 ++++++++++++++++ .../baremaps/flatgeobuf/GeometryOffsets.java | 30 ++ .../org/apache/baremaps/flatgeobuf/NodeItem.java | 82 ++++ .../apache/baremaps/flatgeobuf/PackedRTree.java | 443 +++++++++++++++++++++ .../baremaps/flatgeobuf/generated/Column.java | 233 +++++++++++ .../baremaps/flatgeobuf/generated/ColumnType.java | 46 +++ .../apache/baremaps/flatgeobuf/generated/Crs.java | 185 +++++++++ .../baremaps/flatgeobuf/generated/Feature.java | 188 +++++++++ .../baremaps/flatgeobuf/generated/Geometry.java | 397 ++++++++++++++++++ .../flatgeobuf/generated/GeometryType.java | 51 +++ .../baremaps/flatgeobuf/generated/Header.java | 340 ++++++++++++++++ .../src/main/resources/fbs/feature.fbs | 22 + .../src/main/resources/fbs/header.fbs | 84 ++++ .../apache/baremaps/flatgeobuf/BufferUtilTest.java | 127 ++++++ .../apache/baremaps/flatgeobuf/FlatGeoBufTest.java | 76 ++++ baremaps-testing/pom.xml | 6 + pom.xml | 5 +- scripts/generate-flatgeobuf.sh | 6 + 27 files changed, 3554 insertions(+), 4 deletions(-) diff --git a/baremaps-core/pom.xml b/baremaps-core/pom.xml index 28fc6af3..b5c39796 100644 --- a/baremaps-core/pom.xml +++ b/baremaps-core/pom.xml @@ -146,6 +146,10 @@ limitations under the License. <groupId>org.wololo</groupId> <artifactId>flatgeobuf</artifactId> </dependency> + <dependency> + <groupId>org.wololo</groupId> + <artifactId>flatgeobuf</artifactId> + </dependency> <dependency> <groupId>org.xerial</groupId> <artifactId>sqlite-jdbc</artifactId> diff --git a/baremaps-flatgeobuf/pom.xml b/baremaps-flatgeobuf/pom.xml new file mode 100644 index 00000000..e1955007 --- /dev/null +++ b/baremaps-flatgeobuf/pom.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.baremaps</groupId> + <artifactId>baremaps</artifactId> + <version>0.7.4-SNAPSHOT</version> + </parent> + <artifactId>baremaps-flatgeobuf</artifactId> + <dependencies> + <dependency> + <groupId>com.google.flatbuffers</groupId> + <artifactId>flatbuffers-java</artifactId> + <version>24.3.25</version> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </dependency> + <dependency> + <groupId>org.apache.baremaps</groupId> + <artifactId>baremaps-testing</artifactId> + </dependency> + <dependency> + <groupId>org.locationtech.jts</groupId> + <artifactId>jts-core</artifactId> + </dependency> + </dependencies> +</project> diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/BoundedInputStream.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/BoundedInputStream.java new file mode 100644 index 00000000..38b91bf5 --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/BoundedInputStream.java @@ -0,0 +1,74 @@ +/* + * 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.flatgeobuf; + +import java.io.IOException; +import java.io.InputStream; + +public class BoundedInputStream extends InputStream { + private final InputStream in; + private long remaining; + + public BoundedInputStream(InputStream in, long size) { + this.in = in; + this.remaining = size; + } + + @Override + public int read() throws IOException { + if (remaining == 0) { + return -1; + } + int result = in.read(); + if (result != -1) { + remaining--; + } + return result; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (remaining == 0) { + return -1; + } + int toRead = (int) Math.min(len, remaining); + int result = in.read(b, off, toRead); + if (result != -1) { + remaining -= result; + } + return result; + } + + @Override + public long skip(long n) throws IOException { + long toSkip = Math.min(n, remaining); + long skipped = in.skip(toSkip); + remaining -= skipped; + return skipped; + } + + @Override + public int available() throws IOException { + return (int) Math.min(in.available(), remaining); + } + + @Override + public void close() throws IOException { + in.close(); + } +} diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/BufferUtil.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/BufferUtil.java new file mode 100644 index 00000000..5530a340 --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/BufferUtil.java @@ -0,0 +1,133 @@ +/* + * 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.flatgeobuf; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.ReadableByteChannel; + +public class BufferUtil { + + private BufferUtil() { + // Prevent instantiation + } + + public static ByteBuffer createByteBuffer(int capacity, ByteOrder order) { + ByteBuffer buffer = ByteBuffer.allocate(capacity).order(order); + buffer.flip(); + return buffer; + } + + /** + * Skips the given number of bytes from the specified channel, accounting for the bytes already in + * the buffer. + * + * @param channel the channel to skip bytes from + * @param buffer the buffer to use + * @param bytesToSkip the number of bytes to skip + * @return the buffer after skipping the specified number of bytes + * @throws IOException if an I/O error occurs while reading from the channel + */ + public static ByteBuffer skipBytes(ReadableByteChannel channel, ByteBuffer buffer, + long bytesToSkip) throws IOException { + if (channel == null || buffer == null) { + throw new IllegalArgumentException("Channel and buffer must not be null"); + } + + if (bytesToSkip < 0) { + throw new IllegalArgumentException("The number of bytes to skip must be non-negative"); + } + + // If the buffer already has `bytesToSkip` or more bytes remaining, simply adjust the position. + if (buffer.remaining() >= bytesToSkip) { + buffer.position(buffer.position() + (int) bytesToSkip); + return buffer; + } + + // Calculate the number of bytes we still need to skip after accounting for the buffer's + // remaining bytes. + long remainingBytesToSkip = bytesToSkip - buffer.remaining(); + + // Clear the buffer to prepare it for reading. + buffer.clear(); + + // Skip bytes directly from the channel. + while (remainingBytesToSkip > 0) { + // Read into the buffer to discard the data. + int bytesRead = channel.read(buffer); + if (bytesRead == -1) { + break; // End of channel reached + } + remainingBytesToSkip -= bytesRead; + buffer.clear(); + } + + return buffer; + } + + /** + * Prepares the given buffer for reading at least `n` bytes from the specified channel. + * + * @param channel the channel to read bytes from + * @param buffer the buffer to prepare for reading + * @param bytesToRead the minimum number of bytes the buffer should contain + * @return a ByteBuffer that contains at least `n` bytes read from the channel + * @throws IOException if an I/O error occurs while reading from the channel + */ + public static ByteBuffer readBytes(ReadableByteChannel channel, ByteBuffer buffer, + int bytesToRead) throws IOException { + if (channel == null || buffer == null) { + throw new IllegalArgumentException("Channel and buffer must not be null"); + } + + if (bytesToRead < 0) { + throw new IllegalArgumentException("The number of bytes to read must be non-negative"); + } + + // If the buffer already has `n` or more bytes remaining, it will be returned as is. + if (buffer.remaining() >= bytesToRead) { + return buffer; + } + + // If the buffer has sufficient capacity but fewer than `n` bytes remaining, compact it and read + // more bytes. + if (buffer.capacity() >= bytesToRead) { + buffer.compact(); + while (buffer.position() < bytesToRead) { + if (channel.read(buffer) == -1) { + break; // End of channel reached + } + } + buffer.flip(); + return buffer; + } + + // If the buffer has insufficient capacity, allocate a new buffer with the required capacity. + ByteBuffer newBuffer = ByteBuffer.allocate(bytesToRead).order(buffer.order()); + buffer.flip(); + newBuffer.put(buffer); + while (newBuffer.position() < bytesToRead) { + if (channel.read(newBuffer) == -1) { + break; // End of channel reached + } + } + newBuffer.flip(); + return newBuffer; + } +} diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/Constants.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/Constants.java new file mode 100644 index 00000000..1a15e1b7 --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/Constants.java @@ -0,0 +1,37 @@ +/* + * 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.flatgeobuf; + +import java.nio.ByteBuffer; + +public class Constants { + + public static final byte[] MAGIC_BYTES = + new byte[] {0x66, 0x67, 0x62, 0x03, 0x66, 0x67, 0x62, 0x00}; + + public static boolean isFlatgeobuf(ByteBuffer bb) { + return bb.get() == MAGIC_BYTES[0] && + bb.get() == MAGIC_BYTES[1] && + bb.get() == MAGIC_BYTES[2] && + bb.get() == MAGIC_BYTES[3] && + bb.get() == MAGIC_BYTES[4] && + bb.get() == MAGIC_BYTES[5] && + bb.get() == MAGIC_BYTES[6] && + bb.get() == MAGIC_BYTES[7]; + } +} diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/FeatureMetaIterator.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/FeatureMetaIterator.java new file mode 100644 index 00000000..c11b93f6 --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/FeatureMetaIterator.java @@ -0,0 +1,85 @@ +/* + * 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.flatgeobuf; + +// +// import java.io.IOException; +// import java.nio.ByteBuffer; +// import java.nio.channels.ReadableByteChannel; +// import java.nio.channels.SeekableByteChannel; +// import java.util.Iterator; +// import java.util.NoSuchElementException; +// +// public class FeatureMetaIterator implements Iterator<FeatureMeta> { +// +// private final HeaderMeta headerMeta; +// +// private final ReadableByteChannel channel; +// +// private final ByteBuffer buffer; +// +// private long cursor = 0; +// +// /** +// * Constructs a row iterator. +// * +// * @param channel the channel to read from +// * @param headerMeta the header meta +// * @param buffer the buffer to use +// */ +// public FeatureMetaIterator( +// SeekableByteChannel channel, +// HeaderMeta headerMeta, +// ByteBuffer buffer) { +// this.channel = channel; +// this.headerMeta = headerMeta; +// this.buffer = buffer; +// } +// +// /** +// * {@inheritDoc} +// */ +// @Override +// public boolean hasNext() { +// return cursor < headerMeta.featuresCount; +// } +// +// /** +// * {@inheritDoc} +// */ +// @Override +// public FeatureMeta next() { +// try { +// channel.read(buffer); +// buffer.flip(); +// +// var featureSize = buffer.getInt(); +// var featureMeta = FlatGeoBufReader.readFeature(buffer, headerMeta); +// +// buffer.position(Integer.BYTES + featureSize); +// buffer.compact(); +// +// cursor++; +// +// return featureMeta; +// } catch (IOException e) { +// throw new NoSuchElementException(e); +// } +// } +// +// } diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/FlatGeoBuf.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/FlatGeoBuf.java new file mode 100644 index 00000000..0a07db14 --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/FlatGeoBuf.java @@ -0,0 +1,231 @@ +/* + * 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.flatgeobuf; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; +import org.apache.baremaps.flatgeobuf.FlatGeoBuf.Header.Feature; +import org.locationtech.jts.geom.Geometry; + +public class FlatGeoBuf { + + private FlatGeoBuf() { + // Prevent instantiation + } + + // Geometry type enumeration + public enum GeometryType { + UNKNOWN(0), + POINT(1), + LINESTRING(2), + POLYGON(3), + MULTIPOINT(4), + MULTILINESTRING(5), + MULTIPOLYGON(6), + GEOMETRYCOLLECTION(7), + CIRCULARSTRING(8), + COMPOUNDCURVE(9), + CURVEPOLYGON(10), + MULTICURVE(11), + MULTISURFACE(12), + CURVE(13), + SURFACE(14), + POLYHEDRALSURFACE(15), + TIN(16), + TRIANGLE(17); + + private final int value; + + GeometryType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + + public enum ColumnType { + BYTE, + UBYTE, + BOOL, + SHORT, + USHORT, + INT, + UINT, + LONG, + ULONG, + FLOAT, + DOUBLE, + STRING, + JSON, + DATETIME, + BINARY + } + + public record Column( + String name, + ColumnType type, + String title, + String description, + int width, + int precision, + int scale, + boolean nullable, + boolean unique, + boolean primaryKey, + String metadata) { + } + + public record Crs( + String org, + int code, + String name, + String description, + String wkt, + String codeString) { + } + + public record Header( + String name, + double[] envelope, + GeometryType geometryType, + boolean hasZ, + boolean hasM, + boolean hasT, + boolean hasTM, + List<Column> columns, + long featuresCount, + int indexNodeSize, + Crs crs, + String title, + String description, + String metadata) { + public Header { + indexNodeSize = indexNodeSize == 0 ? 16 : indexNodeSize; + } + + public record Feature( + Geometry geometry, + List<Object> properties) { + } + } + + public static Header asHeaderRecord(org.apache.baremaps.flatgeobuf.generated.Header header) { + return new Header( + header.name(), + new double[] { + header.envelope(0), + header.envelope(1), + header.envelope(2), + header.envelope(3) + }, + GeometryType.values()[header.geometryType()], + header.hasZ(), + header.hasM(), + header.hasT(), + header.hasTm(), + IntStream.range(0, header.columnsLength()) + .mapToObj(header::columns) + .map(column -> new Column( + column.name(), + ColumnType.values()[column.type()], + column.title(), + column.description(), + column.width(), + column.precision(), + column.scale(), + column.nullable(), + column.unique(), + column.primaryKey(), + column.metadata())) + .toList(), + header.featuresCount(), + header.indexNodeSize(), + new Crs( + header.crs().org(), + header.crs().code(), + header.crs().name(), + header.crs().description(), + header.crs().wkt(), + header.crs().codeString()), + header.title(), + header.description(), + header.metadata()); + } + + public static Feature asFeatureRecord(org.apache.baremaps.flatgeobuf.generated.Header header, + org.apache.baremaps.flatgeobuf.generated.Feature feature) { + var values = new ArrayList<>(); + if (feature.propertiesLength() > 0) { + var propertiesBuffer = feature.propertiesAsByteBuffer(); + while (propertiesBuffer.hasRemaining()) { + var columnPosition = propertiesBuffer.getShort(); + var columnType = header.columns(columnPosition); + var columnValue = readValue(propertiesBuffer, columnType); + values.add(columnValue); + } + } + return new Feature( + GeometryConversions.readGeometry(feature.geometry(), header.geometryType()), + values); + } + + private static Object readValue(ByteBuffer buffer, + org.apache.baremaps.flatgeobuf.generated.Column column) { + return switch (ColumnType.values()[column.type()]) { + case BYTE -> buffer.get(); + case UBYTE -> buffer.get(); + case BOOL -> buffer.get() == 1; + case SHORT -> buffer.getShort(); + case USHORT -> buffer.getShort(); + case INT -> buffer.getInt(); + case UINT -> buffer.getInt(); + case LONG -> buffer.getLong(); + case ULONG -> buffer.getLong(); + case FLOAT -> buffer.getFloat(); + case DOUBLE -> buffer.getDouble(); + case STRING -> readString(buffer); + case JSON -> readJson(buffer); + case DATETIME -> readDateTime(buffer); + case BINARY -> readBinary(buffer); + }; + } + + private static Object readString(ByteBuffer buffer) { + var length = buffer.getInt(); + var bytes = new byte[length]; + buffer.get(bytes); + return new String(bytes, StandardCharsets.UTF_8); + } + + private static Object readJson(ByteBuffer buffer) { + throw new UnsupportedOperationException(); + } + + private static Object readDateTime(ByteBuffer buffer) { + throw new UnsupportedOperationException(); + } + + private static Object readBinary(ByteBuffer buffer) { + throw new UnsupportedOperationException(); + } +} diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/FlatGeoBufReader.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/FlatGeoBufReader.java new file mode 100644 index 00000000..f59b9397 --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/FlatGeoBufReader.java @@ -0,0 +1,131 @@ +/* + * 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.flatgeobuf; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import org.apache.baremaps.flatgeobuf.generated.Feature; +import org.apache.baremaps.flatgeobuf.generated.Header; + +public class FlatGeoBufReader { + + public static Header readHeader(ReadableByteChannel channel) + throws IOException { + + // Check if the file is a flatgeobuf + ByteBuffer buffer = BufferUtil.createByteBuffer(12, ByteOrder.LITTLE_ENDIAN); + BufferUtil.readBytes(channel, buffer, 12); + if (!Constants.isFlatgeobuf(buffer)) { + throw new IOException("This is not a flatgeobuf!"); + } + + // Read the header size + int headerSize = buffer.getInt(); + ByteBuffer headerBuffer = BufferUtil.createByteBuffer(headerSize, ByteOrder.LITTLE_ENDIAN); + BufferUtil.readBytes(channel, headerBuffer, headerSize); + + return Header.getRootAsHeader(headerBuffer); + } + + public static void skipIndex(ReadableByteChannel channel, Header header) + throws IOException { + readIndexAsBuffer(channel, header); + } + + public static ByteBuffer readIndexAsBuffer(ReadableByteChannel channel, Header header) + throws IOException { + long indexSize = PackedRTree.calcSize(header.featuresCount(), header.indexNodeSize()); + + if (indexSize > 1L << 31) { + throw new IOException("Index size is greater than 2GB!"); + } + ByteBuffer buffer = BufferUtil.createByteBuffer((int) indexSize, ByteOrder.LITTLE_ENDIAN); + BufferUtil.readBytes(channel, buffer, (int) indexSize); + + return buffer; + } + + public static InputStream readIndexAsStream(ReadableByteChannel channel, Header header) { + long indexSize = PackedRTree.calcSize(header.featuresCount(), header.indexNodeSize()); + return new BoundedInputStream(Channels.newInputStream(channel), indexSize); + } + + public static Feature readFeature(ReadableByteChannel channel, ByteBuffer buffer) + throws IOException { + ByteBuffer newBuffer = BufferUtil.readBytes(channel, buffer, 4); + int featureSize = newBuffer.getInt(); + newBuffer = BufferUtil.readBytes(channel, buffer, featureSize); + Feature feature = Feature.getRootAsFeature(newBuffer); + buffer.position(buffer.position() + featureSize); + return feature; + } + + // var geometryBuffer = feature.geometry(); + // var geometry = GeometryConversions.readGeometry(geometryBuffer, geometryBuffer.type()); + // var properties = new ArrayList<>(); + // if (feature.propertiesLength() > 0) { + // var propertiesBuffer = feature.propertiesAsByteBuffer(); + // while (propertiesBuffer.hasRemaining()) { + // var type = propertiesBuffer.getShort(); + // var column = header.columns.get(type); + // var value = readColumnValue(propertiesBuffer, column); + // properties.add(value); + // } + // } + // } + // + // public static Object readColumnValue(ByteBuffer buffer, ColumnMeta column) { + // return switch (column.type()) { + // case ColumnType.Byte -> buffer.get(); + // case ColumnType.Bool -> buffer.get() == 1; + // case ColumnType.Short -> buffer.getShort(); + // case ColumnType.Int -> buffer.getInt(); + // case ColumnType.Long -> buffer.getLong(); + // case ColumnType.Float -> buffer.getFloat(); + // case ColumnType.Double -> buffer.getDouble(); + // case ColumnType.String -> readColumnString(buffer); + // case ColumnType.Json -> readColumnJson(buffer); + // case ColumnType.DateTime -> readColumnDateTime(buffer); + // case ColumnType.Binary -> readColumnBinary(buffer); + // default -> null; + // }; + // } + // + // public static Object readColumnString(ByteBuffer buffer) { + // var length = buffer.getInt(); + // var bytes = new byte[length]; + // buffer.get(bytes); + // return new String(bytes, StandardCharsets.UTF_8); + // } + // + // public static Object readColumnJson(ByteBuffer buffer) { + // throw new UnsupportedOperationException(); + // } + // + // public static Object readColumnDateTime(ByteBuffer buffer) { + // throw new UnsupportedOperationException(); + // } + // + // public static Object readColumnBinary(ByteBuffer buffer) { + // throw new UnsupportedOperationException(); + // } +} diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/FlatGeoBufWriter.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/FlatGeoBufWriter.java new file mode 100644 index 00000000..c1872857 --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/FlatGeoBufWriter.java @@ -0,0 +1,160 @@ +/* + * 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.flatgeobuf; + +// +// import com.google.flatbuffers.FlatBufferBuilder; +// import java.io.IOException; +// import java.io.OutputStream; +// import java.nio.ByteBuffer; +// import java.nio.ByteOrder; +// import java.nio.channels.Channels; +// import java.nio.channels.WritableByteChannel; +// import java.nio.charset.StandardCharsets; +// +// import org.wololo.flatgeobuf.generated.*; +// +// public class FlatGeoBufWriter { +// public static void writeColumnValue(ByteBuffer buffer, ColumnMeta column, Object value) { +// switch (column.type()) { +// case ColumnType.Byte -> buffer.put((byte) value); +// case ColumnType.Bool -> buffer.put((byte) ((boolean) value ? 1 : 0)); +// case ColumnType.Short -> buffer.putShort((short) value); +// case ColumnType.Int -> buffer.putInt((int) value); +// case ColumnType.Long -> buffer.putLong((long) value); +// case ColumnType.Float -> buffer.putFloat((float) value); +// case ColumnType.Double -> buffer.putDouble((double) value); +// case ColumnType.String -> writeColumnString(buffer, value); +// case ColumnType.Json -> writeColumnJson(buffer, value); +// case ColumnType.DateTime -> writeColumnDateTime(buffer, value); +// case ColumnType.Binary -> writeColumnBinary(buffer, value); +// default -> { +// // Do nothing +// } +// }; +// } +// +// public static void writeColumnString(ByteBuffer propertiesBuffer, Object value) { +// var bytes = ((String) value).getBytes(StandardCharsets.UTF_8); +// propertiesBuffer.putInt(bytes.length); +// propertiesBuffer.put(bytes); +// } +// +// public static void writeColumnJson(ByteBuffer propertiesBuffer, Object value) { +// throw new UnsupportedOperationException(); +// } +// +// public static void writeColumnDateTime(ByteBuffer propertiesBuffer, Object value) { +// throw new UnsupportedOperationException(); +// } +// +// public static void writeColumnBinary(ByteBuffer propertiesBuffer, Object value) { +// throw new UnsupportedOperationException(); +// } +// +// public static void writeFeature( +// OutputStream outputStream, HeaderMeta headerMeta, +// FeatureMeta featureMeta) throws IOException { +// var featureBuilder = new FlatBufferBuilder(4096); +// +// // Write the properties +// var propertiesBuffer = ByteBuffer.allocate(1 << 20).order(ByteOrder.LITTLE_ENDIAN); +// var properties = featureMeta.properties(); +// for (int i = 0; i < properties.size(); i++) { +// var column = headerMeta.columns.get(i); +// var value = properties.get(i); +// propertiesBuffer.putShort((short) i); +// writeColumnValue(propertiesBuffer, column, value); +// } +// if (propertiesBuffer.position() > 0) { +// propertiesBuffer.flip(); +// } +// var propertiesOffset = Feature.createPropertiesVector(featureBuilder, propertiesBuffer); +// +// // Write the geometry +// var geometry = featureMeta.geometry(); +// var geometryOffset = 0; +// if (geometry != null) { +// geometryOffset = +// GeometryConversions.writeGeometry(featureBuilder, geometry, headerMeta.geometryType); +// } +// +// // Write the feature +// var featureOffset = Feature.createFeature(featureBuilder, geometryOffset, propertiesOffset, 0); +// featureBuilder.finishSizePrefixed(featureOffset); +// +// byte[] data = featureBuilder.sizedByteArray(); +// outputStream.write(data); +// } +// +// public static void write(HeaderMeta headerMeta, OutputStream to, FlatBufferBuilder builder) +// throws IOException { +// int[] columnsArray = headerMeta.columns.stream().mapToInt(c -> { +// int nameOffset = builder.createString(c.name()); +// int type = c.type(); +// return Column.createColumn( +// builder, +// nameOffset, +// type, +// 0, +// 0, +// c.width(), +// c.precision(), +// c.scale(), +// c.nullable(), +// c.unique(), +// c.primaryKey(), +// 0); +// }).toArray(); +// int columnsOffset = Header.createColumnsVector(builder, columnsArray); +// +// int nameOffset = 0; +// if (headerMeta.name != null) { +// nameOffset = builder.createString(headerMeta.name); +// } +// int crsOffset = 0; +// if (headerMeta.srid != 0) { +// Crs.startCrs(builder); +// Crs.addCode(builder, headerMeta.srid); +// crsOffset = Crs.endCrs(builder); +// } +// int envelopeOffset = 0; +// if (headerMeta.envelope != null) { +// envelopeOffset = Header.createEnvelopeVector(builder, +// new double[] {headerMeta.envelope.getMinX(), headerMeta.envelope.getMinY(), +// headerMeta.envelope.getMaxX(), headerMeta.envelope.getMaxY()}); +// } +// Header.startHeader(builder); +// Header.addGeometryType(builder, headerMeta.geometryType); +// Header.addIndexNodeSize(builder, headerMeta.indexNodeSize); +// Header.addColumns(builder, columnsOffset); +// Header.addEnvelope(builder, envelopeOffset); +// Header.addName(builder, nameOffset); +// Header.addCrs(builder, crsOffset); +// Header.addFeaturesCount(builder, headerMeta.featuresCount); +// int offset = Header.endHeader(builder); +// +// builder.finishSizePrefixed(offset); +// +// WritableByteChannel channel = Channels.newChannel(to); +// ByteBuffer dataBuffer = builder.dataBuffer(); +// while (dataBuffer.hasRemaining()) { +// channel.write(dataBuffer); +// } +// } +// } diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/GeometryConversions.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/GeometryConversions.java new file mode 100644 index 00000000..359b9e79 --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/GeometryConversions.java @@ -0,0 +1,353 @@ +/* + * 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.flatgeobuf; + +import com.google.flatbuffers.FlatBufferBuilder; +import java.io.IOException; +import java.util.Arrays; +import java.util.function.IntFunction; +import java.util.function.Supplier; +import org.apache.baremaps.flatgeobuf.generated.Geometry; +import org.apache.baremaps.flatgeobuf.generated.GeometryType; +import org.locationtech.jts.geom.*; + +public class GeometryConversions { + + public static GeometryOffsets writeGeometryPart( + FlatBufferBuilder builder, + org.locationtech.jts.geom.Geometry geometry, + int geometryType) throws IOException { + + GeometryOffsets go = new GeometryOffsets(); + + if (geometry == null) { + return go; + } + + if (geometryType == GeometryType.MultiLineString) { + int end = 0; + MultiLineString mls = (MultiLineString) geometry; + if (mls.getNumGeometries() > 1) { + go.ends = new long[mls.getNumGeometries()]; + for (int i = 0; i < mls.getNumGeometries(); i++) { + go.ends[i] = end += mls.getGeometryN(i).getNumPoints(); + } + } + } else if (geometryType == GeometryType.Polygon) { + Polygon p = (Polygon) geometry; + go.ends = new long[p.getNumInteriorRing() + 1]; + int end = p.getExteriorRing().getNumPoints(); + go.ends[0] = end; + for (int i = 0; i < p.getNumInteriorRing(); i++) { + go.ends[i + 1] = end += p.getInteriorRingN(i).getNumPoints(); + } + } else if (geometryType == GeometryType.MultiPolygon) { + MultiPolygon mp = (MultiPolygon) geometry; + int numGeometries = mp.getNumGeometries(); + GeometryOffsets[] gos = new GeometryOffsets[numGeometries]; + for (int i = 0; i < numGeometries; i++) { + Polygon p = (Polygon) mp.getGeometryN(i); + gos[i] = writeGeometryPart(builder, p, GeometryType.Polygon); + } + go.gos = gos; + return go; + } + + final int numPoints = geometry.getNumPoints(); + // build the vector "manually", using a CoordinateSequenceFilter to avoid creating + // Coordinate arrays or any Coordinate at all, depending on the underlying + // CoordinateSequence implementation. Vector elements ought to be added in reverse order + Geometry.startXyVector(builder, 2 * numPoints); + ReverseXYCoordinateSequenceFilter filter = new ReverseXYCoordinateSequenceFilter(builder); + applyInReverseOrder(geometry, filter); + go.coordsOffset = builder.endVector(); + + if (filter.hasZ) { + Geometry.startZVector(builder, numPoints); + applyInReverseOrder(geometry, new OrdinateCoordinateSequenceFilter(builder, 2)); + go.zOffset = builder.endVector(); + } else { + go.zOffset = 0; + } + + if (filter.hasM) { + Geometry.startMVector(builder, numPoints); + applyInReverseOrder(geometry, new OrdinateCoordinateSequenceFilter(builder, 3)); + go.mOffset = builder.endVector(); + } else { + go.mOffset = 0; + } + + if (go.ends != null) { + go.endsOffset = Geometry.createEndsVector(builder, go.ends); + } + + go.type = geometryType; + + return go; + } + + public static int writeGeometry(FlatBufferBuilder builder, + org.locationtech.jts.geom.Geometry geometry, byte geometryType) throws IOException { + byte knownGeometryType = geometryType; + if (geometryType == GeometryType.Unknown) { + knownGeometryType = GeometryConversions.toGeometryType(geometry.getClass()); + } + + GeometryOffsets go = + GeometryConversions.writeGeometryPart(builder, geometry, knownGeometryType); + + int geometryOffset; + if (go.gos != null && go.gos.length > 0) { + int[] partOffsets = new int[go.gos.length]; + for (int i = 0; i < go.gos.length; i++) { + GeometryOffsets goPart = go.gos[i]; + int partOffset = Geometry.createGeometry(builder, goPart.endsOffset, + goPart.coordsOffset, goPart.zOffset, goPart.mOffset, 0, 0, goPart.type, 0); + partOffsets[i] = partOffset; + } + int partsOffset = Geometry.createPartsVector(builder, partOffsets); + geometryOffset = Geometry.createGeometry(builder, 0, 0, 0, 0, 0, 0, + geometryType == GeometryType.Unknown ? knownGeometryType : 0, partsOffset); + } else { + geometryOffset = + Geometry.createGeometry(builder, go.endsOffset, go.coordsOffset, go.zOffset, go.mOffset, + 0, 0, geometryType == GeometryType.Unknown ? knownGeometryType : 0, 0); + } + return geometryOffset; + } + + /** + * Applies the {@code filter} to all {@link org.locationtech.jts.geom.Geometry#getGeometryN(int) + * subgeometries} in reverse order if it's a {@link GeometryCollection} or a {@link Polygon} (i.e. + * interior rings in reverse order first) + */ + private static void applyInReverseOrder(org.locationtech.jts.geom.Geometry geometry, + CoordinateSequenceFilter filter) { + + final int numGeometries = geometry.getNumGeometries(); + if (numGeometries > 1) { + for (int i = numGeometries - 1; i >= 0; i--) { + org.locationtech.jts.geom.Geometry sub = geometry.getGeometryN(i); + applyInReverseOrder(sub, filter); + } + } else if (geometry instanceof Polygon) { + Polygon p = (Polygon) geometry; + for (int i = p.getNumInteriorRing() - 1; i >= 0; i--) { + org.locationtech.jts.geom.Geometry hole = p.getInteriorRingN(i); + applyInReverseOrder(hole, filter); + } + applyInReverseOrder(p.getExteriorRing(), filter); + } else { + geometry.apply(filter); + } + } + + + private static class OrdinateCoordinateSequenceFilter implements CoordinateSequenceFilter { + private FlatBufferBuilder builder; + private final int ordinateIndex; + + OrdinateCoordinateSequenceFilter(FlatBufferBuilder builder, int ordinateIndex) { + this.builder = builder; + this.ordinateIndex = ordinateIndex; + } + + public @Override void filter(final CoordinateSequence seq, final int coordIndex) { + int reverseSeqIndex = seq.size() - coordIndex - 1; + builder.addDouble(seq.getOrdinate(reverseSeqIndex, ordinateIndex)); + } + + public @Override boolean isGeometryChanged() { + return false; + } + + public @Override boolean isDone() { + return false; + } + } + + private static class ReverseXYCoordinateSequenceFilter implements CoordinateSequenceFilter { + private FlatBufferBuilder builder; + boolean hasZ = false; + boolean hasM = false; + + ReverseXYCoordinateSequenceFilter(FlatBufferBuilder builder) { + this.builder = builder; + } + + public @Override void filter(final CoordinateSequence seq, final int coordIndex) { + int reverseSeqIndex = seq.size() - coordIndex - 1; + double y = seq.getOrdinate(reverseSeqIndex, 1); + double x = seq.getOrdinate(reverseSeqIndex, 0); + builder.addDouble(y); + builder.addDouble(x); + if (!hasZ && seq.hasZ()) { + hasZ = true; + } + if (!hasM && seq.hasM()) { + hasM = true; + } + } + + public boolean isHasZ() { + return hasZ; + } + + public boolean isHasM() { + return hasM; + } + + public @Override boolean isGeometryChanged() { + return false; + } + + public @Override boolean isDone() { + return false; + } + } + + public static org.locationtech.jts.geom.Geometry readGeometry(Geometry geometry, + int geometryType) { + GeometryFactory factory = new GeometryFactory(); + + if (geometryType == GeometryType.MultiPolygon) { + int partsLength = geometry.partsLength(); + Polygon[] polygons = new Polygon[partsLength]; + for (int i = 0; i < geometry.partsLength(); i++) { + polygons[i] = (Polygon) readGeometry(geometry.parts(i), GeometryType.Polygon); + } + return factory.createMultiPolygon(polygons); + } + + int xyLength = geometry.xyLength(); + + Coordinate[] coordinates = new Coordinate[xyLength >> 1]; + + int c = 0; + for (int i = 0; i < xyLength; i = i + 2) { + if (c < geometry.mLength()) { + coordinates[c++] = new CoordinateXYZM(geometry.xy(i), geometry.xy(i + 1), + (i >> 1) < geometry.zLength() ? geometry.z((i >> 1)) : Coordinate.NULL_ORDINATE, + (i >> 1) < geometry.mLength() ? geometry.m((i >> 1)) : Coordinate.NULL_ORDINATE); + } else { + coordinates[c++] = new Coordinate(geometry.xy(i), geometry.xy(i + 1), + (i >> 1) < geometry.zLength() ? geometry.z((i >> 1)) : Coordinate.NULL_ORDINATE); + } + } + + IntFunction<Polygon> makePolygonWithRings = (int endsLength) -> { + LinearRing[] lrs = new LinearRing[endsLength]; + int s = 0; + for (int i = 0; i < endsLength; i++) { + int e = (int) geometry.ends(i); + Coordinate[] cs = Arrays.copyOfRange(coordinates, s, e); + lrs[i] = factory.createLinearRing(cs); + s = e; + } + LinearRing shell = lrs[0]; + LinearRing holes[] = Arrays.copyOfRange(lrs, 1, endsLength); + return factory.createPolygon(shell, holes); + }; + + Supplier<Polygon> makePolygon = () -> { + int endsLength = geometry.endsLength(); + if (endsLength > 1) { + return makePolygonWithRings.apply(endsLength); + } else { + return factory.createPolygon(coordinates); + } + }; + + switch (geometryType) { + case GeometryType.Unknown: + return null; + case GeometryType.Point: + if (coordinates.length > 0) { + return factory.createPoint(coordinates[0]); + } else { + return factory.createPoint(); + } + case GeometryType.MultiPoint: + return factory.createMultiPointFromCoords(coordinates); + case GeometryType.LineString: + return factory.createLineString(coordinates); + case GeometryType.MultiLineString: { + int lengthLengths = geometry.endsLength(); + if (lengthLengths < 2) { + return factory + .createMultiLineString(new LineString[] {factory.createLineString(coordinates)}); + } + LineString[] lss = new LineString[lengthLengths]; + int s = 0; + for (int i = 0; i < lengthLengths; i++) { + int e = (int) geometry.ends(i); + Coordinate[] cs = Arrays.copyOfRange(coordinates, s, e); + lss[i] = factory.createLineString(cs); + s = e; + } + return factory.createMultiLineString(lss); + } + case GeometryType.Polygon: + return makePolygon.get(); + default: + throw new RuntimeException("Unknown geometry type"); + } + } + + public static Class<?> getGeometryClass(int geometryType) { + switch (geometryType) { + case GeometryType.Unknown: + return Geometry.class; + case GeometryType.Point: + return Point.class; + case GeometryType.MultiPoint: + return MultiPoint.class; + case GeometryType.LineString: + return LineString.class; + case GeometryType.MultiLineString: + return MultiLineString.class; + case GeometryType.Polygon: + return Polygon.class; + case GeometryType.MultiPolygon: + return MultiPolygon.class; + default: + throw new RuntimeException("Unknown geometry type"); + } + } + + public static byte toGeometryType(Class<?> geometryClass) { + if (geometryClass == org.locationtech.jts.geom.Geometry.class) { + return GeometryType.Unknown; + } else if (MultiPoint.class.isAssignableFrom(geometryClass)) { + return GeometryType.MultiPoint; + } else if (Point.class.isAssignableFrom(geometryClass)) { + return GeometryType.Point; + } else if (MultiLineString.class.isAssignableFrom(geometryClass)) { + return GeometryType.MultiLineString; + } else if (LineString.class.isAssignableFrom(geometryClass)) { + return GeometryType.LineString; + } else if (MultiPolygon.class.isAssignableFrom(geometryClass)) { + return GeometryType.MultiPolygon; + } else if (Polygon.class.isAssignableFrom(geometryClass)) { + return GeometryType.Polygon; + } else { + throw new RuntimeException("Unknown geometry type"); + } + } +} diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/GeometryOffsets.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/GeometryOffsets.java new file mode 100644 index 00000000..c4c22a43 --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/GeometryOffsets.java @@ -0,0 +1,30 @@ +/* + * 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.flatgeobuf; + +public class GeometryOffsets { + public int coordsOffset; + public int zOffset; + public int mOffset; + public long[] ends = null; + public int[] lengths = null; + public int endsOffset = 0; + public int lengthsOffset = 0; + public int type = 0; + public GeometryOffsets[] gos = null; +} diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/NodeItem.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/NodeItem.java new file mode 100644 index 00000000..c55a5c51 --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/NodeItem.java @@ -0,0 +1,82 @@ +/* + * 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.flatgeobuf; + +import org.locationtech.jts.geom.Envelope; + +public record NodeItem( + double minX, + double minY, + double maxX, + double maxY, + long offset) { + + public NodeItem(double minX, double minY, double maxX, double maxY) { + this(minX, minY, maxX, maxY, 0); + } + + public NodeItem(long offset) { + this( + Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY, + Double.NEGATIVE_INFINITY, + Double.NEGATIVE_INFINITY, + offset); + } + + public double width() { + return maxX - minX; + } + + public double height() { + return maxY - minY; + } + + public static NodeItem sum(NodeItem a, final NodeItem b) { + return a.expand(b); + } + + public NodeItem expand(final NodeItem nodeItem) { + return new NodeItem( + Math.min(nodeItem.minX, minX), + Math.min(nodeItem.minY, minY), + Math.max(nodeItem.maxX, maxX), + Math.max(nodeItem.maxY, maxY), + offset); + } + + public boolean intersects(NodeItem nodeItem) { + if (nodeItem.minX > maxX) { + return false; + } + if (nodeItem.minY > maxY) { + return false; + } + if (nodeItem.maxX < minX) { + return false; + } + if (nodeItem.maxY < minY) { + return false; + } + return true; + } + + public Envelope toEnvelope() { + return new Envelope(minX, maxX, minY, maxY); + } +} diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/PackedRTree.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/PackedRTree.java new file mode 100644 index 00000000..14f8fb56 --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/PackedRTree.java @@ -0,0 +1,443 @@ +/* + * 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.flatgeobuf; + +import com.google.common.io.LittleEndianDataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.*; +import org.apache.baremaps.flatgeobuf.generated.Header; +import org.locationtech.jts.geom.Envelope; + +public class PackedRTree { + + private static int NODE_ITEM_LEN = 8 * 4 + 8; + final static int HILBERT_MAX = (1 << 16) - 1; + private int numItems; + private int nodeSize; + public NodeItem[] nodeItems; + private long numNodes; + private List<Pair<Integer, Integer>> levelBounds; + + public PackedRTree(final List<? extends Item> items, final short nodeSize) { + this.numItems = items.size(); + init(nodeSize); + int k = (int) (this.numNodes - (long) this.numItems); + Iterator<? extends Item> it = items.iterator(); + for (int i = 0; i < this.numItems; ++i) { + this.nodeItems[k++] = it.next().nodeItem; + } + generateNodes(); + } + + public void init(int nodeSize) { + if (nodeSize < 2) + throw new RuntimeException("Node size must be at least 2"); + if (numItems == 0) + throw new RuntimeException("Number of items must be greater than 0"); + this.nodeSize = Math.min(Math.max(2, nodeSize), HILBERT_MAX); + this.levelBounds = generateLevelBounds(numItems, this.nodeSize); + this.numNodes = levelBounds.get(0).second; + this.nodeItems = new NodeItem[Math.toIntExact(numNodes)]; + } + + void generateNodes() { + long pos; + long end = 0; + for (short i = 0; i < levelBounds.size() - 1; i++) { + pos = levelBounds.get(i).first; + end = levelBounds.get(i).second; + long newpos = levelBounds.get(i + 1).first; + while (pos < end) { + NodeItem node = new NodeItem(pos); + for (short j = 0; j < this.nodeSize && pos < end; j++) + node.expand(nodeItems[(int) pos++]); + nodeItems[(int) newpos++] = node; + } + } + } + + public static List<? extends Item> hilbertSort(List<? extends Item> items, NodeItem extent) { + double minX = extent.minX(); + double minY = extent.minY(); + double width = extent.width(); + double height = extent.height(); + Collections.sort(items, (a, b) -> { + long ha = hibert(a.nodeItem, HILBERT_MAX, minX, minY, width, height); + long hb = hibert(b.nodeItem, HILBERT_MAX, minX, minY, width, height); + return (ha - hb) > 0 ? 1 : (ha - hb) == 0 ? 0 : -1; + }); + return items; + } + + public static long hibert(NodeItem nodeItem, int hilbertMax, double minX, double minY, + double width, double height) { + long x = 0; + long y = 0; + if (width != 0.0) + x = (long) Math.floor(hilbertMax * ((nodeItem.minX() + nodeItem.maxX()) / 2 - minX) / width); + if (height != 0.0) + y = (long) Math.floor(hilbertMax * ((nodeItem.minY() + nodeItem.maxY()) / 2 - minY) / height); + return hibert(x, y); + } + + // Based on public domain code at https://github.com/rawrunprotected/hilbert_curves + private static long hibert(long x, long y) { + long a = x ^ y; + long b = 0xFFFF ^ a; + long c = 0xFFFF ^ (x | y); + long d = x & (y ^ 0xFFFF); + long A = a | (b >> 1); + long B = (a >> 1) ^ a; + long C = ((c >> 1) ^ (b & (d >> 1))) ^ c; + long D = ((a & (c >> 1)) ^ (d >> 1)) ^ d; + + a = A; + b = B; + c = C; + d = D; + A = ((a & (a >> 2)) ^ (b & (b >> 2))); + B = ((a & (b >> 2)) ^ (b & ((a ^ b) >> 2))); + C ^= ((a & (c >> 2)) ^ (b & (d >> 2))); + D ^= ((b & (c >> 2)) ^ ((a ^ b) & (d >> 2))); + + a = A; + b = B; + c = C; + d = D; + A = ((a & (a >> 4)) ^ (b & (b >> 4))); + B = ((a & (b >> 4)) ^ (b & ((a ^ b) >> 4))); + C ^= ((a & (c >> 4)) ^ (b & (d >> 4))); + D ^= ((b & (c >> 4)) ^ ((a ^ b) & (d >> 4))); + + a = A; + b = B; + c = C; + d = D; + C ^= ((a & (c >> 8)) ^ (b & (d >> 8))); + D ^= ((b & (c >> 8)) ^ ((a ^ b) & (d >> 8))); + + a = C ^ (C >> 1); + b = D ^ (D >> 1); + + long i0 = x ^ y; + long i1 = b | (0xFFFF ^ (i0 | a)); + + i0 = (i0 | (i0 << 8)) & 0x00FF00FF; + i0 = (i0 | (i0 << 4)) & 0x0F0F0F0F; + i0 = (i0 | (i0 << 2)) & 0x33333333; + i0 = (i0 | (i0 << 1)) & 0x55555555; + + i1 = (i1 | (i1 << 8)) & 0x00FF00FF; + i1 = (i1 | (i1 << 4)) & 0x0F0F0F0F; + i1 = (i1 | (i1 << 2)) & 0x33333333; + i1 = (i1 | (i1 << 1)) & 0x55555555; + + long value = ((i1 << 1) | i0); + + return value; + } + + public static NodeItem calcExtent(List<? extends Item> items) { + return items.stream().map(item -> item.nodeItem).reduce(new NodeItem(0), + (nodeItem, nodeItem2) -> nodeItem.expand(nodeItem2)); + } + + public void write(OutputStream outputStream) { + // nodeItem 40 Byte + ByteBuffer buffer = ByteBuffer.allocate((int) (NODE_ITEM_LEN * numNodes)); + buffer.order(ByteOrder.LITTLE_ENDIAN); + for (NodeItem nodeItem : nodeItems) { + buffer.putDouble(nodeItem.minX()); + buffer.putDouble(nodeItem.minY()); + buffer.putDouble(nodeItem.maxX()); + buffer.putDouble(nodeItem.maxY()); + buffer.putLong(nodeItem.offset()); + } + buffer.flip(); + try { + if (buffer.hasRemaining()) { + byte[] arr = new byte[buffer.remaining()]; + buffer.get(arr); + outputStream.write(arr); + outputStream.flush(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + buffer.clear(); + buffer = null; + } + } + + public static long calcSize(long numItems, int nodeSize) { + if (nodeSize < 2) + throw new RuntimeException("Node size must be at least 2"); + if (numItems == 0) + throw new RuntimeException("Number of items must be greater than 0"); + int nodeSizeMin = Math.min(Math.max(nodeSize, 2), 65535); + // limit so that resulting size in bytes can be represented by ulong + if (numItems > 1 << 56) + throw new IndexOutOfBoundsException("Number of items must be less than 2^56"); + long n = numItems; + long numNodes = n; + do { + n = (n + nodeSizeMin - 1) / nodeSizeMin; + numNodes += n; + } while (n != 1); + return numNodes * NODE_ITEM_LEN; + } + + static List<Pair<Integer, Integer>> generateLevelBounds(int numItems, int nodeSize) { + if (nodeSize < 2) + throw new RuntimeException("Node size must be at least 2"); + if (numItems == 0) + throw new RuntimeException("Number of items must be greater than 0"); + + // number of nodes per level in bottom-up order + int n = numItems; + int numNodes = n; + ArrayList<Integer> levelNumNodes = new ArrayList<Integer>(); + levelNumNodes.add(n); + do { + n = (n + nodeSize - 1) / nodeSize; + numNodes += n; + levelNumNodes.add(n); + } while (n != 1); + + // offsets per level in reversed storage order (top-down) + ArrayList<Integer> levelOffsets = new ArrayList<Integer>(); + n = numNodes; + for (int size : levelNumNodes) + levelOffsets.add(n -= size); + List<Pair<Integer, Integer>> levelBounds = new LinkedList<>(); + // bounds per level in reversed storage order (top-down) + for (int i = 0; i < levelNumNodes.size(); i++) + levelBounds.add(new Pair<>(levelOffsets.get(i), levelOffsets.get(i) + levelNumNodes.get(i))); + return levelBounds; + } + + private static class QueueItem { + public QueueItem(long nodeIndex, int level) { + this.nodeIndex = nodeIndex; + this.level = level; + } + + long nodeIndex; + int level; + } + + public static class SearchHit { + public SearchHit(long offset, long index) { + this.offset = offset; + this.index = index; + } + + public long offset; + public long index; + } + + public static ArrayList<SearchHit> search(ByteBuffer bb, int start, int numItems, int nodeSize, + Envelope rect) { + ArrayList<SearchHit> searchHits = new ArrayList<SearchHit>(); + double minX = rect.getMinX(); + double minY = rect.getMinY(); + double maxX = rect.getMaxX(); + double maxY = rect.getMaxY(); + List<Pair<Integer, Integer>> levelBounds = generateLevelBounds(numItems, nodeSize); + int leafNodesOffset = levelBounds.get(0).first; + int numNodes = levelBounds.get(0).second; + Deque<QueueItem> queue = new LinkedList<QueueItem>(); + queue.add(new QueueItem(0, levelBounds.size() - 1)); + while (queue.size() != 0) { + QueueItem stackItem = queue.pop(); + int nodeIndex = (int) stackItem.nodeIndex; + int level = stackItem.level; + boolean isLeafNode = nodeIndex >= numNodes - numItems; + // find the end index of the node + int levelEnd = levelBounds.get(level).second; + int end = Math.min(nodeIndex + nodeSize, levelEnd); + int nodeStart = start + (nodeIndex * NODE_ITEM_LEN); + // int length = end - nodeIndex; + // search through child nodes + for (int pos = nodeIndex; pos < end; pos++) { + int offset = nodeStart + ((pos - nodeIndex) * NODE_ITEM_LEN); + double nodeMinX = bb.getDouble(offset + 0); + double nodeMinY = bb.getDouble(offset + 8); + double nodeMaxX = bb.getDouble(offset + 16); + double nodeMaxY = bb.getDouble(offset + 24); + if (maxX < nodeMinX) + continue; + if (maxY < nodeMinY) + continue; + if (minX > nodeMaxX) + continue; + if (minY > nodeMaxY) + continue; + long indexOffset = bb.getLong(offset + 32); + if (isLeafNode) + searchHits.add(new SearchHit(indexOffset, pos - leafNodesOffset)); + else + queue.add(new QueueItem(indexOffset, level - 1)); + } + } + return searchHits; + } + + public static class SearchResult { + public ArrayList<SearchHit> hits = new ArrayList<SearchHit>(); + public int pos; + } + + public static SearchResult search(InputStream stream, int start, int numItems, int nodeSize, + Envelope rect) throws IOException { + LittleEndianDataInputStream data = new LittleEndianDataInputStream(stream); + int dataPos = 0; + int skip; + SearchResult searchResult = new SearchResult(); + double minX = rect.getMinX(); + double minY = rect.getMinY(); + double maxX = rect.getMaxX(); + double maxY = rect.getMaxY(); + List<Pair<Integer, Integer>> levelBounds = generateLevelBounds(numItems, nodeSize); + int leafNodesOffset = levelBounds.get(0).first; + int numNodes = levelBounds.get(0).second; + Deque<QueueItem> queue = new LinkedList<QueueItem>(); + queue.add(new QueueItem(0, levelBounds.size() - 1)); + while (queue.size() != 0) { + QueueItem stackItem = queue.pop(); + int nodeIndex = (int) stackItem.nodeIndex; + int level = stackItem.level; + boolean isLeafNode = nodeIndex >= numNodes - numItems; + // find the end index of the node + int levelBound = levelBounds.get(level).second; + int end = Math.min(nodeIndex + nodeSize, levelBound); + int nodeStart = nodeIndex * NODE_ITEM_LEN; + skip = nodeStart - dataPos; + if (skip > 0) { + skipNBytes(data, skip); + dataPos += skip; + } + // int length = end - nodeIndex; + // search through child nodes + for (int pos = nodeIndex; pos < end; pos++) { + int offset = nodeStart + ((pos - nodeIndex) * NODE_ITEM_LEN); + skip = offset - dataPos; + if (skip > 0) { + skipNBytes(data, skip); + dataPos += skip; + } + double nodeMinX = data.readDouble(); + dataPos += 8; + if (maxX < nodeMinX) + continue; + double nodeMinY = data.readDouble(); + dataPos += 8; + if (maxY < nodeMinY) + continue; + double nodeMaxX = data.readDouble(); + dataPos += 8; + if (minX > nodeMaxX) + continue; + double nodeMaxY = data.readDouble(); + dataPos += 8; + if (minY > nodeMaxY) + continue; + long indexOffset = data.readLong(); + dataPos += 8; + if (isLeafNode) + searchResult.hits.add(new SearchHit(indexOffset, pos - leafNodesOffset)); + else + queue.add(new QueueItem(indexOffset, level - 1)); + } + } + searchResult.pos = dataPos; + return searchResult; + } + + public static long[] readFeatureOffsets( + LittleEndianDataInputStream data, long[] fids, Header header) + throws IOException { + + long treeSize = calcSize((int) header.featuresCount(), header.indexNodeSize()); + List<Pair<Integer, Integer>> levelBounds = + generateLevelBounds((int) header.featuresCount(), header.indexNodeSize()); + long bottomLevelOffset = levelBounds.get(0).first * 40; + + long pos = 0; + long[] featureOffsets = new long[fids.length]; + for (int i = 0; i < fids.length; i++) { + if (fids[i] > header.featuresCount() - 1) + throw new NoSuchElementException(); + long nodeItemOffset = bottomLevelOffset + (fids[i] * 40); + long delta = nodeItemOffset + (8 * 4) - pos; + skipNBytes(data, delta); + long featureOffset = data.readLong(); + pos += delta + 8; + featureOffsets[i] = featureOffset; + } + long remainingIndexOffset = treeSize - pos; + skipNBytes(data, remainingIndexOffset); + + return featureOffsets; + } + + static void skipNBytes(InputStream stream, long skip) throws IOException { + long remaining = skip; + while (remaining > 0) { + remaining -= stream.skip(remaining); + } + } + + public static class Item { + public NodeItem nodeItem; + } + + public static class FeatureItem extends Item { + public long size; + public long offset; + } + + static class Pair<T, U> { + public T first; + public U second; + + public Pair(T first, U second) { + this.first = first; + this.second = second; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Pair<?, ?> pair = (Pair<?, ?>) o; + return Objects.equals(first, pair.first) && Objects.equals(second, pair.second); + } + + @Override + public int hashCode() { + return Objects.hash(first, second); + } + } +} diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/Column.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/Column.java new file mode 100644 index 00000000..02aaf9e2 --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/Column.java @@ -0,0 +1,233 @@ +/* + * 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.flatgeobuf.generated; + +import com.google.flatbuffers.BaseVector; +import com.google.flatbuffers.Constants; +import com.google.flatbuffers.FlatBufferBuilder; +import com.google.flatbuffers.Table; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@SuppressWarnings("unused") +public final class Column extends Table { + public static void ValidateVersion() { + Constants.FLATBUFFERS_24_3_25(); + } + + public static Column getRootAsColumn(ByteBuffer _bb) { + return getRootAsColumn(_bb, new Column()); + } + + public static Column getRootAsColumn(ByteBuffer _bb, Column obj) { + _bb.order(ByteOrder.LITTLE_ENDIAN); + return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); + } + + public void __init(int _i, ByteBuffer _bb) { + __reset(_i, _bb); + } + + public Column __assign(int _i, ByteBuffer _bb) { + __init(_i, _bb); + return this; + } + + public String name() { + int o = __offset(4); + return o != 0 ? __string(o + bb_pos) : null; + } + + public ByteBuffer nameAsByteBuffer() { + return __vector_as_bytebuffer(4, 1); + } + + public ByteBuffer nameInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 4, 1); + } + + public int type() { + int o = __offset(6); + return o != 0 ? bb.get(o + bb_pos) & 0xFF : 0; + } + + public String title() { + int o = __offset(8); + return o != 0 ? __string(o + bb_pos) : null; + } + + public ByteBuffer titleAsByteBuffer() { + return __vector_as_bytebuffer(8, 1); + } + + public ByteBuffer titleInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 8, 1); + } + + public String description() { + int o = __offset(10); + return o != 0 ? __string(o + bb_pos) : null; + } + + public ByteBuffer descriptionAsByteBuffer() { + return __vector_as_bytebuffer(10, 1); + } + + public ByteBuffer descriptionInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 10, 1); + } + + public int width() { + int o = __offset(12); + return o != 0 ? bb.getInt(o + bb_pos) : -1; + } + + public int precision() { + int o = __offset(14); + return o != 0 ? bb.getInt(o + bb_pos) : -1; + } + + public int scale() { + int o = __offset(16); + return o != 0 ? bb.getInt(o + bb_pos) : -1; + } + + public boolean nullable() { + int o = __offset(18); + return o != 0 ? 0 != bb.get(o + bb_pos) : true; + } + + public boolean unique() { + int o = __offset(20); + return o != 0 ? 0 != bb.get(o + bb_pos) : false; + } + + public boolean primaryKey() { + int o = __offset(22); + return o != 0 ? 0 != bb.get(o + bb_pos) : false; + } + + public String metadata() { + int o = __offset(24); + return o != 0 ? __string(o + bb_pos) : null; + } + + public ByteBuffer metadataAsByteBuffer() { + return __vector_as_bytebuffer(24, 1); + } + + public ByteBuffer metadataInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 24, 1); + } + + public static int createColumn(FlatBufferBuilder builder, + int nameOffset, + int type, + int titleOffset, + int descriptionOffset, + int width, + int precision, + int scale, + boolean nullable, + boolean unique, + boolean primaryKey, + int metadataOffset) { + builder.startTable(11); + Column.addMetadata(builder, metadataOffset); + Column.addScale(builder, scale); + Column.addPrecision(builder, precision); + Column.addWidth(builder, width); + Column.addDescription(builder, descriptionOffset); + Column.addTitle(builder, titleOffset); + Column.addName(builder, nameOffset); + Column.addPrimaryKey(builder, primaryKey); + Column.addUnique(builder, unique); + Column.addNullable(builder, nullable); + Column.addType(builder, type); + return Column.endColumn(builder); + } + + public static void startColumn(FlatBufferBuilder builder) { + builder.startTable(11); + } + + public static void addName(FlatBufferBuilder builder, int nameOffset) { + builder.addOffset(0, nameOffset, 0); + } + + public static void addType(FlatBufferBuilder builder, int type) { + builder.addByte(1, (byte) type, (byte) 0); + } + + public static void addTitle(FlatBufferBuilder builder, int titleOffset) { + builder.addOffset(2, titleOffset, 0); + } + + public static void addDescription(FlatBufferBuilder builder, int descriptionOffset) { + builder.addOffset(3, descriptionOffset, 0); + } + + public static void addWidth(FlatBufferBuilder builder, int width) { + builder.addInt(4, width, -1); + } + + public static void addPrecision(FlatBufferBuilder builder, int precision) { + builder.addInt(5, precision, -1); + } + + public static void addScale(FlatBufferBuilder builder, int scale) { + builder.addInt(6, scale, -1); + } + + public static void addNullable(FlatBufferBuilder builder, boolean nullable) { + builder.addBoolean(7, nullable, true); + } + + public static void addUnique(FlatBufferBuilder builder, boolean unique) { + builder.addBoolean(8, unique, false); + } + + public static void addPrimaryKey(FlatBufferBuilder builder, boolean primaryKey) { + builder.addBoolean(9, primaryKey, false); + } + + public static void addMetadata(FlatBufferBuilder builder, int metadataOffset) { + builder.addOffset(10, metadataOffset, 0); + } + + public static int endColumn(FlatBufferBuilder builder) { + int o = builder.endTable(); + builder.required(o, 4); // name + return o; + } + + public static final class Vector extends BaseVector { + public Vector __assign(int _vector, int _element_size, ByteBuffer _bb) { + __reset(_vector, _element_size, _bb); + return this; + } + + public Column get(int j) { + return get(new Column(), j); + } + + public Column get(Column obj, int j) { + return obj.__assign(__indirect(__element(j), bb), bb); + } + } +} diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/ColumnType.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/ColumnType.java new file mode 100644 index 00000000..1da21f8e --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/ColumnType.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.baremaps.flatgeobuf.generated; + +@SuppressWarnings("unused") +public final class ColumnType { + private ColumnType() {} + + public static final int Byte = 0; + public static final int UByte = 1; + public static final int Bool = 2; + public static final int Short = 3; + public static final int UShort = 4; + public static final int Int = 5; + public static final int UInt = 6; + public static final int Long = 7; + public static final int ULong = 8; + public static final int Float = 9; + public static final int Double = 10; + public static final int String = 11; + public static final int Json = 12; + public static final int DateTime = 13; + public static final int Binary = 14; + + public static final String[] names = {"Byte", "UByte", "Bool", "Short", "UShort", "Int", "UInt", + "Long", "ULong", "Float", "Double", "String", "Json", "DateTime", "Binary",}; + + public static String name(int e) { + return names[e]; + } +} diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/Crs.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/Crs.java new file mode 100644 index 00000000..291de1dc --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/Crs.java @@ -0,0 +1,185 @@ +/* + * 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.flatgeobuf.generated; + +import com.google.flatbuffers.BaseVector; +import com.google.flatbuffers.Constants; +import com.google.flatbuffers.FlatBufferBuilder; +import com.google.flatbuffers.Table; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@SuppressWarnings("unused") +public final class Crs extends Table { + public static void ValidateVersion() { + Constants.FLATBUFFERS_24_3_25(); + } + + public static Crs getRootAsCrs(ByteBuffer _bb) { + return getRootAsCrs(_bb, new Crs()); + } + + public static Crs getRootAsCrs(ByteBuffer _bb, Crs obj) { + _bb.order(ByteOrder.LITTLE_ENDIAN); + return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); + } + + public void __init(int _i, ByteBuffer _bb) { + __reset(_i, _bb); + } + + public Crs __assign(int _i, ByteBuffer _bb) { + __init(_i, _bb); + return this; + } + + public String org() { + int o = __offset(4); + return o != 0 ? __string(o + bb_pos) : null; + } + + public ByteBuffer orgAsByteBuffer() { + return __vector_as_bytebuffer(4, 1); + } + + public ByteBuffer orgInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 4, 1); + } + + public int code() { + int o = __offset(6); + return o != 0 ? bb.getInt(o + bb_pos) : 0; + } + + public String name() { + int o = __offset(8); + return o != 0 ? __string(o + bb_pos) : null; + } + + public ByteBuffer nameAsByteBuffer() { + return __vector_as_bytebuffer(8, 1); + } + + public ByteBuffer nameInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 8, 1); + } + + public String description() { + int o = __offset(10); + return o != 0 ? __string(o + bb_pos) : null; + } + + public ByteBuffer descriptionAsByteBuffer() { + return __vector_as_bytebuffer(10, 1); + } + + public ByteBuffer descriptionInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 10, 1); + } + + public String wkt() { + int o = __offset(12); + return o != 0 ? __string(o + bb_pos) : null; + } + + public ByteBuffer wktAsByteBuffer() { + return __vector_as_bytebuffer(12, 1); + } + + public ByteBuffer wktInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 12, 1); + } + + public String codeString() { + int o = __offset(14); + return o != 0 ? __string(o + bb_pos) : null; + } + + public ByteBuffer codeStringAsByteBuffer() { + return __vector_as_bytebuffer(14, 1); + } + + public ByteBuffer codeStringInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 14, 1); + } + + public static int createCrs(FlatBufferBuilder builder, + int orgOffset, + int code, + int nameOffset, + int descriptionOffset, + int wktOffset, + int codeStringOffset) { + builder.startTable(6); + Crs.addCodeString(builder, codeStringOffset); + Crs.addWkt(builder, wktOffset); + Crs.addDescription(builder, descriptionOffset); + Crs.addName(builder, nameOffset); + Crs.addCode(builder, code); + Crs.addOrg(builder, orgOffset); + return Crs.endCrs(builder); + } + + public static void startCrs(FlatBufferBuilder builder) { + builder.startTable(6); + } + + public static void addOrg(FlatBufferBuilder builder, int orgOffset) { + builder.addOffset(0, orgOffset, 0); + } + + public static void addCode(FlatBufferBuilder builder, int code) { + builder.addInt(1, code, 0); + } + + public static void addName(FlatBufferBuilder builder, int nameOffset) { + builder.addOffset(2, nameOffset, 0); + } + + public static void addDescription(FlatBufferBuilder builder, int descriptionOffset) { + builder.addOffset(3, descriptionOffset, 0); + } + + public static void addWkt(FlatBufferBuilder builder, int wktOffset) { + builder.addOffset(4, wktOffset, 0); + } + + public static void addCodeString(FlatBufferBuilder builder, int codeStringOffset) { + builder.addOffset(5, codeStringOffset, 0); + } + + public static int endCrs(FlatBufferBuilder builder) { + int o = builder.endTable(); + return o; + } + + public static final class Vector extends BaseVector { + public Vector __assign(int _vector, int _element_size, ByteBuffer _bb) { + __reset(_vector, _element_size, _bb); + return this; + } + + public Crs get(int j) { + return get(new Crs(), j); + } + + public Crs get(Crs obj, int j) { + return obj.__assign(__indirect(__element(j), bb), bb); + } + } +} diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/Feature.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/Feature.java new file mode 100644 index 00000000..0a9ba8f2 --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/Feature.java @@ -0,0 +1,188 @@ +/* + * 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.flatgeobuf.generated; + +import com.google.flatbuffers.BaseVector; +import com.google.flatbuffers.ByteVector; +import com.google.flatbuffers.Constants; +import com.google.flatbuffers.FlatBufferBuilder; +import com.google.flatbuffers.Table; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@SuppressWarnings("unused") +public final class Feature extends Table { + public static void ValidateVersion() { + Constants.FLATBUFFERS_24_3_25(); + } + + public static Feature getRootAsFeature(ByteBuffer _bb) { + return getRootAsFeature(_bb, new Feature()); + } + + public static Feature getRootAsFeature(ByteBuffer _bb, Feature obj) { + _bb.order(ByteOrder.LITTLE_ENDIAN); + return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); + } + + public void __init(int _i, ByteBuffer _bb) { + __reset(_i, _bb); + } + + public Feature __assign(int _i, ByteBuffer _bb) { + __init(_i, _bb); + return this; + } + + public Geometry geometry() { + return geometry(new Geometry()); + } + + public Geometry geometry(Geometry obj) { + int o = __offset(4); + return o != 0 ? obj.__assign(__indirect(o + bb_pos), bb) : null; + } + + public int properties(int j) { + int o = __offset(6); + return o != 0 ? bb.get(__vector(o) + j * 1) & 0xFF : 0; + } + + public int propertiesLength() { + int o = __offset(6); + return o != 0 ? __vector_len(o) : 0; + } + + public ByteVector propertiesVector() { + return propertiesVector(new ByteVector()); + } + + public ByteVector propertiesVector(ByteVector obj) { + int o = __offset(6); + return o != 0 ? obj.__assign(__vector(o), bb) : null; + } + + public ByteBuffer propertiesAsByteBuffer() { + return __vector_as_bytebuffer(6, 1); + } + + public ByteBuffer propertiesInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 6, 1); + } + + public Column columns(int j) { + return columns(new Column(), j); + } + + public Column columns(Column obj, int j) { + int o = __offset(8); + return o != 0 ? obj.__assign(__indirect(__vector(o) + j * 4), bb) : null; + } + + public int columnsLength() { + int o = __offset(8); + return o != 0 ? __vector_len(o) : 0; + } + + public Column.Vector columnsVector() { + return columnsVector(new Column.Vector()); + } + + public Column.Vector columnsVector(Column.Vector obj) { + int o = __offset(8); + return o != 0 ? obj.__assign(__vector(o), 4, bb) : null; + } + + public static int createFeature(FlatBufferBuilder builder, + int geometryOffset, + int propertiesOffset, + int columnsOffset) { + builder.startTable(3); + Feature.addColumns(builder, columnsOffset); + Feature.addProperties(builder, propertiesOffset); + Feature.addGeometry(builder, geometryOffset); + return Feature.endFeature(builder); + } + + public static void startFeature(FlatBufferBuilder builder) { + builder.startTable(3); + } + + public static void addGeometry(FlatBufferBuilder builder, int geometryOffset) { + builder.addOffset(0, geometryOffset, 0); + } + + public static void addProperties(FlatBufferBuilder builder, int propertiesOffset) { + builder.addOffset(1, propertiesOffset, 0); + } + + public static int createPropertiesVector(FlatBufferBuilder builder, byte[] data) { + return builder.createByteVector(data); + } + + public static int createPropertiesVector(FlatBufferBuilder builder, ByteBuffer data) { + return builder.createByteVector(data); + } + + public static void startPropertiesVector(FlatBufferBuilder builder, int numElems) { + builder.startVector(1, numElems, 1); + } + + public static void addColumns(FlatBufferBuilder builder, int columnsOffset) { + builder.addOffset(2, columnsOffset, 0); + } + + public static int createColumnsVector(FlatBufferBuilder builder, int[] data) { + builder.startVector(4, data.length, 4); + for (int i = data.length - 1; i >= 0; i--) + builder.addOffset(data[i]); + return builder.endVector(); + } + + public static void startColumnsVector(FlatBufferBuilder builder, int numElems) { + builder.startVector(4, numElems, 4); + } + + public static int endFeature(FlatBufferBuilder builder) { + int o = builder.endTable(); + return o; + } + + public static void finishFeatureBuffer(FlatBufferBuilder builder, int offset) { + builder.finish(offset); + } + + public static void finishSizePrefixedFeatureBuffer(FlatBufferBuilder builder, int offset) { + builder.finishSizePrefixed(offset); + } + + public static final class Vector extends BaseVector { + public Vector __assign(int _vector, int _element_size, ByteBuffer _bb) { + __reset(_vector, _element_size, _bb); + return this; + } + + public Feature get(int j) { + return get(new Feature(), j); + } + + public Feature get(Feature obj, int j) { + return obj.__assign(__indirect(__element(j), bb), bb); + } + } +} diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/Geometry.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/Geometry.java new file mode 100644 index 00000000..02d93f42 --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/Geometry.java @@ -0,0 +1,397 @@ +/* + * 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.flatgeobuf.generated; + +import com.google.flatbuffers.BaseVector; +import com.google.flatbuffers.Constants; +import com.google.flatbuffers.DoubleVector; +import com.google.flatbuffers.FlatBufferBuilder; +import com.google.flatbuffers.IntVector; +import com.google.flatbuffers.LongVector; +import com.google.flatbuffers.Table; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@SuppressWarnings("unused") +public final class Geometry extends Table { + public static void ValidateVersion() { + Constants.FLATBUFFERS_24_3_25(); + } + + public static Geometry getRootAsGeometry(ByteBuffer _bb) { + return getRootAsGeometry(_bb, new Geometry()); + } + + public static Geometry getRootAsGeometry(ByteBuffer _bb, Geometry obj) { + _bb.order(ByteOrder.LITTLE_ENDIAN); + return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); + } + + public void __init(int _i, ByteBuffer _bb) { + __reset(_i, _bb); + } + + public Geometry __assign(int _i, ByteBuffer _bb) { + __init(_i, _bb); + return this; + } + + public long ends(int j) { + int o = __offset(4); + return o != 0 ? (long) bb.getInt(__vector(o) + j * 4) & 0xFFFFFFFFL : 0; + } + + public int endsLength() { + int o = __offset(4); + return o != 0 ? __vector_len(o) : 0; + } + + public IntVector endsVector() { + return endsVector(new IntVector()); + } + + public IntVector endsVector(IntVector obj) { + int o = __offset(4); + return o != 0 ? obj.__assign(__vector(o), bb) : null; + } + + public ByteBuffer endsAsByteBuffer() { + return __vector_as_bytebuffer(4, 4); + } + + public ByteBuffer endsInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 4, 4); + } + + public double xy(int j) { + int o = __offset(6); + return o != 0 ? bb.getDouble(__vector(o) + j * 8) : 0; + } + + public int xyLength() { + int o = __offset(6); + return o != 0 ? __vector_len(o) : 0; + } + + public DoubleVector xyVector() { + return xyVector(new DoubleVector()); + } + + public DoubleVector xyVector(DoubleVector obj) { + int o = __offset(6); + return o != 0 ? obj.__assign(__vector(o), bb) : null; + } + + public ByteBuffer xyAsByteBuffer() { + return __vector_as_bytebuffer(6, 8); + } + + public ByteBuffer xyInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 6, 8); + } + + public double z(int j) { + int o = __offset(8); + return o != 0 ? bb.getDouble(__vector(o) + j * 8) : 0; + } + + public int zLength() { + int o = __offset(8); + return o != 0 ? __vector_len(o) : 0; + } + + public DoubleVector zVector() { + return zVector(new DoubleVector()); + } + + public DoubleVector zVector(DoubleVector obj) { + int o = __offset(8); + return o != 0 ? obj.__assign(__vector(o), bb) : null; + } + + public ByteBuffer zAsByteBuffer() { + return __vector_as_bytebuffer(8, 8); + } + + public ByteBuffer zInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 8, 8); + } + + public double m(int j) { + int o = __offset(10); + return o != 0 ? bb.getDouble(__vector(o) + j * 8) : 0; + } + + public int mLength() { + int o = __offset(10); + return o != 0 ? __vector_len(o) : 0; + } + + public DoubleVector mVector() { + return mVector(new DoubleVector()); + } + + public DoubleVector mVector(DoubleVector obj) { + int o = __offset(10); + return o != 0 ? obj.__assign(__vector(o), bb) : null; + } + + public ByteBuffer mAsByteBuffer() { + return __vector_as_bytebuffer(10, 8); + } + + public ByteBuffer mInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 10, 8); + } + + public double t(int j) { + int o = __offset(12); + return o != 0 ? bb.getDouble(__vector(o) + j * 8) : 0; + } + + public int tLength() { + int o = __offset(12); + return o != 0 ? __vector_len(o) : 0; + } + + public DoubleVector tVector() { + return tVector(new DoubleVector()); + } + + public DoubleVector tVector(DoubleVector obj) { + int o = __offset(12); + return o != 0 ? obj.__assign(__vector(o), bb) : null; + } + + public ByteBuffer tAsByteBuffer() { + return __vector_as_bytebuffer(12, 8); + } + + public ByteBuffer tInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 12, 8); + } + + public long tm(int j) { + int o = __offset(14); + return o != 0 ? bb.getLong(__vector(o) + j * 8) : 0; + } + + public int tmLength() { + int o = __offset(14); + return o != 0 ? __vector_len(o) : 0; + } + + public LongVector tmVector() { + return tmVector(new LongVector()); + } + + public LongVector tmVector(LongVector obj) { + int o = __offset(14); + return o != 0 ? obj.__assign(__vector(o), bb) : null; + } + + public ByteBuffer tmAsByteBuffer() { + return __vector_as_bytebuffer(14, 8); + } + + public ByteBuffer tmInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 14, 8); + } + + public int type() { + int o = __offset(16); + return o != 0 ? bb.get(o + bb_pos) & 0xFF : 0; + } + + public Geometry parts(int j) { + return parts(new Geometry(), j); + } + + public Geometry parts(Geometry obj, int j) { + int o = __offset(18); + return o != 0 ? obj.__assign(__indirect(__vector(o) + j * 4), bb) : null; + } + + public int partsLength() { + int o = __offset(18); + return o != 0 ? __vector_len(o) : 0; + } + + public Geometry.Vector partsVector() { + return partsVector(new Geometry.Vector()); + } + + public Geometry.Vector partsVector(Geometry.Vector obj) { + int o = __offset(18); + return o != 0 ? obj.__assign(__vector(o), 4, bb) : null; + } + + public static int createGeometry(FlatBufferBuilder builder, + int endsOffset, + int xyOffset, + int zOffset, + int mOffset, + int tOffset, + int tmOffset, + int type, + int partsOffset) { + builder.startTable(8); + Geometry.addParts(builder, partsOffset); + Geometry.addTm(builder, tmOffset); + Geometry.addT(builder, tOffset); + Geometry.addM(builder, mOffset); + Geometry.addZ(builder, zOffset); + Geometry.addXy(builder, xyOffset); + Geometry.addEnds(builder, endsOffset); + Geometry.addType(builder, type); + return Geometry.endGeometry(builder); + } + + public static void startGeometry(FlatBufferBuilder builder) { + builder.startTable(8); + } + + public static void addEnds(FlatBufferBuilder builder, int endsOffset) { + builder.addOffset(0, endsOffset, 0); + } + + public static int createEndsVector(FlatBufferBuilder builder, long[] data) { + builder.startVector(4, data.length, 4); + for (int i = data.length - 1; i >= 0; i--) + builder.addInt((int) data[i]); + return builder.endVector(); + } + + public static void startEndsVector(FlatBufferBuilder builder, int numElems) { + builder.startVector(4, numElems, 4); + } + + public static void addXy(FlatBufferBuilder builder, int xyOffset) { + builder.addOffset(1, xyOffset, 0); + } + + public static int createXyVector(FlatBufferBuilder builder, double[] data) { + builder.startVector(8, data.length, 8); + for (int i = data.length - 1; i >= 0; i--) + builder.addDouble(data[i]); + return builder.endVector(); + } + + public static void startXyVector(FlatBufferBuilder builder, int numElems) { + builder.startVector(8, numElems, 8); + } + + public static void addZ(FlatBufferBuilder builder, int zOffset) { + builder.addOffset(2, zOffset, 0); + } + + public static int createZVector(FlatBufferBuilder builder, double[] data) { + builder.startVector(8, data.length, 8); + for (int i = data.length - 1; i >= 0; i--) + builder.addDouble(data[i]); + return builder.endVector(); + } + + public static void startZVector(FlatBufferBuilder builder, int numElems) { + builder.startVector(8, numElems, 8); + } + + public static void addM(FlatBufferBuilder builder, int mOffset) { + builder.addOffset(3, mOffset, 0); + } + + public static int createMVector(FlatBufferBuilder builder, double[] data) { + builder.startVector(8, data.length, 8); + for (int i = data.length - 1; i >= 0; i--) + builder.addDouble(data[i]); + return builder.endVector(); + } + + public static void startMVector(FlatBufferBuilder builder, int numElems) { + builder.startVector(8, numElems, 8); + } + + public static void addT(FlatBufferBuilder builder, int tOffset) { + builder.addOffset(4, tOffset, 0); + } + + public static int createTVector(FlatBufferBuilder builder, double[] data) { + builder.startVector(8, data.length, 8); + for (int i = data.length - 1; i >= 0; i--) + builder.addDouble(data[i]); + return builder.endVector(); + } + + public static void startTVector(FlatBufferBuilder builder, int numElems) { + builder.startVector(8, numElems, 8); + } + + public static void addTm(FlatBufferBuilder builder, int tmOffset) { + builder.addOffset(5, tmOffset, 0); + } + + public static int createTmVector(FlatBufferBuilder builder, long[] data) { + builder.startVector(8, data.length, 8); + for (int i = data.length - 1; i >= 0; i--) + builder.addLong(data[i]); + return builder.endVector(); + } + + public static void startTmVector(FlatBufferBuilder builder, int numElems) { + builder.startVector(8, numElems, 8); + } + + public static void addType(FlatBufferBuilder builder, int type) { + builder.addByte(6, (byte) type, (byte) 0); + } + + public static void addParts(FlatBufferBuilder builder, int partsOffset) { + builder.addOffset(7, partsOffset, 0); + } + + public static int createPartsVector(FlatBufferBuilder builder, int[] data) { + builder.startVector(4, data.length, 4); + for (int i = data.length - 1; i >= 0; i--) + builder.addOffset(data[i]); + return builder.endVector(); + } + + public static void startPartsVector(FlatBufferBuilder builder, int numElems) { + builder.startVector(4, numElems, 4); + } + + public static int endGeometry(FlatBufferBuilder builder) { + int o = builder.endTable(); + return o; + } + + public static final class Vector extends BaseVector { + public Vector __assign(int _vector, int _element_size, ByteBuffer _bb) { + __reset(_vector, _element_size, _bb); + return this; + } + + public Geometry get(int j) { + return get(new Geometry(), j); + } + + public Geometry get(Geometry obj, int j) { + return obj.__assign(__indirect(__element(j), bb), bb); + } + } +} diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/GeometryType.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/GeometryType.java new file mode 100644 index 00000000..82584c6f --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/GeometryType.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 org.apache.baremaps.flatgeobuf.generated; + +@SuppressWarnings("unused") +public final class GeometryType { + private GeometryType() {} + + public static final int Unknown = 0; + public static final int Point = 1; + public static final int LineString = 2; + public static final int Polygon = 3; + public static final int MultiPoint = 4; + public static final int MultiLineString = 5; + public static final int MultiPolygon = 6; + public static final int GeometryCollection = 7; + public static final int CircularString = 8; + public static final int CompoundCurve = 9; + public static final int CurvePolygon = 10; + public static final int MultiCurve = 11; + public static final int MultiSurface = 12; + public static final int Curve = 13; + public static final int Surface = 14; + public static final int PolyhedralSurface = 15; + public static final int TIN = 16; + public static final int Triangle = 17; + + public static final String[] names = + {"Unknown", "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", "MultiPolygon", + "GeometryCollection", "CircularString", "CompoundCurve", "CurvePolygon", "MultiCurve", + "MultiSurface", "Curve", "Surface", "PolyhedralSurface", "TIN", "Triangle",}; + + public static String name(int e) { + return names[e]; + } +} diff --git a/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/Header.java b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/Header.java new file mode 100644 index 00000000..d4d17ee9 --- /dev/null +++ b/baremaps-flatgeobuf/src/main/java/org/apache/baremaps/flatgeobuf/generated/Header.java @@ -0,0 +1,340 @@ +/* + * 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.flatgeobuf.generated; + +import com.google.flatbuffers.BaseVector; +import com.google.flatbuffers.Constants; +import com.google.flatbuffers.DoubleVector; +import com.google.flatbuffers.FlatBufferBuilder; +import com.google.flatbuffers.Table; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@SuppressWarnings("unused") +public final class Header extends Table { + public static void ValidateVersion() { + Constants.FLATBUFFERS_24_3_25(); + } + + public static Header getRootAsHeader(ByteBuffer _bb) { + return getRootAsHeader(_bb, new Header()); + } + + public static Header getRootAsHeader(ByteBuffer _bb, Header obj) { + _bb.order(ByteOrder.LITTLE_ENDIAN); + return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); + } + + public void __init(int _i, ByteBuffer _bb) { + __reset(_i, _bb); + } + + public Header __assign(int _i, ByteBuffer _bb) { + __init(_i, _bb); + return this; + } + + public String name() { + int o = __offset(4); + return o != 0 ? __string(o + bb_pos) : null; + } + + public ByteBuffer nameAsByteBuffer() { + return __vector_as_bytebuffer(4, 1); + } + + public ByteBuffer nameInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 4, 1); + } + + public double envelope(int j) { + int o = __offset(6); + return o != 0 ? bb.getDouble(__vector(o) + j * 8) : 0; + } + + public int envelopeLength() { + int o = __offset(6); + return o != 0 ? __vector_len(o) : 0; + } + + public DoubleVector envelopeVector() { + return envelopeVector(new DoubleVector()); + } + + public DoubleVector envelopeVector(DoubleVector obj) { + int o = __offset(6); + return o != 0 ? obj.__assign(__vector(o), bb) : null; + } + + public ByteBuffer envelopeAsByteBuffer() { + return __vector_as_bytebuffer(6, 8); + } + + public ByteBuffer envelopeInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 6, 8); + } + + public int geometryType() { + int o = __offset(8); + return o != 0 ? bb.get(o + bb_pos) & 0xFF : 0; + } + + public boolean hasZ() { + int o = __offset(10); + return o != 0 ? 0 != bb.get(o + bb_pos) : false; + } + + public boolean hasM() { + int o = __offset(12); + return o != 0 ? 0 != bb.get(o + bb_pos) : false; + } + + public boolean hasT() { + int o = __offset(14); + return o != 0 ? 0 != bb.get(o + bb_pos) : false; + } + + public boolean hasTm() { + int o = __offset(16); + return o != 0 ? 0 != bb.get(o + bb_pos) : false; + } + + public Column columns(int j) { + return columns(new Column(), j); + } + + public Column columns(Column obj, int j) { + int o = __offset(18); + return o != 0 ? obj.__assign(__indirect(__vector(o) + j * 4), bb) : null; + } + + public int columnsLength() { + int o = __offset(18); + return o != 0 ? __vector_len(o) : 0; + } + + public Column.Vector columnsVector() { + return columnsVector(new Column.Vector()); + } + + public Column.Vector columnsVector(Column.Vector obj) { + int o = __offset(18); + return o != 0 ? obj.__assign(__vector(o), 4, bb) : null; + } + + public long featuresCount() { + int o = __offset(20); + return o != 0 ? bb.getLong(o + bb_pos) : 0L; + } + + public int indexNodeSize() { + int o = __offset(22); + return o != 0 ? bb.getShort(o + bb_pos) & 0xFFFF : 16; + } + + public Crs crs() { + return crs(new Crs()); + } + + public Crs crs(Crs obj) { + int o = __offset(24); + return o != 0 ? obj.__assign(__indirect(o + bb_pos), bb) : null; + } + + public String title() { + int o = __offset(26); + return o != 0 ? __string(o + bb_pos) : null; + } + + public ByteBuffer titleAsByteBuffer() { + return __vector_as_bytebuffer(26, 1); + } + + public ByteBuffer titleInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 26, 1); + } + + public String description() { + int o = __offset(28); + return o != 0 ? __string(o + bb_pos) : null; + } + + public ByteBuffer descriptionAsByteBuffer() { + return __vector_as_bytebuffer(28, 1); + } + + public ByteBuffer descriptionInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 28, 1); + } + + public String metadata() { + int o = __offset(30); + return o != 0 ? __string(o + bb_pos) : null; + } + + public ByteBuffer metadataAsByteBuffer() { + return __vector_as_bytebuffer(30, 1); + } + + public ByteBuffer metadataInByteBuffer(ByteBuffer _bb) { + return __vector_in_bytebuffer(_bb, 30, 1); + } + + public static int createHeader(FlatBufferBuilder builder, + int nameOffset, + int envelopeOffset, + int geometryType, + boolean hasZ, + boolean hasM, + boolean hasT, + boolean hasTm, + int columnsOffset, + long featuresCount, + int indexNodeSize, + int crsOffset, + int titleOffset, + int descriptionOffset, + int metadataOffset) { + builder.startTable(14); + Header.addFeaturesCount(builder, featuresCount); + Header.addMetadata(builder, metadataOffset); + Header.addDescription(builder, descriptionOffset); + Header.addTitle(builder, titleOffset); + Header.addCrs(builder, crsOffset); + Header.addColumns(builder, columnsOffset); + Header.addEnvelope(builder, envelopeOffset); + Header.addName(builder, nameOffset); + Header.addIndexNodeSize(builder, indexNodeSize); + Header.addHasTm(builder, hasTm); + Header.addHasT(builder, hasT); + Header.addHasM(builder, hasM); + Header.addHasZ(builder, hasZ); + Header.addGeometryType(builder, geometryType); + return Header.endHeader(builder); + } + + public static void startHeader(FlatBufferBuilder builder) { + builder.startTable(14); + } + + public static void addName(FlatBufferBuilder builder, int nameOffset) { + builder.addOffset(0, nameOffset, 0); + } + + public static void addEnvelope(FlatBufferBuilder builder, int envelopeOffset) { + builder.addOffset(1, envelopeOffset, 0); + } + + public static int createEnvelopeVector(FlatBufferBuilder builder, double[] data) { + builder.startVector(8, data.length, 8); + for (int i = data.length - 1; i >= 0; i--) + builder.addDouble(data[i]); + return builder.endVector(); + } + + public static void startEnvelopeVector(FlatBufferBuilder builder, int numElems) { + builder.startVector(8, numElems, 8); + } + + public static void addGeometryType(FlatBufferBuilder builder, int geometryType) { + builder.addByte(2, (byte) geometryType, (byte) 0); + } + + public static void addHasZ(FlatBufferBuilder builder, boolean hasZ) { + builder.addBoolean(3, hasZ, false); + } + + public static void addHasM(FlatBufferBuilder builder, boolean hasM) { + builder.addBoolean(4, hasM, false); + } + + public static void addHasT(FlatBufferBuilder builder, boolean hasT) { + builder.addBoolean(5, hasT, false); + } + + public static void addHasTm(FlatBufferBuilder builder, boolean hasTm) { + builder.addBoolean(6, hasTm, false); + } + + public static void addColumns(FlatBufferBuilder builder, int columnsOffset) { + builder.addOffset(7, columnsOffset, 0); + } + + public static int createColumnsVector(FlatBufferBuilder builder, int[] data) { + builder.startVector(4, data.length, 4); + for (int i = data.length - 1; i >= 0; i--) + builder.addOffset(data[i]); + return builder.endVector(); + } + + public static void startColumnsVector(FlatBufferBuilder builder, int numElems) { + builder.startVector(4, numElems, 4); + } + + public static void addFeaturesCount(FlatBufferBuilder builder, long featuresCount) { + builder.addLong(8, featuresCount, 0L); + } + + public static void addIndexNodeSize(FlatBufferBuilder builder, int indexNodeSize) { + builder.addShort(9, (short) indexNodeSize, (short) 16); + } + + public static void addCrs(FlatBufferBuilder builder, int crsOffset) { + builder.addOffset(10, crsOffset, 0); + } + + public static void addTitle(FlatBufferBuilder builder, int titleOffset) { + builder.addOffset(11, titleOffset, 0); + } + + public static void addDescription(FlatBufferBuilder builder, int descriptionOffset) { + builder.addOffset(12, descriptionOffset, 0); + } + + public static void addMetadata(FlatBufferBuilder builder, int metadataOffset) { + builder.addOffset(13, metadataOffset, 0); + } + + public static int endHeader(FlatBufferBuilder builder) { + int o = builder.endTable(); + return o; + } + + public static void finishHeaderBuffer(FlatBufferBuilder builder, int offset) { + builder.finish(offset); + } + + public static void finishSizePrefixedHeaderBuffer(FlatBufferBuilder builder, int offset) { + builder.finishSizePrefixed(offset); + } + + public static final class Vector extends BaseVector { + public Vector __assign(int _vector, int _element_size, ByteBuffer _bb) { + __reset(_vector, _element_size, _bb); + return this; + } + + public Header get(int j) { + return get(new Header(), j); + } + + public Header get(Header obj, int j) { + return obj.__assign(__indirect(__element(j), bb), bb); + } + } +} diff --git a/baremaps-flatgeobuf/src/main/resources/fbs/feature.fbs b/baremaps-flatgeobuf/src/main/resources/fbs/feature.fbs new file mode 100644 index 00000000..b9a874dc --- /dev/null +++ b/baremaps-flatgeobuf/src/main/resources/fbs/feature.fbs @@ -0,0 +1,22 @@ +include "header.fbs"; + +namespace FlatGeobuf; + +table Geometry { + ends: [uint]; // Array of end index in flat coordinates per geometry part with exterior ring first (NOTE: allowed and recommended to be null if 1 part) + xy: [double]; // Flat x and y coordinate array (flat pairs) (should not be null or empty) + z: [double]; // Flat z height array (should not be null or empty if header hasZ) + m: [double]; // Flat m measurement array (should not be null or empty if header hasM) + t: [double]; // Flat t geodetic decimal year time array (should not be null or empty if header hasT) + tm: [ulong]; // Flat tm time nanosecond measurement array (should not be null or empty if header hasTM) + type: GeometryType; // Type of geometry (only for elements in heterogeneous collection types or if unknown in header) + parts: [Geometry]; // Array of parts (for heterogeneous collection types) +} + +table Feature { + geometry: Geometry; // Geometry + properties: [ubyte]; // Custom buffer, variable length collection of key/value pairs (key=ushort) + columns: [Column]; // Attribute columns schema (optional) +} + +root_type Feature; \ No newline at end of file diff --git a/baremaps-flatgeobuf/src/main/resources/fbs/header.fbs b/baremaps-flatgeobuf/src/main/resources/fbs/header.fbs new file mode 100644 index 00000000..b1e716c5 --- /dev/null +++ b/baremaps-flatgeobuf/src/main/resources/fbs/header.fbs @@ -0,0 +1,84 @@ +namespace FlatGeobuf; + +// Geometry type enumeration +// NOTE: Same as WKB 2D geometry type enumeration +enum GeometryType: ubyte { + Unknown = 0, + Point = 1, + LineString = 2, + Polygon = 3, + MultiPoint = 4, + MultiLineString = 5, + MultiPolygon = 6, + GeometryCollection = 7, + CircularString = 8, + CompoundCurve = 9, + CurvePolygon = 10, + MultiCurve = 11, + MultiSurface = 12, + Curve = 13, + Surface = 14, + PolyhedralSurface = 15, + TIN = 16, + Triangle = 17 +} + +enum ColumnType: ubyte { + Byte, // Signed 8-bit integer + UByte, // Unsigned 8-bit integer + Bool, // Boolean + Short, // Signed 16-bit integer + UShort, // Unsigned 16-bit integer + Int, // Signed 32-bit integer + UInt, // Unsigned 32-bit integer + Long, // Signed 64-bit integer + ULong, // Unsigned 64-bit integer + Float, // Single precision floating point number + Double, // Double precision floating point number + String, // UTF8 string + Json, // General JSON type intended to be application specific + DateTime, // ISO 8601 date time + Binary // General binary type intended to be application specific +} + +table Column { + name: string (required); // Column name + type: ColumnType; // Column type + title: string; // Column title + description: string; // Column description (intended for free form long text) + width: int = -1; // Column values expected width (-1 = unknown) (currently only used to indicate the number of characters in strings) + precision: int = -1; // Column values expected precision (-1 = unknown) as defined by SQL + scale: int = -1; // Column values expected scale (-1 = unknown) as defined by SQL + nullable: bool = true; // Column values expected nullability + unique: bool = false; // Column values expected uniqueness + primary_key: bool = false; // Indicates this column has been (part of) a primary key + metadata: string; // Column metadata (intended to be application specific and suggested to be structured fx. JSON) +} + +table Crs { + org: string; // Case-insensitive name of the defining organization e.g. EPSG or epsg (NULL = EPSG) + code: int; // Numeric ID of the Spatial Reference System assigned by the organization (0 = unknown) + name: string; // Human readable name of this SRS + description: string; // Human readable description of this SRS + wkt: string; // Well-known Text Representation of the Spatial Reference System + code_string: string; // Text ID of the Spatial Reference System assigned by the organization in the (rare) case when it is not an integer and thus cannot be set into code +} + +table Header { + name: string; // Dataset name + envelope: [double]; // Bounds + geometry_type: GeometryType; // Geometry type (should be set to Unknown if per feature geometry type) + has_z: bool = false; // Does geometry have Z dimension? + has_m: bool = false; // Does geometry have M dimension? + has_t: bool = false; // Does geometry have T dimension? + has_tm: bool = false; // Does geometry have TM dimension? + columns: [Column]; // Attribute columns schema (can be omitted if per feature schema) + features_count: ulong; // Number of features in the dataset (0 = unknown) + index_node_size: ushort = 16; // Index node size (0 = no index) + crs: Crs; // Spatial Reference System + title: string; // Dataset title + description: string; // Dataset description (intended for free form long text) + metadata: string; // Dataset metadata (intended to be application specific and suggested to be structured fx. JSON) +} + +root_type Header; \ No newline at end of file diff --git a/baremaps-flatgeobuf/src/test/java/org/apache/baremaps/flatgeobuf/BufferUtilTest.java b/baremaps-flatgeobuf/src/test/java/org/apache/baremaps/flatgeobuf/BufferUtilTest.java new file mode 100644 index 00000000..54906543 --- /dev/null +++ b/baremaps-flatgeobuf/src/test/java/org/apache/baremaps/flatgeobuf/BufferUtilTest.java @@ -0,0 +1,127 @@ +/* + * 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.flatgeobuf; + +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import org.junit.jupiter.api.Test; + +public class BufferUtilTest { + + @Test + void testPrepareBufferAlreadySufficient() throws IOException { + byte[] data = "Hello, World!".getBytes(); + ReadableByteChannel channel = Channels.newChannel(new ByteArrayInputStream(data)); + ByteBuffer buffer = ByteBuffer.allocate(15); + buffer.put(data); + buffer.flip(); + + ByteBuffer result = BufferUtil.readBytes(channel, buffer, 5); + assertEquals(buffer, result); + assertEquals(13, result.remaining()); + } + + @Test + void testPrepareBufferCompactAndRead() throws IOException { + byte[] data = "Hello, World!".getBytes(); + ReadableByteChannel channel = Channels.newChannel(new ByteArrayInputStream(data)); + ByteBuffer buffer = ByteBuffer.allocate(15); + buffer.put("Hello".getBytes()); + buffer.flip(); + + ByteBuffer result = BufferUtil.readBytes(channel, buffer, 10); + assertEquals(buffer, result); + assertTrue(result.remaining() >= 10); + } + + @Test + void testPrepareBufferAllocateNewBuffer() throws IOException { + byte[] data = "Hello, World!".getBytes(); + ReadableByteChannel channel = Channels.newChannel(new ByteArrayInputStream(data)); + ByteBuffer buffer = ByteBuffer.allocate(5); + buffer.put("Hi".getBytes()); + buffer.flip(); + + ByteBuffer result = BufferUtil.readBytes(channel, buffer, 10); + assertNotEquals(buffer, result); + assertTrue(result.capacity() >= 10); + assertTrue(result.remaining() >= 10); + } + + @Test + void testPrepareBufferWithExactCapacity() throws IOException { + byte[] data = "Hello, World!".getBytes(); + ReadableByteChannel channel = Channels.newChannel(new ByteArrayInputStream(data)); + ByteBuffer buffer = ByteBuffer.allocate(13); + buffer.put(data, 0, 5); + buffer.flip(); + + ByteBuffer result = BufferUtil.readBytes(channel, buffer, 10); + assertEquals(buffer, result); + assertTrue(result.remaining() >= 10); + } + + @Test + void testPrepareEndOfChannel() throws IOException { + byte[] data = "Hello".getBytes(); + ReadableByteChannel channel = Channels.newChannel(new ByteArrayInputStream(data)); + ByteBuffer buffer = ByteBuffer.allocate(10); + buffer.put("Hi".getBytes()); + buffer.flip(); + + ByteBuffer result = BufferUtil.readBytes(channel, buffer, 10); + assertEquals(buffer, result); + assertTrue(result.remaining() <= 10); + } + + @Test + void testPrepareNullChannel() { + ByteBuffer buffer = ByteBuffer.allocate(10); + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { + BufferUtil.readBytes(null, buffer, 10); + }); + assertEquals("Channel and buffer must not be null", thrown.getMessage()); + } + + @Test + void testPrepareNullBuffer() { + byte[] data = "Hello".getBytes(); + ReadableByteChannel channel = Channels.newChannel(new ByteArrayInputStream(data)); + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { + BufferUtil.readBytes(channel, null, 10); + }); + assertEquals("Channel and buffer must not be null", thrown.getMessage()); + } + + @Test + void testPrepareNegativeBytes() { + byte[] data = "Hello".getBytes(); + ReadableByteChannel channel = Channels.newChannel(new ByteArrayInputStream(data)); + ByteBuffer buffer = ByteBuffer.allocate(10); + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { + BufferUtil.readBytes(channel, buffer, -1); + }); + assertEquals("The number of bytes to read must be non-negative", thrown.getMessage()); + } + +} diff --git a/baremaps-flatgeobuf/src/test/java/org/apache/baremaps/flatgeobuf/FlatGeoBufTest.java b/baremaps-flatgeobuf/src/test/java/org/apache/baremaps/flatgeobuf/FlatGeoBufTest.java new file mode 100644 index 00000000..03401dd5 --- /dev/null +++ b/baremaps-flatgeobuf/src/test/java/org/apache/baremaps/flatgeobuf/FlatGeoBufTest.java @@ -0,0 +1,76 @@ +/* + * 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.flatgeobuf; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; +import java.nio.file.StandardOpenOption; +import org.apache.baremaps.flatgeobuf.generated.Feature; +import org.apache.baremaps.flatgeobuf.generated.Header; +import org.apache.baremaps.testing.TestFiles; +import org.junit.jupiter.api.Test; + +public class FlatGeoBufTest { + + @Test + void readHeader() throws IOException { + var file = TestFiles.resolve("baremaps-testing/data/samples/countries.fgb"); + try (var channel = FileChannel.open(file, StandardOpenOption.READ)) { + Header header = FlatGeoBufReader.readHeader(channel); + assertNotNull(header); + assertEquals(179, header.featuresCount()); + } + } + + @Test + void readFeature() throws IOException { + var file = TestFiles.resolve("baremaps-testing/data/samples/countries.fgb"); + try (var channel = FileChannel.open(file, StandardOpenOption.READ)) { + + // Read the header + Header header = FlatGeoBufReader.readHeader(channel); + assertNotNull(header); + assertEquals(179, header.featuresCount()); + + // Read the index + FlatGeoBufReader.skipIndex(channel, header); + + // Read the first feature + ByteBuffer buffer = BufferUtil.createByteBuffer(1 << 16, ByteOrder.LITTLE_ENDIAN); + for (int i = 0; i < header.featuresCount(); i++) { + Feature feature = FlatGeoBufReader.readFeature(channel, buffer); + + System.out.println(FlatGeoBuf.asFeatureRecord(header, feature)); + + assertNotNull(feature); + } + } + } + + + public static void main(String... args) { + System.out.println(Long.MAX_VALUE); + System.out.println(Long.MAX_VALUE >> 32); + } + +} diff --git a/baremaps-testing/pom.xml b/baremaps-testing/pom.xml index c2578470..ac8075f4 100644 --- a/baremaps-testing/pom.xml +++ b/baremaps-testing/pom.xml @@ -10,4 +10,10 @@ <properties> <maven.deploy.skip>true</maven.deploy.skip> </properties> + <dependencies> + <dependency> + <groupId>org.locationtech.jts</groupId> + <artifactId>jts-core</artifactId> + </dependency> + </dependencies> </project> diff --git a/pom.xml b/pom.xml index eb1e3bd5..4d25e7c4 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,7 @@ limitations under the License. <module>baremaps-cli</module> <module>baremaps-core</module> <module>baremaps-data</module> + <module>baremaps-flatgeobuf</module> <module>baremaps-geoparquet</module> <module>baremaps-maplibre</module> <module>baremaps-openstreetmap</module> @@ -526,10 +527,6 @@ limitations under the License. <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> </dependency> - <dependency> - <groupId>org.wololo</groupId> - <artifactId>flatgeobuf</artifactId> - </dependency> </dependencies> <build> diff --git a/scripts/generate-flatgeobuf.sh b/scripts/generate-flatgeobuf.sh new file mode 100755 index 00000000..ea5317da --- /dev/null +++ b/scripts/generate-flatgeobuf.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +flatc --java --gen-all \ + --java-package-prefix org.apache.baremaps.flatgeobuf.generated \ + -o baremaps-flatgeobuf/src/main/java \ + baremaps-flatgeobuf/src/main/resources/fbs/*.fbs
