Skip to content

Commit 933ca83

Browse files
authored
Add a flatgeobuf submodule (#881)
* Move support for flatgeobuf in a dedicated submodule * Add an API that supports reading and writing flatbuffers and domain objects corresponding to flatbuffers * Make picocli and jackson insensitive with enums
1 parent 8716d53 commit 933ca83

File tree

27 files changed

+3788
-324
lines changed

27 files changed

+3788
-324
lines changed

baremaps-cli/src/license/override.properties

-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ org.postgresql--postgresql--42.7.2=BSD 2-Clause License
142142
org.reactivestreams--reactive-streams--1.0.4=MIT License
143143
org.roaringbitmap--RoaringBitmap--1.0.1=Apache License 2.0
144144
org.slf4j--slf4j-api--2.0.12=MIT License
145-
org.wololo--flatgeobuf--3.26.2=ISC License
146145
org.xerial--sqlite-jdbc--3.45.1.0=The Apache Software License, Version 2.0
147146
org.xerial.thirdparty--nestedvm--1.0=Apache License 2.0
148147
org.yaml--snakeyaml--2.2=Apache License 2.0

baremaps-cli/src/main/java/org/apache/baremaps/cli/Baremaps.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,12 @@ public static void main(String... args) {
6868
}
6969

7070
// Execute the command
71-
CommandLine cmd = new CommandLine(new Baremaps()).setUsageHelpLongOptionsMaxWidth(30)
71+
CommandLine commandLine = new CommandLine(new Baremaps())
72+
.setCaseInsensitiveEnumValuesAllowed(true)
73+
.setUsageHelpLongOptionsMaxWidth(30)
7274
.addMixin("options", new Options());
73-
cmd.execute(args);
75+
76+
commandLine.execute(args);
7477
}
7578

7679
@Override

baremaps-core/pom.xml

+4-4
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ limitations under the License.
8282
<groupId>org.apache.baremaps</groupId>
8383
<artifactId>baremaps-data</artifactId>
8484
</dependency>
85+
<dependency>
86+
<groupId>org.apache.baremaps</groupId>
87+
<artifactId>baremaps-flatgeobuf</artifactId>
88+
</dependency>
8589
<dependency>
8690
<groupId>org.apache.baremaps</groupId>
8791
<artifactId>baremaps-geoparquet</artifactId>
@@ -142,10 +146,6 @@ limitations under the License.
142146
<groupId>org.postgresql</groupId>
143147
<artifactId>postgresql</artifactId>
144148
</dependency>
145-
<dependency>
146-
<groupId>org.wololo</groupId>
147-
<artifactId>flatgeobuf</artifactId>
148-
</dependency>
149149
<dependency>
150150
<groupId>org.xerial</groupId>
151151
<artifactId>sqlite-jdbc</artifactId>

baremaps-core/src/main/java/org/apache/baremaps/storage/flatgeobuf/FlatGeoBufDataTable.java

+73-140
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
package org.apache.baremaps.storage.flatgeobuf;
1919

20-
import com.google.flatbuffers.FlatBufferBuilder;
2120
import java.io.IOException;
2221
import java.nio.ByteBuffer;
2322
import java.nio.ByteOrder;
@@ -26,18 +25,14 @@
2625
import java.nio.file.StandardOpenOption;
2726
import java.util.Iterator;
2827
import java.util.NoSuchElementException;
28+
import java.util.Objects;
2929
import org.apache.baremaps.data.collection.DataCollection;
30-
import org.apache.baremaps.data.storage.DataRow;
31-
import org.apache.baremaps.data.storage.DataSchema;
32-
import org.apache.baremaps.data.storage.DataStoreException;
33-
import org.apache.baremaps.data.storage.DataTable;
34-
import org.locationtech.jts.geom.*;
35-
import org.wololo.flatgeobuf.Constants;
36-
import org.wololo.flatgeobuf.GeometryConversions;
37-
import org.wololo.flatgeobuf.HeaderMeta;
38-
import org.wololo.flatgeobuf.PackedRTree;
39-
import org.wololo.flatgeobuf.generated.Feature;
40-
import org.wololo.flatgeobuf.generated.GeometryType;
30+
import org.apache.baremaps.data.storage.*;
31+
import org.apache.baremaps.data.storage.DataColumn.Type;
32+
import org.apache.baremaps.flatgeobuf.FlatGeoBuf;
33+
import org.apache.baremaps.flatgeobuf.FlatGeoBufReader;
34+
import org.apache.baremaps.flatgeobuf.FlatGeoBufWriter;
35+
import org.apache.baremaps.flatgeobuf.PackedRTree;
4136

