From 72fea024a995d021c296947c06931a75c9f7ac81 Mon Sep 17 00:00:00 2001 From: Claus Nagel Date: Sat, 11 Jan 2025 15:05:38 +0100 Subject: [PATCH] reworked byte buffer for WKB parser and writer --- .../database/postgres/GeometryAdapter.java | 2 +- .../citydb/database/geometry/WKBParser.java | 80 ++++- .../citydb/database/geometry/WKBWriter.java | 331 ++++++++++-------- 3 files changed, 255 insertions(+), 158 deletions(-) diff --git a/citydb-database-postgres/src/main/java/org/citydb/database/postgres/GeometryAdapter.java b/citydb-database-postgres/src/main/java/org/citydb/database/postgres/GeometryAdapter.java index 22570a2a..02f036b1 100644 --- a/citydb-database-postgres/src/main/java/org/citydb/database/postgres/GeometryAdapter.java +++ b/citydb-database-postgres/src/main/java/org/citydb/database/postgres/GeometryAdapter.java @@ -69,7 +69,7 @@ public Envelope getEnvelope(Object geometryObject) throws GeometryException { } @Override - public Object getGeometry(Geometry geometry, boolean force3D) throws GeometryException { + public Object getGeometry(Geometry geometry, boolean force3D) { return writer.write(geometry, force3D); } diff --git a/citydb-database/src/main/java/org/citydb/database/geometry/WKBParser.java b/citydb-database/src/main/java/org/citydb/database/geometry/WKBParser.java index f9acd268..f92153a0 100644 --- a/citydb-database/src/main/java/org/citydb/database/geometry/WKBParser.java +++ b/citydb-database/src/main/java/org/citydb/database/geometry/WKBParser.java @@ -23,28 +23,25 @@ import org.citydb.model.geometry.*; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; public class WKBParser { public Geometry parse(String wkb) throws GeometryException { - return wkb != null ? read(ByteBuffer.wrap(toBytes(wkb))) : null; + return wkb != null ? read(new StringBuffer(wkb)) : null; } public Geometry parse(Object wkb) throws GeometryException { - return wkb != null ? read(ByteBuffer.wrap(toBytes(wkb.toString()))) : null; + return wkb != null ? read(new StringBuffer(wkb.toString())) : null; } public Geometry parse(byte[] bytes) throws GeometryException { - return bytes != null ? read(ByteBuffer.wrap(bytes)) : null; + return bytes != null ? read(new ArrayBuffer(bytes)) : null; } private Geometry read(ByteBuffer buffer) throws GeometryException { - byte byteOrder = buffer.get(); - buffer.order(byteOrder == 1 ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); + buffer.order(buffer.getByte()); int typeInt = buffer.getInt(); int geometryType = typeInt & 0xff; @@ -182,14 +179,69 @@ private Coordinate getCoordinate(ByteBuffer buffer, int dimension) { Coordinate.of(x, y, buffer.getDouble()); } - private byte[] toBytes(String hex) { - int len = hex.length(); - byte[] bytes = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - bytes[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + - Character.digit(hex.charAt(i + 1), 16)); + private static abstract class ByteBuffer { + final int[] bigEndian = new int[]{1, 0, 1, 2, 3, 4, 5, 6, 7}; + final int[] littleEndian = new int[]{5, 7, 6, 5, 4, 3, 2, 1, 0}; + int[] indexes = bigEndian; + int pos; + + abstract int get(int pos); + + void order(byte order) { + indexes = order == 0 ? bigEndian : littleEndian; + } + + byte getByte() { + return (byte) get(pos++); + } + + int getInt() { + int low = indexes[0]; + int value = (get(pos + indexes[low]) << 24) + (get(pos + indexes[low + 1]) << 16) + + (get(pos + indexes[low + 2]) << 8) + get(pos + indexes[low + 3]); + pos += 4; + return value; } - return bytes; + long getLong() { + long value = ((long) get(pos + indexes[1]) << 56) + ((long) get(pos + indexes[2]) << 48) + + ((long) get(pos + indexes[3]) << 40) + ((long) get(pos + indexes[4]) << 32) + + ((long) get(pos + indexes[5]) << 24) + ((long) get(pos + indexes[6]) << 16) + + ((long) get(pos + indexes[7]) << 8) + ((long) get(pos + indexes[8])); + pos += 8; + return value; + } + + double getDouble() { + return Double.longBitsToDouble(getLong()); + } + } + + private static class StringBuffer extends ByteBuffer { + final String data; + + StringBuffer(String data) { + this.data = data; + } + + @Override + int get(int pos) { + pos *= 2; + return (Character.digit(data.charAt(pos), 16) << 4) + + Character.digit(data.charAt(pos + 1), 16); + } + } + + private static class ArrayBuffer extends ByteBuffer { + final byte[] data; + + ArrayBuffer(byte[] data) { + this.data = data; + } + + @Override + int get(int pos) { + return data[pos] & 0xFF; + } } } diff --git a/citydb-database/src/main/java/org/citydb/database/geometry/WKBWriter.java b/citydb-database/src/main/java/org/citydb/database/geometry/WKBWriter.java index 33a1c898..c6ee5ae7 100644 --- a/citydb-database/src/main/java/org/citydb/database/geometry/WKBWriter.java +++ b/citydb-database/src/main/java/org/citydb/database/geometry/WKBWriter.java @@ -23,12 +23,9 @@ import org.citydb.model.geometry.*; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.util.List; public class WKBWriter { - private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); private boolean useBigEndian; private boolean includeSRID; @@ -42,115 +39,150 @@ public WKBWriter includeSRID(boolean includeSRID) { return this; } - public String write(Geometry geometry) throws GeometryException { + public String write(Geometry geometry) { return write(geometry, false); } - public String write(Geometry geometry, boolean force3D) throws GeometryException { - if (geometry != null) { - int srid = includeSRID ? geometry.getSRID().orElse(0) : 0; - int dimension = force3D ? 3 : geometry.getVertexDimension(); - GeometryType geometryType = geometry.getGeometryType(); - - return switch (geometryType) { - case POINT -> write((Point) geometry, dimension, srid); - case MULTI_POINT -> write((MultiPoint) geometry, dimension, srid); - case LINE_STRING -> write((LineString) geometry, dimension, srid); - case MULTI_LINE_STRING -> write((MultiLineString) geometry, dimension, srid); - case POLYGON -> write((Polygon) geometry, dimension, srid); - case MULTI_SURFACE, COMPOSITE_SURFACE, TRIANGULATED_SURFACE -> - write((SurfaceCollection) geometry, dimension, srid); - case SOLID -> write((Solid) geometry, dimension, srid); - case COMPOSITE_SOLID, MULTI_SOLID -> write((SolidCollection) geometry, dimension, srid); - }; - } else { - return null; - } + public String write(Geometry geometry, boolean force3D) { + return geometry != null ? + write(geometry, force3D, StringBuffer.class).build() : + null; } - private String write(Point point, int dimension, int srid) { - ByteBuffer buffer = ByteBuffer.allocate(calculateBytes(point, dimension, srid)); - put(buffer, point, dimension, srid); - return toHex(buffer.array()); + public byte[] writeBinary(Geometry geometry) { + return writeBinary(geometry, false); } - private String write(MultiPoint multiPoint, int dimension, int srid) { - ByteBuffer buffer = ByteBuffer.allocate(calculateBytes(multiPoint, dimension, srid)); - put(buffer, multiPoint, dimension, srid); - return toHex(buffer.array()); + public byte[] writeBinary(Geometry geometry, boolean force3D) { + return geometry != null ? + write(geometry, force3D, ArrayBuffer.class).build() : + null; } - private String write(LineString lineString, int dimension, int srid) { - ByteBuffer buffer = ByteBuffer.allocate(calculateBytes(lineString, dimension, srid)); - put(buffer, lineString, dimension, srid); - return toHex(buffer.array()); - } + @SuppressWarnings("unchecked") + private T write(Geometry geometry, boolean force3D, Class type) { + int srid = includeSRID ? geometry.getSRID().orElse(0) : 0; + int dimension = force3D ? 3 : geometry.getVertexDimension(); + int length = calculateBytes(geometry, dimension, srid); + T buffer = (T) (type == ArrayBuffer.class ? + new ArrayBuffer(length, useBigEndian) : + new StringBuffer(length, useBigEndian)); - private String write(MultiLineString multiLineString, int dimension, int srid) { - ByteBuffer buffer = ByteBuffer.allocate(calculateBytes(multiLineString, dimension, srid)); - put(buffer, multiLineString, dimension, srid); - return toHex(buffer.array()); + switch (geometry.getGeometryType()) { + case POINT -> write((Point) geometry, dimension, srid, buffer); + case MULTI_POINT -> write((MultiPoint) geometry, dimension, srid, buffer); + case LINE_STRING -> write((LineString) geometry, dimension, srid, buffer); + case MULTI_LINE_STRING -> write((MultiLineString) geometry, dimension, srid, buffer); + case POLYGON -> write((Polygon) geometry, dimension, srid, buffer); + case MULTI_SURFACE, COMPOSITE_SURFACE, TRIANGULATED_SURFACE -> + write((SurfaceCollection) geometry, dimension, srid, buffer); + case SOLID -> write((Solid) geometry, dimension, srid, buffer); + case COMPOSITE_SOLID, MULTI_SOLID -> write((SolidCollection) geometry, dimension, srid, buffer); + } + + return buffer; } - private String write(Polygon polygon, int dimension, int srid) { - ByteBuffer buffer = ByteBuffer.allocate(calculateBytes(polygon, dimension, srid)); - put(buffer, polygon, dimension, srid); - return toHex(buffer.array()); + private void write(Point point, int dimension, int srid, ByteBuffer buffer) { + putByteOrder(buffer); + putGeometryType(buffer, WKBConstants.POINT, dimension, srid); + putCoordinate(buffer, point.getCoordinate(), dimension); } - private String write(SurfaceCollection surfaces, int dimension, int srid) { - ByteBuffer buffer = ByteBuffer.allocate(calculateBytes(surfaces, dimension, srid)); - put(buffer, surfaces, dimension, srid); - return toHex(buffer.array()); + private void write(MultiPoint multiPoint, int dimension, int srid, ByteBuffer buffer) { + putByteOrder(buffer); + putGeometryType(buffer, WKBConstants.MULTIPOINT, dimension, srid); + buffer.putInt(multiPoint.getPoints().size()); + for (Point point : multiPoint.getPoints()) { + write(point, dimension, srid, buffer); + } } - private String write(Solid solid, int dimension, int srid) { - ByteBuffer buffer = ByteBuffer.allocate(calculateBytes(solid, dimension, srid)); - put(buffer, solid, dimension, srid); - return toHex(buffer.array()); + private void write(MultiLineString multiLineString, int dimension, int srid, ByteBuffer buffer) { + putByteOrder(buffer); + putGeometryType(buffer, WKBConstants.MULTILINESTRING, dimension, srid); + buffer.putInt(multiLineString.getLineStrings().size()); + for (LineString lineString : multiLineString.getLineStrings()) { + write(lineString, dimension, srid, buffer); + } } - private String write(SolidCollection solids, int dimension, int srid) { - ByteBuffer buffer = ByteBuffer.allocate(calculateBytes(solids, dimension, srid)); - put(buffer, solids, dimension, srid); - return toHex(buffer.array()); + private void write(LineString lineString, int dimension, int srid, ByteBuffer buffer) { + putByteOrder(buffer); + putGeometryType(buffer, WKBConstants.LINESTRING, dimension, srid); + if (!lineString.getPoints().isEmpty()) { + putCoordinates(buffer, lineString.getPoints(), dimension); + } } - private int calculateBytes(Point point, int dimension, int srid) { - return getHeaderBytes(srid) + getCoordinatesBytes(1, dimension); + private void write(Polygon polygon, int dimension, int srid, ByteBuffer buffer) { + putByteOrder(buffer); + putGeometryType(buffer, WKBConstants.POLYGON, dimension, srid); + + int numberOfRings = 1 + (polygon.hasInteriorRings() ? polygon.getInteriorRings().size() : 0); + buffer.putInt(numberOfRings); + + putCoordinates(buffer, polygon.getExteriorRing().getPoints(), dimension); + if (polygon.hasInteriorRings()) { + for (LinearRing interiorRing : polygon.getInteriorRings()) { + putCoordinates(buffer, interiorRing.getPoints(), dimension); + } + } } - private void put(ByteBuffer buffer, Point point, int dimension, int srid) { + private void write(SurfaceCollection surfaces, int dimension, int srid, ByteBuffer buffer) { putByteOrder(buffer); - putGeometryType(buffer, WKBConstants.POINT, dimension, srid); - putCoordinate(buffer, point.getCoordinate(), dimension); + putGeometryType(buffer, WKBConstants.MULTIPOLYGON, dimension, srid); + buffer.putInt(surfaces.getPolygons().size()); + for (Polygon polygon : surfaces.getPolygons()) { + write(polygon, dimension, srid, buffer); + } } - private int calculateBytes(MultiPoint multiPoint, int dimension, int srid) { - return 4 + getHeaderBytes(srid) + - ((getHeaderBytes(srid) + getCoordinatesBytes(1, dimension)) * multiPoint.getPoints().size()); + private void write(Solid solid, int dimension, int srid, ByteBuffer buffer) { + putByteOrder(buffer); + putGeometryType(buffer, WKBConstants.POLYHEDRALSURFACE, dimension, srid); + buffer.putInt(solid.getShell().getPolygons().size()); + for (Polygon polygon : solid.getShell().getPolygons()) { + write(polygon, dimension, srid, buffer); + } } - private void put(ByteBuffer buffer, MultiPoint multiPoint, int dimension, int srid) { + private void write(SolidCollection solids, int dimension, int srid, ByteBuffer buffer) { putByteOrder(buffer); - putGeometryType(buffer, WKBConstants.MULTIPOINT, dimension, srid); - buffer.putInt(multiPoint.getPoints().size()); - for (Point point : multiPoint.getPoints()) { - put(buffer, point, dimension, srid); + putGeometryType(buffer, WKBConstants.GEOMETRYCOLLECTION, dimension, srid); + buffer.putInt(solids.getSolids().size()); + for (Solid solid : solids.getSolids()) { + write(solid, dimension, srid, buffer); } } - private int calculateBytes(LineString lineString, int dimension, int srid) { + private int calculateBytes(Geometry geometry, int dimension, int srid) { + return switch (geometry.getGeometryType()) { + case POINT -> calculateBytes((Point) geometry, dimension, srid); + case MULTI_POINT -> calculateBytes((MultiPoint) geometry, dimension, srid); + case LINE_STRING -> calculateBytes((LineString) geometry, dimension, srid); + case MULTI_LINE_STRING -> calculateBytes((MultiLineString) geometry, dimension, srid); + case POLYGON -> calculateBytes((Polygon) geometry, dimension, srid); + case MULTI_SURFACE, COMPOSITE_SURFACE, TRIANGULATED_SURFACE -> + calculateBytes((SurfaceCollection) geometry, dimension, srid); + case SOLID -> calculateBytes((Solid) geometry, dimension, srid); + case COMPOSITE_SOLID, MULTI_SOLID -> calculateBytes((SolidCollection) geometry, dimension, srid); + }; + } + + private int calculateBytes(Point point, int dimension, int srid) { + return getHeaderBytes(srid) + getCoordinatesBytes(1, dimension); + } + + private int calculateBytes(MultiPoint multiPoint, int dimension, int srid) { return 4 + getHeaderBytes(srid) + - getCoordinatesBytes(lineString.getPoints().size(), dimension); + ((getHeaderBytes(srid) + getCoordinatesBytes(1, dimension)) * multiPoint.getPoints().size()); } - private void put(ByteBuffer buffer, LineString lineString, int dimension, int srid) { - putByteOrder(buffer); - putGeometryType(buffer, WKBConstants.LINESTRING, dimension, srid); - if (!lineString.getPoints().isEmpty()) { - putCoordinates(buffer, lineString.getPoints(), dimension); - } + private int calculateBytes(LineString lineString, int dimension, int srid) { + return 4 + getHeaderBytes(srid) + + getCoordinatesBytes(lineString.getPoints().size(), dimension); } private int calculateBytes(MultiLineString multiLineString, int dimension, int srid) { @@ -162,15 +194,6 @@ private int calculateBytes(MultiLineString multiLineString, int dimension, int s return numberOfBytes; } - private void put(ByteBuffer buffer, MultiLineString multiLineString, int dimension, int srid) { - putByteOrder(buffer); - putGeometryType(buffer, WKBConstants.MULTILINESTRING, dimension, srid); - buffer.putInt(multiLineString.getLineStrings().size()); - for (LineString lineString : multiLineString.getLineStrings()) { - put(buffer, lineString, dimension, srid); - } - } - private int calculateBytes(Polygon polygon, int dimension, int srid) { int numberOfRings = 1; int numberOfCoordinates = polygon.getExteriorRing().getPoints().size(); @@ -188,21 +211,6 @@ private int calculateBytes(Polygon polygon, int dimension, int srid) { getCoordinatesBytes(numberOfCoordinates, dimension); } - private void put(ByteBuffer buffer, Polygon polygon, int dimension, int srid) { - putByteOrder(buffer); - putGeometryType(buffer, WKBConstants.POLYGON, dimension, srid); - - int numberOfRings = 1 + (polygon.hasInteriorRings() ? polygon.getInteriorRings().size() : 0); - buffer.putInt(numberOfRings); - - putCoordinates(buffer, polygon.getExteriorRing().getPoints(), dimension); - if (polygon.hasInteriorRings()) { - for (LinearRing interiorRing : polygon.getInteriorRings()) { - putCoordinates(buffer, interiorRing.getPoints(), dimension); - } - } - } - private int calculateBytes(SurfaceCollection surfaces, int dimension, int srid) { int numberOfBytes = 4 + getHeaderBytes(srid); for (Polygon polygon : surfaces.getPolygons()) { @@ -212,15 +220,6 @@ private int calculateBytes(SurfaceCollection surfaces, int dimension, int sri return numberOfBytes; } - private void put(ByteBuffer buffer, SurfaceCollection surfaces, int dimension, int srid) { - putByteOrder(buffer); - putGeometryType(buffer, WKBConstants.MULTIPOLYGON, dimension, srid); - buffer.putInt(surfaces.getPolygons().size()); - for (Polygon polygon : surfaces.getPolygons()) { - put(buffer, polygon, dimension, srid); - } - } - private int calculateBytes(Solid solid, int dimension, int srid) { int numberOfBytes = 4 + getHeaderBytes(srid); for (Polygon polygon : solid.getShell().getPolygons()) { @@ -230,15 +229,6 @@ private int calculateBytes(Solid solid, int dimension, int srid) { return numberOfBytes; } - private void put(ByteBuffer buffer, Solid solid, int dimension, int srid) { - putByteOrder(buffer); - putGeometryType(buffer, WKBConstants.POLYHEDRALSURFACE, dimension, srid); - buffer.putInt(solid.getShell().getPolygons().size()); - for (Polygon polygon : solid.getShell().getPolygons()) { - put(buffer, polygon, dimension, srid); - } - } - private int calculateBytes(SolidCollection solids, int dimension, int srid) { int numberOfBytes = 4 + getHeaderBytes(srid); for (Solid solid : solids.getSolids()) { @@ -248,15 +238,6 @@ private int calculateBytes(SolidCollection solids, int dimension, int srid) { return numberOfBytes; } - private void put(ByteBuffer buffer, SolidCollection solids, int dimension, int srid) { - putByteOrder(buffer); - putGeometryType(buffer, WKBConstants.GEOMETRYCOLLECTION, dimension, srid); - buffer.putInt(solids.getSolids().size()); - for (Solid solid : solids.getSolids()) { - put(buffer, solid, dimension, srid); - } - } - private int getHeaderBytes(int srid) { int numberOfBytes = 1 + 4; if (srid > 0) { @@ -271,13 +252,7 @@ private int getCoordinatesBytes(int numberOfCoordinates, int dimension) { } private void putByteOrder(ByteBuffer buffer) { - if (useBigEndian) { - buffer.order(ByteOrder.BIG_ENDIAN); - buffer.put((byte) 0); - } else { - buffer.order(ByteOrder.LITTLE_ENDIAN); - buffer.put((byte) 1); - } + buffer.put(useBigEndian ? (byte) 0 : (byte) 1); } private void putGeometryType(ByteBuffer buffer, int geometryType, int dimension, int srid) { @@ -304,14 +279,84 @@ private void putCoordinates(ByteBuffer buffer, List coordinates, int } } - private String toHex(byte[] bytes) { - char[] hexChars = new char[bytes.length * 2]; - for (int i = 0; i < bytes.length; i++) { - int v = bytes[i] & 0xFF; - hexChars[i * 2] = hexArray[v >>> 4]; - hexChars[i * 2 + 1] = hexArray[v & 0x0F]; + private static abstract class ByteBuffer { + final int[] bigEndian = new int[]{1, 0, 1, 2, 3, 4, 5, 6, 7}; + final int[] littleEndian = new int[]{5, 7, 6, 5, 4, 3, 2, 1, 0}; + final int[] indexes; + int pos; + + ByteBuffer(boolean useBigEndian) { + indexes = useBigEndian ? bigEndian : littleEndian; + } + + abstract void put(byte b, int index); + + void put(byte b) { + put(b, pos++); + } + + void putInt(int value) { + int low = indexes[0]; + put((byte) (value >>> 24), pos + indexes[low]); + put((byte) (value >>> 16), pos + indexes[low + 1]); + put((byte) (value >>> 8), pos + indexes[low + 2]); + put((byte) value, pos + indexes[low + 3]); + pos += 4; + } + + void putLong(long value) { + put((byte) (value >>> 56), pos + indexes[1]); + put((byte) (value >>> 48), pos + indexes[2]); + put((byte) (value >>> 40), pos + indexes[3]); + put((byte) (value >>> 32), pos + indexes[4]); + put((byte) (value >>> 24), pos + indexes[5]); + put((byte) (value >>> 16), pos + indexes[6]); + put((byte) (value >>> 8), pos + indexes[7]); + put((byte) value, pos + indexes[8]); + pos += 8; } - return new String(hexChars); + void putDouble(double value) { + putLong(Double.doubleToLongBits(value)); + } + } + + private static class StringBuffer extends ByteBuffer { + final static char[] hexArray = "0123456789ABCDEF".toCharArray(); + final char[] data; + + StringBuffer(int length, boolean useBigEndian) { + super(useBigEndian); + data = new char[length * 2]; + } + + @Override + void put(byte b, int index) { + index *= 2; + data[index] = hexArray[(b >>> 4) & 0xF]; + data[index + 1] = hexArray[b & 0xF]; + } + + String build() { + return new String(data); + } + } + + private static class ArrayBuffer extends ByteBuffer { + final byte[] data; + + ArrayBuffer(int length, boolean useBigEndian) { + super(useBigEndian); + data = new byte[length]; + } + + @Override + void put(byte b, int index) { + data[index] = b; + } + + byte[] build() { + return data; + } } } \ No newline at end of file