This is an automated email from the ASF dual-hosted git repository.

bchapuis pushed a commit to branch 619-transformation-workflow
in repository https://gitbox.apache.org/repos/asf/incubator-baremaps.git

commit 9b59aefad6472fcfa8ac3dd71025954ffa8f2c3b
Author: Bertil Chapuis <[email protected]>
AuthorDate: Fri Apr 7 23:46:05 2023 +0200

    Improve support for geometries in collections
---
 .../{GeometryDataType.java => WKBDataType.java}    |   5 +-
 .../type/geometry/CoordinateArrayDataType.java     |  62 +++++++
 .../type/geometry/GeometryCollectionDataType.java  |  96 +++++++++++
 .../collection/type/geometry/GeometryDataType.java | 185 +++++++++++++++++++++
 .../type/geometry/LineStringDataType.java          |  67 ++++++++
 .../type/geometry/MultiLineStringDataType.java     |  85 ++++++++++
 .../type/geometry/MultiPointDataType.java          |  67 ++++++++
 .../type/geometry/MultiPolygonDataType.java        |  83 +++++++++
 .../collection/type/geometry/PointDataType.java    |  76 +++++++++
 .../collection/type/geometry/PolygonDataType.java  | 111 +++++++++++++
 10 files changed, 835 insertions(+), 2 deletions(-)

diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/collection/type/GeometryDataType.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/WKBDataType.java
similarity index 91%
rename from 
baremaps-core/src/main/java/org/apache/baremaps/collection/type/GeometryDataType.java
rename to 
baremaps-core/src/main/java/org/apache/baremaps/collection/type/WKBDataType.java
index 395a74cf..4bf89729 100644
--- 
a/baremaps-core/src/main/java/org/apache/baremaps/collection/type/GeometryDataType.java
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/WKBDataType.java
@@ -19,12 +19,13 @@ import 
org.apache.baremaps.openstreetmap.utils.GeometryUtils;
 import org.locationtech.jts.geom.Geometry;
 
 /** A {@link DataType} for reading and writing {@link Geometry} in {@link 
ByteBuffer}s. */
-public class GeometryDataType implements DataType<Geometry> {
+public class WKBDataType implements DataType<Geometry> {
 
   /** {@inheritDoc} */
   @Override
   public int size(Geometry value) {
-    return Integer.BYTES + GeometryUtils.serialize(value).length;
+    byte[] bytes = GeometryUtils.serialize(value);
+    return Integer.BYTES + bytes.length;
   }
 
   /** {@inheritDoc} */
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/CoordinateArrayDataType.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/CoordinateArrayDataType.java
new file mode 100644
index 00000000..d3980ee0
--- /dev/null
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/CoordinateArrayDataType.java
@@ -0,0 +1,62 @@
+package org.apache.baremaps.collection.type.geometry;
+
+import org.apache.baremaps.collection.type.DataType;
+import org.locationtech.jts.geom.Coordinate;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A data type for {@link Coordinate} arrays.
+ */
+public class CoordinateArrayDataType implements DataType<Coordinate[]> {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(Coordinate[] value) {
+        return Integer.BYTES + Double.BYTES * 2 * value.length;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(ByteBuffer buffer, int position) {
+        return buffer.getInt(position);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void write(ByteBuffer buffer, int position, Coordinate[] value) {
+        buffer.putInt(position, size(value));
+        position += Integer.BYTES;
+        for (int i = 0; i < value.length; i++) {
+            Coordinate coordinate = value[i];
+            buffer.putDouble(position, coordinate.x);
+            position += Double.BYTES;
+            buffer.putDouble(position, coordinate.y);
+            position += Double.BYTES;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Coordinate[] read(ByteBuffer buffer, int position) {
+        int size = buffer.getInt(position);
+        int numPoints = (size - Integer.BYTES) / (Double.BYTES * 2);
+        position += Integer.BYTES;
+        Coordinate[] coordinates = new Coordinate[numPoints];
+        for (int i = 0; i < numPoints; i++) {
+            double x = buffer.getDouble(position);
+            double y = buffer.getDouble(position + Double.BYTES);
+            coordinates[i] = new Coordinate(x, y);
+            position += Double.BYTES * 2;
+        }
+        return coordinates;
+    }
+}
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/GeometryCollectionDataType.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/GeometryCollectionDataType.java
new file mode 100644
index 00000000..6d06b187
--- /dev/null
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/GeometryCollectionDataType.java
@@ -0,0 +1,96 @@
+package org.apache.baremaps.collection.type.geometry;
+
+import org.apache.baremaps.collection.type.DataType;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.GeometryFactory;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * A data type for {@link GeometryCollection} objects.
+ */
+public class GeometryCollectionDataType implements 
DataType<GeometryCollection> {
+
+    private final GeometryFactory geometryFactory;
+
+    private GeometryDataType geometryDataType;
+
+    /**
+     * Constructs a {@code GeometryCollectionDataType} with a default {@code 
GeometryFactory}.
+     */
+    public GeometryCollectionDataType() {
+        this(new GeometryFactory(), new GeometryDataType());
+    }
+
+    /**
+     * Constructs a {@code GeometryCollectionDataType} with a specified {@code 
GeometryFactory}.
+     *
+     * @param geometryFactory the geometry factory
+     */
+    public GeometryCollectionDataType(GeometryFactory geometryFactory) {
+        this(geometryFactory, new GeometryDataType());
+    }
+
+    /**
+     * Constructs a {@code GeometryCollectionDataType} with a specified {@code 
GeometryFactory} and {@code GeometryDataType}.
+     *
+     * @param geometryFactory the geometry factory
+     * @param geometryDataType the geometry data type
+     */
+    public GeometryCollectionDataType(GeometryFactory geometryFactory, 
GeometryDataType geometryDataType) {
+        this.geometryFactory = geometryFactory;
+        this.geometryDataType = geometryDataType;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(GeometryCollection value) {
+        int size = Integer.BYTES;
+        for (int i = 0; i < value.getNumGeometries(); i++) {
+            size += geometryDataType.size(value.getGeometryN(i));
+        }
+        return size;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(ByteBuffer buffer, int position) {
+        return buffer.getInt(position);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void write(ByteBuffer buffer, int position, GeometryCollection 
value) {
+        buffer.putInt(position, size(value));
+        position += Integer.BYTES;
+        for (int i = 0; i < value.getNumGeometries(); i++) {
+            var geometry = value.getGeometryN(i);
+            geometryDataType.write(buffer, position, geometry);
+            position += geometryDataType.size(buffer, position);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GeometryCollection read(ByteBuffer buffer, int position) {
+        var size = buffer.getInt(position);
+        position += Integer.BYTES;
+        var geometries = new ArrayList<Geometry>();
+        while (position < size) {
+            var geometry = geometryDataType.read(buffer, position);
+            geometries.add(geometry);
+            position += geometryDataType.size(geometry);
+        }
+        return 
geometryFactory.createGeometryCollection(geometries.toArray(Geometry[]::new));
+    }
+}
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/GeometryDataType.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/GeometryDataType.java
new file mode 100644
index 00000000..5713e734
--- /dev/null
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/GeometryDataType.java
@@ -0,0 +1,185 @@
+package org.apache.baremaps.collection.type.geometry;
+
+
+import org.apache.baremaps.collection.type.DataType;
+import org.locationtech.jts.geom.*;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A {@code DataType} for {@code MultiPolygon} objects.
+ */
+public class GeometryDataType implements DataType<Geometry> {
+
+    private final GeometryFactory geometryFactory;
+
+    private final PointDataType pointDataType;
+
+    private final LineStringDataType lineStringDataType;
+
+    private final PolygonDataType polygonDataType;
+
+    private final MultiPointDataType multiPointDataType;
+
+    private final MultiLineStringDataType multiLineStringDataType;
+
+    private final MultiPolygonDataType multiPolygonDataType;
+
+    private final GeometryCollectionDataType geometryCollectionDataType;
+
+    /**
+     * Constructs a {@code GeometryDataType} with a default {@code 
GeometryFactory}.
+     */
+    public GeometryDataType() {
+        this(new GeometryFactory());
+    }
+
+    /**
+     * Constructs a {@code GeometryDataType} with a specified {@code 
GeometryFactory}.
+     *
+     * @param geometryFactory
+     */
+    public GeometryDataType(GeometryFactory geometryFactory) {
+        this.geometryFactory = geometryFactory;
+        this.pointDataType = new PointDataType(geometryFactory);
+        this.lineStringDataType = new LineStringDataType(geometryFactory);
+        this.polygonDataType = new PolygonDataType(geometryFactory);
+        this.multiPointDataType = new MultiPointDataType(geometryFactory);
+        this.multiLineStringDataType = new 
MultiLineStringDataType(geometryFactory);
+        this.multiPolygonDataType = new MultiPolygonDataType(geometryFactory);
+        this.geometryCollectionDataType = new 
GeometryCollectionDataType(geometryFactory, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(Geometry value) {
+        var size = 0;
+
+        // Geometry type
+        size += Byte.BYTES;
+
+        // Size of the geometry
+        if (value instanceof Point point) {
+            size += pointDataType.size(point);
+        } else if (value instanceof LineString lineString) {
+            size += lineStringDataType.size(lineString);
+        } else if (value instanceof Polygon polygon) {
+            size += polygonDataType.size(polygon);
+        } else if (value instanceof MultiPoint multiPoint) {
+            size += multiPointDataType.size(multiPoint);
+        } else if (value instanceof MultiLineString multiLineString) {
+            size += multiLineStringDataType.size(multiLineString);
+        } else if (value instanceof MultiPolygon multiPolygon) {
+            size += multiPolygonDataType.size(multiPolygon);
+        } else if (value instanceof GeometryCollection geometryCollection) {
+            size += geometryCollectionDataType.size(geometryCollection);
+        } else {
+            throw new IllegalArgumentException("Unsupported geometry type: " + 
value.getClass());
+        }
+
+        return size;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(ByteBuffer buffer, int position) {
+        var size = 0;
+
+        // Geometry type
+        var type = buffer.get(position);
+        size += Byte.BYTES;
+
+        // Size of the geometry
+        if (type == 1) {
+            size += pointDataType.size(buffer, position + Byte.BYTES);
+        } else if (type == 2) {
+            size += lineStringDataType.size(buffer, position + Byte.BYTES);
+        } else if (type == 3) {
+            size += polygonDataType.size(buffer, position + Byte.BYTES);
+        } else if (type == 4) {
+            size += multiPointDataType.size(buffer, position + Byte.BYTES);
+        } else if (type == 5) {
+            size += multiLineStringDataType.size(buffer, position + 
Byte.BYTES);
+        } else if (type == 6) {
+            size += multiPolygonDataType.size(buffer, position + Byte.BYTES);
+        } else if (type == 7) {
+            size += geometryCollectionDataType.size(buffer, position + 
Byte.BYTES);
+        } else {
+            throw new IllegalArgumentException("Unsupported geometry type: " + 
type);
+        }
+
+        return size;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void write(ByteBuffer buffer, int position, Geometry value) {
+        // Write the geometry
+        if (value instanceof Point) {
+            buffer.put(position, (byte) 1);
+            position += Byte.BYTES;
+            pointDataType.write(buffer, position, (Point) value);
+        } else if (value instanceof LineString) {
+            buffer.put(position, (byte) 2);
+            position += Byte.BYTES;
+            lineStringDataType.write(buffer, position, (LineString) value);
+        } else if (value instanceof Polygon) {
+            buffer.put(position, (byte) 3);
+            position += Byte.BYTES;
+            polygonDataType.write(buffer, position, (Polygon) value);
+        } else if (value instanceof MultiPoint) {
+            buffer.put(position, (byte) 4);
+            position += Byte.BYTES;
+            multiPointDataType.write(buffer, position, (MultiPoint) value);
+        } else if (value instanceof MultiLineString) {
+            buffer.put(position, (byte) 5);
+            position += Byte.BYTES;
+            multiLineStringDataType.write(buffer, position, (MultiLineString) 
value);
+        } else if (value instanceof MultiPolygon) {
+            buffer.put(position, (byte) 6);
+            position += Byte.BYTES;
+            multiPolygonDataType.write(buffer, position, (MultiPolygon) value);
+        } else if (value instanceof GeometryCollection) {
+            buffer.put(position, (byte) 7);
+            position += Byte.BYTES;
+            geometryCollectionDataType.write(buffer, position, 
(GeometryCollection) value);
+        } else {
+            throw new IllegalArgumentException("Unsupported geometry type: " + 
value.getClass());
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Geometry read(ByteBuffer buffer, int position) {
+        // Read the geometry type
+        var type = buffer.get(position);
+        position += Byte.BYTES;
+
+        // Read the geometry
+        if (type == 1) {
+            return pointDataType.read(buffer, position);
+        } else if (type == 2) {
+            return lineStringDataType.read(buffer, position);
+        } else if (type == 3) {
+            return polygonDataType.read(buffer, position);
+        } else if (type == 4) {
+            return multiPointDataType.read(buffer, position);
+        } else if (type == 5) {
+            return multiLineStringDataType.read(buffer, position);
+        } else if (type == 6) {
+            return multiPolygonDataType.read(buffer, position);
+        } else if (type == 7) {
+            return geometryCollectionDataType.read(buffer, position);
+        } else {
+            throw new IllegalArgumentException("Unsupported geometry type: " + 
type);
+        }
+    }
+}
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/LineStringDataType.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/LineStringDataType.java
new file mode 100644
index 00000000..fcf8ff99
--- /dev/null
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/LineStringDataType.java
@@ -0,0 +1,67 @@
+package org.apache.baremaps.collection.type.geometry;
+
+import org.apache.baremaps.collection.type.DataType;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A data type for {@link LineString} objects.
+ */
+public class LineStringDataType implements DataType<LineString> {
+
+    private final GeometryFactory geometryFactory;
+
+    private final CoordinateArrayDataType coordinateArrayDataType;
+
+    /**
+     * Constructs a {@code LineStringDataType} with a default {@code 
GeometryFactory}.
+     */
+    public LineStringDataType() {
+        this(new GeometryFactory());
+    }
+
+    /**
+     * Constructs a {@code LineStringDataType} with a specified {@code 
GeometryFactory}.
+     *
+     * @param geometryFactory the geometry factory
+     */
+    public LineStringDataType(GeometryFactory geometryFactory) {
+        this.geometryFactory = geometryFactory;
+        this.coordinateArrayDataType = new CoordinateArrayDataType();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(LineString value) {
+        return coordinateArrayDataType.size(value.getCoordinates());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(ByteBuffer buffer, int position) {
+        return coordinateArrayDataType.size(buffer, position);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void write(ByteBuffer buffer, int position, LineString value) {
+        coordinateArrayDataType.write(buffer, position, 
value.getCoordinates());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public LineString read(ByteBuffer buffer, int position) {
+        var coordinates = coordinateArrayDataType.read(buffer, position);
+        return geometryFactory.createLineString(coordinates);
+    }
+}
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/MultiLineStringDataType.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/MultiLineStringDataType.java
new file mode 100644
index 00000000..3c7c7846
--- /dev/null
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/MultiLineStringDataType.java
@@ -0,0 +1,85 @@
+package org.apache.baremaps.collection.type.geometry;
+
+import org.apache.baremaps.collection.type.DataType;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.MultiLineString;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * A data type for {@link MultiLineString} objects.
+ */
+public class MultiLineStringDataType implements DataType<MultiLineString> {
+
+    private final LineStringDataType lineStringDataType;
+
+    private final GeometryFactory geometryFactory;
+
+    /**
+     * Constructs a {@code MultiLineStringDataType} with a default {@code 
GeometryFactory}.
+     */
+    public MultiLineStringDataType() {
+        this(new GeometryFactory());
+    }
+
+    /**
+     * Constructs a {@code MultiLineStringDataType} with a specified {@code 
GeometryFactory}.
+     *
+     * @param geometryFactory the geometry factory
+     */
+    public MultiLineStringDataType(GeometryFactory geometryFactory) {
+        this.geometryFactory = geometryFactory;
+        this.lineStringDataType = new LineStringDataType(geometryFactory);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(MultiLineString value) {
+        int size = Integer.BYTES;
+        for (int i = 0; i < value.getNumGeometries(); i++) {
+            size += lineStringDataType.size((LineString) 
value.getGeometryN(i));
+        }
+        return size;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(ByteBuffer buffer, int position) {
+        return buffer.getInt(position);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void write(ByteBuffer buffer, int position, MultiLineString value) {
+        buffer.putInt(position, size(value));
+        position += Integer.BYTES;
+        for (int i = 0; i < value.getNumGeometries(); i++) {
+            lineStringDataType.write(buffer, position, (LineString) 
value.getGeometryN(i));
+            position += buffer.getInt(position);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public MultiLineString read(ByteBuffer buffer, int position) {
+        int size = buffer.getInt(position);
+        position += Integer.BYTES;
+        var lineStrings = new ArrayList<LineString>();
+        while (position < size) {
+            var lineString = lineStringDataType.read(buffer, position);
+            lineStrings.add(lineString);
+            position += lineStringDataType.size(buffer, position);
+        }
+        return 
geometryFactory.createMultiLineString(lineStrings.toArray(LineString[]::new));
+    }
+}
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/MultiPointDataType.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/MultiPointDataType.java
new file mode 100644
index 00000000..f53fbfcf
--- /dev/null
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/MultiPointDataType.java
@@ -0,0 +1,67 @@
+package org.apache.baremaps.collection.type.geometry;
+
+import org.apache.baremaps.collection.type.DataType;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.MultiPoint;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A data type for {@link MultiPoint} objects.
+ */
+public class MultiPointDataType implements DataType<MultiPoint> {
+
+    private final CoordinateArrayDataType coordinateArrayDataType = new 
CoordinateArrayDataType();
+
+    private final GeometryFactory geometryFactory;
+
+    /**
+     * Constructs a {@code MultiPointDataType} with a default {@code 
GeometryFactory}.
+     */
+    public MultiPointDataType() {
+        this(new GeometryFactory());
+    }
+
+    /**
+     * Constructs a {@code MultiPointDataType} with a specified {@code 
GeometryFactory}.
+     *
+     * @param geometryFactory the geometry factory
+     */
+    public MultiPointDataType(GeometryFactory geometryFactory) {
+        this.geometryFactory = geometryFactory;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(MultiPoint value) {
+        return coordinateArrayDataType.size(value.getCoordinates());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(ByteBuffer buffer, int position) {
+        return coordinateArrayDataType.size(buffer, position);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void write(ByteBuffer buffer, int position, MultiPoint value) {
+        coordinateArrayDataType.write(buffer, position, 
value.getCoordinates());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public MultiPoint read(ByteBuffer buffer, int position) {
+        var coordinates = coordinateArrayDataType.read(buffer, position);
+        return geometryFactory.createMultiPoint(coordinates);
+    }
+}
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/MultiPolygonDataType.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/MultiPolygonDataType.java
new file mode 100644
index 00000000..f2c4141e
--- /dev/null
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/MultiPolygonDataType.java
@@ -0,0 +1,83 @@
+package org.apache.baremaps.collection.type.geometry;
+
+import org.apache.baremaps.collection.type.DataType;
+import org.locationtech.jts.geom.*;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * A data type for {@link GeometryCollection} objects.
+ */
+public class MultiPolygonDataType implements DataType<MultiPolygon> {
+
+    private final GeometryFactory geometryFactory;
+
+    private final PolygonDataType polygonDataType;
+
+    /**
+     * Constructs a {@code MultiPolygonDataType} with a default {@code 
GeometryFactory}.
+     */
+    public MultiPolygonDataType() {
+        this(new GeometryFactory());
+    }
+
+    /**
+     * Constructs a {@code MultiPolygonDataType} with a specified {@code 
GeometryFactory}.
+     *
+     * @param geometryFactory the geometry factory
+     */
+    public MultiPolygonDataType(GeometryFactory geometryFactory) {
+        this.geometryFactory = geometryFactory;
+        this.polygonDataType = new PolygonDataType(geometryFactory);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(MultiPolygon value) {
+        int size = Integer.BYTES;
+        for (int i = 0; i < value.getNumGeometries(); i++) {
+            size += polygonDataType.size((Polygon) value.getGeometryN(i));
+        }
+        return size;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(ByteBuffer buffer, int position) {
+        return buffer.getInt(position);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void write(ByteBuffer buffer, int position, MultiPolygon value) {
+        buffer.putInt(position, size(value));
+        position += Integer.BYTES;
+        for (int i = 0; i < value.getNumGeometries(); i++) {
+            polygonDataType.write(buffer, position, (Polygon) 
value.getGeometryN(i));
+            position += buffer.getInt(position);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public MultiPolygon read(ByteBuffer buffer, int position) {
+        int size = buffer.getInt(position);
+        position += Integer.BYTES;
+        var polygons = new ArrayList<Polygon>();
+        while (position < size) {
+            var polygon = polygonDataType.read(buffer, position);
+            polygons.add(polygon);
+            position += polygonDataType.size(buffer, position);
+        }
+        return 
geometryFactory.createMultiPolygon(polygons.toArray(Polygon[]::new));
+    }
+}
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/PointDataType.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/PointDataType.java
new file mode 100644
index 00000000..97a38407
--- /dev/null
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/PointDataType.java
@@ -0,0 +1,76 @@
+package org.apache.baremaps.collection.type.geometry;
+
+import org.apache.baremaps.collection.type.DataType;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.Point;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A data type for {@link Point} objects.
+ */
+public class PointDataType implements DataType<Point> {
+
+    private final GeometryFactory geometryFactory;
+
+    /**
+     * Constructs a {@code PointDataType} with a default {@code 
GeometryFactory}.
+     */
+    public PointDataType() {
+        this(new GeometryFactory());
+    }
+
+    /**
+     * Constructs a {@code PointDataType} with a specified {@code 
GeometryFactory}.
+     *
+     * @param geometryFactory the geometry factory
+     */
+    public PointDataType(GeometryFactory geometryFactory) {
+        this.geometryFactory = geometryFactory;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(Point value) {
+        return Double.BYTES * 2;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(ByteBuffer buffer, int position) {
+        return Double.BYTES * 2;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void write(ByteBuffer buffer, int position, Point value) {
+        if (value.isEmpty()) {
+            buffer.putDouble(position, Double.NaN);
+            buffer.putDouble(position + Double.BYTES, Double.NaN);
+        } else {
+            buffer.putDouble(position, value.getX());
+            buffer.putDouble(position + Double.BYTES, value.getY());
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Point read(ByteBuffer buffer, int position) {
+        double x = buffer.getDouble(position);
+        double y = buffer.getDouble(position + Double.BYTES);
+        if (Double.isNaN(x) || Double.isNaN(y)) {
+            return geometryFactory.createPoint();
+        } else {
+            return geometryFactory.createPoint(new Coordinate(x, y));
+        }
+    }
+}
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/PolygonDataType.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/PolygonDataType.java
new file mode 100644
index 00000000..a9bf0c9b
--- /dev/null
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/collection/type/geometry/PolygonDataType.java
@@ -0,0 +1,111 @@
+package org.apache.baremaps.collection.type.geometry;
+
+import org.apache.baremaps.collection.type.DataType;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.Polygon;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * A data type for {@link Polygon} objects.
+ */
+public class PolygonDataType implements DataType<Polygon> {
+
+    private final CoordinateArrayDataType coordinateArrayDataType = new 
CoordinateArrayDataType();
+
+    private final GeometryFactory geometryFactory;
+
+    /**
+     * Constructs a {@code PolygonDataType} with a default {@code 
GeometryFactory}.
+     */
+    public PolygonDataType() {
+        this(new GeometryFactory());
+    }
+
+    /**
+     * Constructs a {@code PolygonDataType} with a specified {@code 
GeometryFactory}.
+     *
+     * @param geometryFactory the geometry factory
+     */
+    public PolygonDataType(GeometryFactory geometryFactory) {
+        this.geometryFactory = geometryFactory;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(Polygon value) {
+        int size = Integer.BYTES;
+
+        // Add the size of the exterior ring
+        var exteriorRing = value.getExteriorRing();
+        size += coordinateArrayDataType.size(exteriorRing.getCoordinates());
+
+        // Add the size of the interior rings
+        for (int i = 0; i < value.getNumInteriorRing(); i++) {
+            var interiorRing = value.getInteriorRingN(i);
+            size += 
coordinateArrayDataType.size(interiorRing.getCoordinates());
+        }
+
+        return size;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int size(ByteBuffer buffer, int position) {
+        return coordinateArrayDataType.size(buffer, position);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void write(ByteBuffer buffer, int position, Polygon value) {
+        buffer.putInt(position, size(value));
+        position += Integer.BYTES;
+
+        // Write the exterior ring
+        var exteriorRing = value.getExteriorRing();
+        coordinateArrayDataType.write(buffer, position, 
exteriorRing.getCoordinates());
+        position += 
coordinateArrayDataType.size(exteriorRing.getCoordinates());
+
+        // Write the interior rings
+        for (int i = 0; i < value.getNumInteriorRing(); i++) {
+            var interiorRing = value.getInteriorRingN(i);
+            coordinateArrayDataType.write(buffer, position, 
interiorRing.getCoordinates());
+            position += 
coordinateArrayDataType.size(interiorRing.getCoordinates());
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Polygon read(ByteBuffer buffer, int position) {
+        var size = buffer.getInt(position);
+        position += Integer.BYTES;
+
+        // Read the exterior ring
+        var exteriorRingCoordinates = coordinateArrayDataType.read(buffer, 
position);
+        var exteriorRing = 
geometryFactory.createLinearRing(exteriorRingCoordinates);
+        position += coordinateArrayDataType.size(buffer, position);
+
+        // Read the interior rings
+        var interiorRings = new ArrayList<LineString>();
+        while (position < size) {
+            var interiorRingCoordinates = coordinateArrayDataType.read(buffer, 
position);
+            var interiorRing = 
geometryFactory.createLinearRing(interiorRingCoordinates);
+            interiorRings.add(interiorRing);
+            position += coordinateArrayDataType.size(buffer, position);
+        }
+
+        return geometryFactory.createPolygon(exteriorRing, 
interiorRings.toArray(LinearRing[]::new));
+    }
+}

Reply via email to