4237
/**
4338
* A {@link DataTable} that stores rows in a flatgeobuf file.
@@ -54,8 +49,18 @@ public class FlatGeoBufDataTable implements DataTable {
5449
* @param file the path to the flatgeobuf file
5550
*/
5651
public FlatGeoBufDataTable(Path file) {
52+
this(file, readSchema(file));
53+
}
54+
55+
/**
56+
* Constructs a table from a flatgeobuf file and a schema (used for writing).
57+
*
58+
* @param file the path to the flatgeobuf file
59+
* @param schema the schema of the table
60+
*/
61+
public FlatGeoBufDataTable(Path file, DataSchema schema) {
5762
this.file = file;
58-
this.schema = readSchema(file);
63+
this.schema = schema;
5964
}
6065

6166
/**
@@ -65,27 +70,15 @@ public FlatGeoBufDataTable(Path file) {
6570
* @return the schema of the table
6671
*/
6772
private static DataSchema readSchema(Path file) {
68-
try (var channel = FileChannel.open(file, StandardOpenOption.READ)) {
73+
try (var reader = new FlatGeoBufReader(FileChannel.open(file, StandardOpenOption.READ))) {
6974
// try to read the schema from the file
70-
var buffer = ByteBuffer.allocate(1 << 20).order(ByteOrder.LITTLE_ENDIAN);
71-
HeaderMeta headerMeta = readHeaderMeta(channel, buffer);
72-
return FlatGeoBufTypeConversion.asSchema(headerMeta);
75+
var header = reader.readHeader();
76+
return FlatGeoBufTypeConversion.asSchema(header);
7377
} catch (IOException e) {
7478
return null;
7579
}
7680
}
7781

78-
/**
79-
* Constructs a table from a flatgeobuf file and a schema (used for writing).
80-
*
81-
* @param file the path to the flatgeobuf file
82-
* @param schema the schema of the table
83-
*/
84-
public FlatGeoBufDataTable(Path file, DataSchema schema) {
85-
this.file = file;
86-
this.schema = schema;
87-
}
88-
8982
/**
9083
* {@inheritDoc}
9184
*/
@@ -100,22 +93,13 @@ public DataSchema schema() {
10093
@Override
10194
public Iterator<DataRow> iterator() {
10295
try {
103-
var channel = FileChannel.open(file, StandardOpenOption.READ);
96+
var reader = new FlatGeoBufReader(FileChannel.open(file, StandardOpenOption.READ));
10497

105-
var buffer = ByteBuffer.allocate(1 << 20).order(ByteOrder.LITTLE_ENDIAN);
106-
HeaderMeta headerMeta = readHeaderMeta(channel, buffer);
107-
channel.position(headerMeta.offset);
108-
109-
// skip the index
110-
long indexOffset = headerMeta.offset;
111-
long indexSize =
112-
PackedRTree.calcSize((int) headerMeta.featuresCount, headerMeta.indexNodeSize);
113-
channel.position(indexOffset + indexSize);
114-
115-
buffer.clear();
98+
var header = reader.readHeader();
99+
reader.skipIndex();
116100

117101
// create the feature stream
118-
return new RowIterator(channel, headerMeta, schema, buffer);
102+
return new RowIterator(reader, header, schema);
119103
} catch (IOException e) {
120104
throw new DataStoreException(e);
121105
}
@@ -134,30 +118,14 @@ public void clear() {
134118
*/
135119
@Override
136120
public long size() {
137-
try (var channel = FileChannel.open(file, StandardOpenOption.READ)) {
138-
var buffer = ByteBuffer.allocate(1 << 20).order(ByteOrder.LITTLE_ENDIAN);
139-
HeaderMeta headerMeta = readHeaderMeta(channel, buffer);
140-
return headerMeta.featuresCount;
121+
try (var reader = new FlatGeoBufReader(FileChannel.open(file, StandardOpenOption.READ))) {
122+
FlatGeoBuf.Header header = reader.readHeader();
123+
return header.featuresCount();
141124
} catch (IOException e) {
142125
throw new DataStoreException(e);
143126
}
144127
}
145128

146-
/**
147-
* Reads the header meta from a channel.
148-
*
149-
* @param channel the channel to read from
150-
* @param buffer the buffer to use
151-
* @return the header meta
152-
* @throws IOException if an error occurs while reading the header meta
153-
*/
154-
private static HeaderMeta readHeaderMeta(SeekableByteChannel channel, ByteBuffer buffer)
155-
throws IOException {
156-
channel.read(buffer);
157-
buffer.flip();
158-
return HeaderMeta.read(buffer);
159-
}
160-
161129
/**
162130
* Writes a collection of rows to a flatgeobuf file.
163131
*
@@ -166,68 +134,45 @@ private static HeaderMeta readHeaderMeta(SeekableByteChannel channel, ByteBuffer
166134
*/
167135
public void write(DataCollection<DataRow> features) throws IOException {
168136
try (
169-
var channel = FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
170-
var outputStream = Channels.newOutputStream(channel)) {
171-
outputStream.write(Constants.MAGIC_BYTES);
172-
173-
var bufferBuilder = new FlatBufferBuilder();
174-
175-
var headerMeta = new HeaderMeta();
176-
headerMeta.geometryType = GeometryType.Unknown;
177-
headerMeta.indexNodeSize = 16;
178-
headerMeta.srid = 3857;
179-
headerMeta.featuresCount = features.size();
180-
headerMeta.name = schema.name();
181-
headerMeta.columns = FlatGeoBufTypeConversion.asColumns(schema.columns());
182-
HeaderMeta.write(headerMeta, outputStream, bufferBuilder);
137+
var writer = new FlatGeoBufWriter(
138+
FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE))) {
139+
140+
schema.columns().stream()
141+
.filter(c -> c.cardinality() == DataColumn.Cardinality.REQUIRED)
142+
.forEach(c -> {
143+
if (Objects.requireNonNull(c.type()) == Type.BINARY) {
144+
throw new UnsupportedOperationException();
145+
}
146+
});
147+
148+
var header = new FlatGeoBuf.Header(
149+
schema.name(),
150+
null,
151+
FlatGeoBuf.GeometryType.UNKNOWN,
152+
false,
153+
false,
154+
false,
155+
false,
156+
FlatGeoBufTypeConversion.asColumns(schema.columns()),
157+
features.size(),
158+
2,
159+
null,
160+
null,
161+
null,
162+
null);
163+
164+
writer.writeHeader(header);
183165

184166
var indexSize =
185-
(int) PackedRTree.calcSize((int) headerMeta.featuresCount, headerMeta.indexNodeSize);
167+
(int) PackedRTree.calcSize((int) header.featuresCount(), header.indexNodeSize());
186168

187-
for (int i = 0; i < indexSize; i++) {
188-
outputStream.write(0);
189-
}
169+
writer.writeIndexBuffer(ByteBuffer.allocate(indexSize).order(ByteOrder.LITTLE_ENDIAN));
190170

191171
var iterator = features.iterator();
192172
while (iterator.hasNext()) {
193-
var featureBuilder = new FlatBufferBuilder(4096);
194-
195173
var row = iterator.next();
196-
197-
var propertiesBuffer = ByteBuffer.allocate(1 << 20).order(ByteOrder.LITTLE_ENDIAN);
198-
var properties = row.values().stream()
199-
.filter(v -> !(v instanceof Geometry))
200-
.toList();
201-
for (int i = 0; i < properties.size(); i++) {
202-
var column = headerMeta.columns.get(i);
203-
var value = properties.get(i);
204-
propertiesBuffer.putShort((short) i);
205-
FlatGeoBufTypeConversion.writeValue(propertiesBuffer, column, value);
206-
}
207-
if (propertiesBuffer.position() > 0) {
208-
propertiesBuffer.flip();
209-
}
210-
var propertiesOffset = org.wololo.flatgeobuf.generated.Feature
211-
.createPropertiesVector(featureBuilder, propertiesBuffer);
212-
213-
var geometry = row.values().stream()
214-
.filter(v -> v instanceof Geometry)
215-
.map(Geometry.class::cast)
216-
.findFirst();
217-
218-
var geometryOffset = geometry.isPresent()
219-
? GeometryConversions.serialize(featureBuilder, geometry.get(), headerMeta.geometryType)
220-
: 0;
221-
222-
var featureOffset =
223-
org.wololo.flatgeobuf.generated.Feature.createFeature(featureBuilder, geometryOffset,
224-
propertiesOffset, 0);
225-
featureBuilder.finishSizePrefixed(featureOffset);
226-
227-
ByteBuffer data = featureBuilder.dataBuffer();
228-
while (data.hasRemaining()) {
229-
channel.write(data);
230-
}
174+
var feature = FlatGeoBufTypeConversion.asFeature(row);
175+
writer.writeFeature(feature);
231176
}
232177
}
233178
}
@@ -237,38 +182,36 @@ public void write(DataCollection<DataRow> features) throws IOException {
237182
*/
238183
public static class RowIterator implements Iterator<DataRow> {
239184

240-
private final HeaderMeta headerMeta;
185+
private final FlatGeoBuf.Header header;
241186

242187
private final DataSchema schema;
243188

244-
private final SeekableByteChannel channel;
245-
246-
private final ByteBuffer buffer;
189+
private final FlatGeoBufReader reader;
247190

248191
private long cursor = 0;
249192

250193
/**
251194
* Constructs a row iterator.
252195
*
253-
* @param channel the channel to read from
254-
* @param headerMeta the header meta
196+
* @param reader the channel to read from
197+
* @param header the header of the file
255198
* @param schema the schema of the table
256-
* @param buffer the buffer to use
257199
*/
258-
public RowIterator(SeekableByteChannel channel, HeaderMeta headerMeta,
259-
DataSchema schema, ByteBuffer buffer) {
260-
this.channel = channel;
261-
this.headerMeta = headerMeta;
200+
public RowIterator(
201+
FlatGeoBufReader reader,
202+
FlatGeoBuf.Header header,
203+
DataSchema schema) {
204+
this.reader = reader;
205+
this.header = header;
262206
this.schema = schema;
263-
this.buffer = buffer;
264207
}
265208

266209
/**
267210
* {@inheritDoc}
268211
*/
269212
@Override
270213
public boolean hasNext() {
271-
return cursor < headerMeta.featuresCount;
214+
return cursor < header.featuresCount();
272215
}
273216

274217
/**
@@ -277,19 +220,9 @@ public boolean hasNext() {
277220
@Override
278221
public DataRow next() {
279222
try {
280-
channel.read(buffer);
281-
buffer.flip();
282-
283-
var featureSize = buffer.getInt();
284-
var row =
285-
FlatGeoBufTypeConversion.asRow(headerMeta, schema, Feature.getRootAsFeature(buffer));
286-
287-
buffer.position(Integer.BYTES + featureSize);
288-
buffer.compact();
289-
223+
var feature = reader.readFeature();
290224
cursor++;
291-
292-
return row;
225+
return FlatGeoBufTypeConversion.asRow(schema, feature);
293226
} catch (IOException e) {
294227
throw new NoSuchElementException(e);
295228
}

0 commit comments

Comments
 (0)