Unverified 提交 ba63ffc6 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov 提交者: GitHub

Merge pull request #1428 from katzyn/geometry

Add support for M and ZM dimensions to GEOMETRY data type
......@@ -3038,9 +3038,14 @@ ENUM('clubs', 'diamonds', 'hearts', 'spades')
"Data Types","GEOMETRY Type","
GEOMETRY
","
A spatial geometry type, based on the ""org.locationtech.jts"" library.
A spatial geometry type.
Mapped to ""org.locationtech.jts.geom.Geometry"" if JTS library is in classpath.
May be represented in textual format using the WKT (well-known text) or EWKT (extended well-known text) format.
Values are stored internally in EWKB (extended well-known binary) format.
Only a subset of EWKB and EWKT features is supported.
Supported objects are POINT, LINESTRING, POLYGON, MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, and GEOMETRYCOLLECTION.
Supported dimension systems are XY (2D), XYZ, XYM, and XYZM.
SRID (spatial reference system identifier) is supported.
Use a quoted string containing a WKT/EWKT formatted string or ""PreparedStatement.setObject()"" to store values,
and ""ResultSet.getObject(..)"" or ""ResultSet.getString(..)"" to retrieve the values.
......@@ -3437,7 +3442,7 @@ MODE(X ORDER BY X)
MODE() WITHIN GROUP(ORDER BY X)
"
"Functions (Aggregate)","ST_EXTENT","
"Functions (Aggregate)","ENVELOPE","
ENVELOPE( value ) [ FILTER ( WHERE expression ) ]
","
Returns the minimum bounding box that encloses all specified GEOMETRY values.
......
......@@ -1415,19 +1415,7 @@ that means the probability is about 0.000'000'000'06.
<h2 id="spatial_features">Spatial Features</h2>
<p>
H2 supports the geometry data type and spatial indexes if
the <a href="https://projects.eclipse.org/projects/locationtech.jts">JTS Topology Suite</a>
is in the classpath.
To run the H2 Console tool with the JTS tool, you need to download the
<a href="https://search.maven.org/remotecontent?filepath=org/locationtech/jts/jts-core/1.15.0/jts-core-1.15.0.jar">JTS-CORE 1.15.0 jar file</a>
and place it in the h2 bin directory. Then edit the <code>h2.sh</code> file as follows:
</p>
<pre>
#!/bin/sh
dir=$(dirname "$0")
java -cp "$dir/h2.jar:jts-core-1.15.0.jar:$H2DRIVERS:$CLASSPATH" org.h2.tools.Console "$@"
</pre>
<p>
H2 supports the geometry data type and spatial indexes.
Here is an example SQL script to create a table with a spatial column and index:
</p>
<pre>
......
......@@ -18,16 +18,45 @@ import java.util.UUID;
public final class Bits {
/**
* VarHandle giving access to elements of a byte[] array viewed as if it were a
* int[] array on big-endian system.
* VarHandle giving access to elements of a byte[] array viewed as if it
* were a int[] array on big-endian system.
*/
private static final VarHandle INT_VH = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN);
private static final VarHandle INT_VH_BE = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN);
/**
* VarHandle giving access to elements of a byte[] array viewed as if it were a
* long[] array on big-endian system.
* VarHandle giving access to elements of a byte[] array viewed as if it
* were a int[] array on little-endian system.
*/
private static final VarHandle LONG_VH = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN);
private static final VarHandle INT_VH_LE = MethodHandles.byteArrayViewVarHandle(int[].class,
ByteOrder.LITTLE_ENDIAN);
/**
* VarHandle giving access to elements of a byte[] array viewed as if it
* were a long[] array on big-endian system.
*/
private static final VarHandle LONG_VH_BE = MethodHandles.byteArrayViewVarHandle(long[].class,
ByteOrder.BIG_ENDIAN);
/**
* VarHandle giving access to elements of a byte[] array viewed as if it
* were a long[] array on little-endian system.
*/
private static final VarHandle LONG_VH_LE = MethodHandles.byteArrayViewVarHandle(long[].class,
ByteOrder.LITTLE_ENDIAN);
/**
* VarHandle giving access to elements of a byte[] array viewed as if it
* were a double[] array on big-endian system.
*/
private static final VarHandle DOUBLE_VH_BE = MethodHandles.byteArrayViewVarHandle(double[].class,
ByteOrder.BIG_ENDIAN);
/**
* VarHandle giving access to elements of a byte[] array viewed as if it
* were a double[] array on little-endian system.
*/
private static final VarHandle DOUBLE_VH_LE = MethodHandles.byteArrayViewVarHandle(double[].class,
ByteOrder.LITTLE_ENDIAN);
/**
* Compare the contents of two char arrays. If the content or length of the
......@@ -96,12 +125,26 @@ public final class Bits {
* @return the value
*/
public static int readInt(byte[] buff, int pos) {
return (int) INT_VH.get(buff, pos);
return (int) INT_VH_BE.get(buff, pos);
}
/**
* Reads a long value from the byte array at the given position in big-endian
* order.
* Reads a int value from the byte array at the given position in
* little-endian order.
*
* @param buff
* the byte array
* @param pos
* the position
* @return the value
*/
public static int readIntLE(byte[] buff, int pos) {
return (int) INT_VH_LE.get(buff, pos);
}
/**
* Reads a long value from the byte array at the given position in
* big-endian order.
*
* @param buff
* the byte array
......@@ -110,7 +153,49 @@ public final class Bits {
* @return the value
*/
public static long readLong(byte[] buff, int pos) {
return (long) LONG_VH.get(buff, pos);
return (long) LONG_VH_BE.get(buff, pos);
}
/**
* Reads a long value from the byte array at the given position in
* little-endian order.
*
* @param buff
* the byte array
* @param pos
* the position
* @return the value
*/
public static long readLongLE(byte[] buff, int pos) {
return (long) LONG_VH_LE.get(buff, pos);
}
/**
* Reads a double value from the byte array at the given position in
* big-endian order.
*
* @param buff
* the byte array
* @param pos
* the position
* @return the value
*/
public static double readDouble(byte[] buff, int pos) {
return (double) DOUBLE_VH_BE.get(buff, pos);
}
/**
* Reads a double value from the byte array at the given position in
* little-endian order.
*
* @param buff
* the byte array
* @param pos
* the position
* @return the value
*/
public static double readDoubleLE(byte[] buff, int pos) {
return (double) DOUBLE_VH_LE.get(buff, pos);
}
/**
......@@ -124,8 +209,8 @@ public final class Bits {
*/
public static byte[] uuidToBytes(long msb, long lsb) {
byte[] buff = new byte[16];
LONG_VH.set(buff, 0, msb);
LONG_VH.set(buff, 8, lsb);
LONG_VH_BE.set(buff, 0, msb);
LONG_VH_BE.set(buff, 8, lsb);
return buff;
}
......@@ -152,7 +237,22 @@ public final class Bits {
* the value to write
*/
public static void writeInt(byte[] buff, int pos, int x) {
INT_VH.set(buff, pos, x);
INT_VH_BE.set(buff, pos, x);
}
/**
* Writes a int value to the byte array at the given position in
* little-endian order.
*
* @param buff
* the byte array
* @param pos
* the position
* @param x
* the value to write
*/
public static void writeIntLE(byte[] buff, int pos, int x) {
INT_VH_LE.set(buff, pos, x);
}
/**
......@@ -167,7 +267,52 @@ public final class Bits {
* the value to write
*/
public static void writeLong(byte[] buff, int pos, long x) {
LONG_VH.set(buff, pos, x);
LONG_VH_BE.set(buff, pos, x);
}
/**
* Writes a long value to the byte array at the given position in
* little-endian order.
*
* @param buff
* the byte array
* @param pos
* the position
* @param x
* the value to write
*/
public static void writeLongLE(byte[] buff, int pos, long x) {
LONG_VH_LE.set(buff, pos, x);
}
/**
* Writes a double value to the byte array at the given position in
* big-endian order.
*
* @param buff
* the byte array
* @param pos
* the position
* @param x
* the value to write
*/
public static void writeDouble(byte[] buff, int pos, double x) {
DOUBLE_VH_BE.set(buff, pos, x);
}
/**
* Writes a double value to the byte array at the given position in
* little-endian order.
*
* @param buff
* the byte array
* @param pos
* the position
* @param x
* the value to write
*/
public static void writeDoubleLE(byte[] buff, int pos, double x) {
DOUBLE_VH_LE.set(buff, pos, x);
}
private Bits() {
......
......@@ -565,6 +565,18 @@ public class SysProperties {
public static final String AUTH_CONFIG_FILE =
Utils.getProperty("h2.authConfigFile", null);
/**
* System property {@code h2.mixedGeometries}, {@code false} by default.
* <p>
* If {@code true} illegal geometries with mixed XY/XYZ dimensionality like
* {@code 'LINESTRING (1 2, 3 4 5)'} are accepted.
* </p>
* <p>
* If {@code false} such geometries are rejected with data conversion error.
* </p>
*/
public static final boolean MIXED_GEOMETRIES = Utils.getProperty("h2.mixedGeometries", false);
private static final String H2_BASE_DIR = "h2.baseDir";
private SysProperties() {
......
......@@ -14,18 +14,17 @@ import org.h2.index.Index;
import org.h2.mvstore.db.MVSpatialIndex;
import org.h2.table.Column;
import org.h2.table.TableFilter;
import org.h2.util.geometry.GeometryUtils;
import org.h2.value.Value;
import org.h2.value.ValueGeometry;
import org.h2.value.ValueNull;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.GeometryFactory;
/**
* Data stored while calculating an aggregate.
*/
class AggregateDataEnvelope extends AggregateData {
private Envelope envelope;
private double[] envelope;
/**
* Get the index (if any) for the column specified in the geometry
......@@ -62,18 +61,12 @@ class AggregateDataEnvelope extends AggregateData {
if (v == ValueNull.INSTANCE) {
return;
}
if (envelope == null) {
envelope = new Envelope();
}
envelope.expandToInclude(((ValueGeometry) v.convertTo(Value.GEOMETRY)).getEnvelopeNoCopy());
envelope = GeometryUtils.union(envelope, ((ValueGeometry) v.convertTo(Value.GEOMETRY)).getEnvelopeNoCopy());
}
@Override
Value getValue(Database database, int dataType, boolean distinct) {
if (envelope == null || envelope.isNull()) {
return ValueNull.INSTANCE;
}
return ValueGeometry.getFromGeometry(new GeometryFactory().toGeometry(envelope));
return ValueGeometry.fromEnvelope(envelope);
}
}
......@@ -5,6 +5,11 @@
*/
package org.h2.index;
import static org.h2.util.geometry.GeometryUtils.MAX_X;
import static org.h2.util.geometry.GeometryUtils.MAX_Y;
import static org.h2.util.geometry.GeometryUtils.MIN_X;
import static org.h2.util.geometry.GeometryUtils.MIN_Y;
import java.util.Iterator;
import org.h2.command.dml.AllColumnsForPlan;
import org.h2.engine.Session;
......@@ -23,7 +28,6 @@ import org.h2.table.TableFilter;
import org.h2.value.Value;
import org.h2.value.ValueGeometry;
import org.h2.value.ValueNull;
import org.locationtech.jts.geom.Envelope;
/**
* This is an index based on a MVR-TreeMap.
......@@ -130,13 +134,13 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
return null;
}
Value v = row.getValue(columnIds[0]);
if (v == ValueNull.INSTANCE) {
double[] env;
if (v == ValueNull.INSTANCE ||
(env = ((ValueGeometry) v.convertTo(Value.GEOMETRY)).getEnvelopeNoCopy()) == null) {
return new SpatialKey(row.getKey());
}
Envelope env = ((ValueGeometry) v.convertTo(Value.GEOMETRY)).getEnvelopeNoCopy();
return new SpatialKey(row.getKey(),
(float) env.getMinX(), (float) env.getMaxX(),
(float) env.getMinY(), (float) env.getMaxY());
(float) env[MIN_X], (float) env[MAX_X], (float) env[MIN_Y], (float) env[MAX_Y]);
}
@Override
......
......@@ -5,6 +5,11 @@
*/
package org.h2.mvstore.db;
import static org.h2.util.geometry.GeometryUtils.MAX_X;
import static org.h2.util.geometry.GeometryUtils.MAX_Y;
import static org.h2.util.geometry.GeometryUtils.MIN_X;
import static org.h2.util.geometry.GeometryUtils.MIN_Y;
import java.util.Iterator;
import java.util.List;
import org.h2.api.ErrorCode;
......@@ -33,8 +38,6 @@ import org.h2.value.Value;
import org.h2.value.ValueGeometry;
import org.h2.value.ValueLong;
import org.h2.value.ValueNull;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.GeometryFactory;
/**
* This is an index based on a MVRTreeMap.
......@@ -264,21 +267,21 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex, MVIndex {
bmaxyf = maxyf;
}
}
return ValueGeometry.getFromGeometry(new GeometryFactory().toGeometry(
new Envelope(bminxf, bmaxxf, bminyf, bmaxyf)));
return ValueGeometry.fromEnvelope(new double[] {bminxf, bmaxxf, bminyf, bmaxyf});
}
return ValueNull.INSTANCE;
}
private SpatialKey getKey(SearchRow row) {
Value v = row.getValue(columnIds[0]);
if (v == ValueNull.INSTANCE) {
double[] env;
if (v == ValueNull.INSTANCE ||
(env = ((ValueGeometry) v.convertTo(Value.GEOMETRY)).getEnvelopeNoCopy()) == null) {
return new SpatialKey(row.getKey());
}
Envelope env = ((ValueGeometry) v.convertTo(Value.GEOMETRY)).getEnvelopeNoCopy();
return new SpatialKey(row.getKey(),
(float) env.getMinX(), (float) env.getMaxX(),
(float) env.getMinY(), (float) env.getMaxY());
(float) env[MIN_X], (float) env[MAX_X],
(float) env[MIN_Y], (float) env[MAX_Y]);
}
@Override
......@@ -467,10 +470,9 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex, MVIndex {
if (hasBounds) {
if ((minxf <= bminxf || maxxf >= bmaxxf || minyf <= bminyf || maxyf >= bmaxyf)
&& map.containsKey(key)) {
Envelope env = ((ValueGeometry) mvTable.getRow(session, key.getId()).getValue(columnId))
double[] env = ((ValueGeometry) mvTable.getRow(session, key.getId()).getValue(columnId))
.getEnvelopeNoCopy();
double minxd = env.getMinX(), maxxd = env.getMaxX(), minyd = env.getMinY(),
maxyd = env.getMaxY();
double minxd = env[MIN_X], maxxd = env[MAX_X], minyd = env[MIN_Y], maxyd = env[MAX_Y];
if (minxd < bminxd) {
bminxf = minxf;
bminxd = minxd;
......@@ -490,16 +492,16 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex, MVIndex {
}
} else if (map.containsKey(key)) {
hasBounds = true;
Envelope env = ((ValueGeometry) mvTable.getRow(session, key.getId()).getValue(columnId))
double[] env = ((ValueGeometry) mvTable.getRow(session, key.getId()).getValue(columnId))
.getEnvelopeNoCopy();
bminxf = minxf;
bminxd = env.getMinX();
bminxd = env[MIN_X];
bmaxxf = maxxf;
bmaxxd = env.getMaxX();
bmaxxd = env[MAX_X];
bminyf = minyf;
bminyd = env.getMinY();
bminyd = env[MIN_Y];
bmaxyf = maxyf;
bmaxyd = env.getMaxY();
bmaxyd = env[MAX_Y];
}
} else if (hasBounds) {
if (minxf <= bminxf || maxxf >= bmaxxf || minyf <= bminyf || maxyf >= bmaxyf) {
......@@ -512,8 +514,8 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex, MVIndex {
}
Value getBounds() {
return hasBounds ? ValueGeometry.getFromGeometry(new GeometryFactory().toGeometry(
new Envelope(bminxd, bmaxxd, bminyd, bmaxyd))) : ValueNull.INSTANCE;
return hasBounds ? ValueGeometry.fromEnvelope(new double[] {bminxd, bmaxxd, bminyd, bmaxyd})
: ValueNull.INSTANCE;
}
}
......
......@@ -124,8 +124,22 @@ public final class Bits {
}
/**
* Reads a long value from the byte array at the given position in big-endian
* order.
* Reads a int value from the byte array at the given position in
* little-endian order.
*
* @param buff
* the byte array
* @param pos
* the position
* @return the value
*/
public static int readIntLE(byte[] buff, int pos) {
return (buff[pos++] & 0xff) + ((buff[pos++] & 0xff) << 8) + ((buff[pos++] & 0xff) << 16) + (buff[pos] << 24);
}
/**
* Reads a long value from the byte array at the given position in
* big-endian order.
*
* @param buff
* the byte array
......@@ -134,7 +148,49 @@ public final class Bits {
* @return the value
*/
public static long readLong(byte[] buff, int pos) {
return (((long) readInt(buff, pos)) << 32) + (readInt(buff, pos + 4) & 0xffffffffL);
return (((long) readInt(buff, pos)) << 32) + (readInt(buff, pos + 4) & 0xffff_ffffL);
}
/**
* Reads a long value from the byte array at the given position in
* little-endian order.
*
* @param buff
* the byte array
* @param pos
* the position
* @return the value
*/
public static long readLongLE(byte[] buff, int pos) {
return (readIntLE(buff, pos) & 0xffff_ffffL) + (((long) readIntLE(buff, pos + 4)) << 32);
}
/**
* Reads a double value from the byte array at the given position in
* big-endian order.
*
* @param buff
* the byte array
* @param pos
* the position
* @return the value
*/
public static double readDouble(byte[] buff, int pos) {
return Double.longBitsToDouble(readLong(buff, pos));
}
/**
* Reads a double value from the byte array at the given position in
* little-endian order.
*
* @param buff
* the byte array
* @param pos
* the position
* @return the value
*/
public static double readDoubleLE(byte[] buff, int pos) {
return Double.longBitsToDouble(readLongLE(buff, pos));
}
/**
......@@ -184,6 +240,24 @@ public final class Bits {
buff[pos] = (byte) x;
}
/**
* Writes a int value to the byte array at the given position in
* little-endian order.
*
* @param buff
* the byte array
* @param pos
* the position
* @param x
* the value to write
*/
public static void writeIntLE(byte[] buff, int pos, int x) {
buff[pos++] = (byte) x;
buff[pos++] = (byte) (x >> 8);
buff[pos++] = (byte) (x >> 16);
buff[pos] = (byte) (x >> 24);
}
/**
* Writes a long value to the byte array at the given position in big-endian
* order.
......@@ -200,6 +274,52 @@ public final class Bits {
writeInt(buff, pos + 4, (int) x);
}
/**
* Writes a long value to the byte array at the given position in
* little-endian order.
*
* @param buff
* the byte array
* @param pos
* the position
* @param x
* the value to write
*/
public static void writeLongLE(byte[] buff, int pos, long x) {
writeIntLE(buff, pos, (int) x);
writeIntLE(buff, pos + 4, (int) (x >> 32));
}
/**
* Writes a double value to the byte array at the given position in
* big-endian order.
*
* @param buff
* the byte array
* @param pos
* the position
* @param x
* the value to write
*/
public static void writeDouble(byte[] buff, int pos, double x) {
writeLong(buff, pos, Double.doubleToRawLongBits(x));
}
/**
* Writes a double value to the byte array at the given position in
* little-endian order.
*
* @param buff
* the byte array
* @param pos
* the position
* @param x
* the value to write
*/
public static void writeDoubleLE(byte[] buff, int pos, double x) {
writeLongLE(buff, pos, Double.doubleToRawLongBits(x));
}
private Bits() {
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.util.geometry;
import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYM;
import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYZ;
import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYZM;
import static org.h2.util.geometry.GeometryUtils.GEOMETRY_COLLECTION;
import static org.h2.util.geometry.GeometryUtils.LINE_STRING;
import static org.h2.util.geometry.GeometryUtils.MAX_X;
import static org.h2.util.geometry.GeometryUtils.MAX_Y;
import static org.h2.util.geometry.GeometryUtils.MIN_X;
import static org.h2.util.geometry.GeometryUtils.MIN_Y;
import static org.h2.util.geometry.GeometryUtils.MULTI_LINE_STRING;
import static org.h2.util.geometry.GeometryUtils.MULTI_POINT;
import static org.h2.util.geometry.GeometryUtils.MULTI_POLYGON;
import static org.h2.util.geometry.GeometryUtils.POINT;
import static org.h2.util.geometry.GeometryUtils.POLYGON;
import static org.h2.util.geometry.GeometryUtils.checkFinite;
import static org.h2.util.geometry.GeometryUtils.toCanonicalDouble;
import java.io.ByteArrayOutputStream;
import org.h2.engine.SysProperties;
import org.h2.util.Bits;
import org.h2.util.StringUtils;
import org.h2.util.geometry.GeometryUtils.DimensionSystemTarget;
import org.h2.util.geometry.GeometryUtils.Target;
/**
* EWKB format support for GEOMETRY data type.
*
* <p>
* This class provides limited support of EWKB. EWKB is based on Well-known
* Binary Representation (WKB) from OGC 06-103r4 and includes additional PostGIS
* extensions. This class can read dimension system marks in both OGC WKB and
* EWKB formats, but always writes them in EWKB format. SRID support from EWKB
* is implemented. As an addition POINT EMPTY is stored with NaN values as
* specified in <a href="http://www.geopackage.org/spec/">OGC 12-128r15</a>.
* </p>
*/
public final class EWKBUtils {
/**
* Converter output target that writes a EWKB.
*/
public static final class EWKBTarget extends Target {
private final ByteArrayOutputStream output;
private final int dimensionSystem;
private final byte[] buf = new byte[8];
private int type;
private int srid;
/**
* Creates a new EWKB output target.
*
* @param output
* output stream
* @param dimensionSystem
* dimension system to use
*/
public EWKBTarget(ByteArrayOutputStream output, int dimensionSystem) {
this.output = output;
this.dimensionSystem = dimensionSystem;
}
@Override
protected void init(int srid) {
this.srid = srid;
}
@Override
protected void startPoint() {
writeHeader(POINT);
}
@Override
protected void startLineString(int numPoints) {
writeHeader(LINE_STRING);
writeInt(numPoints);
}
@Override
protected void startPolygon(int numInner, int numPoints) {
writeHeader(POLYGON);
writeInt(numInner + 1);
writeInt(numPoints);
}
@Override
protected void startPolygonInner(int numInner) {
writeInt(numInner);
}
@Override
protected void startCollection(int type, int numItems) {
writeHeader(type);
writeInt(numItems);
}
private void writeHeader(int type) {
this.type = type;
switch (dimensionSystem) {
case DIMENSION_SYSTEM_XYZ:
type |= EWKB_Z;
break;
case DIMENSION_SYSTEM_XYZM:
type |= EWKB_Z;
//$FALL-THROUGH$
case DIMENSION_SYSTEM_XYM:
type |= EWKB_M;
}
if (srid != 0) {
type |= EWKB_SRID;
}
output.write(0);
writeInt(type);
if (srid != 0) {
writeInt(srid);
// Never write SRID in inner objects
srid = 0;
}
}
@Override
protected Target startCollectionItem(int index, int total) {
return this;
}
@Override
protected void addCoordinate(double x, double y, double z, double m, int index, int total) {
boolean check = type != POINT || !Double.isNaN(x) || !Double.isNaN(y) || !Double.isNaN(z)
|| !Double.isNaN(m);
if (check) {
checkFinite(x);
checkFinite(y);
}
writeDouble(x);
writeDouble(y);
if ((dimensionSystem & DIMENSION_SYSTEM_XYZ) != 0) {
writeDouble(!SysProperties.MIXED_GEOMETRIES && check ? checkFinite(z) : z);
}
if ((dimensionSystem & DIMENSION_SYSTEM_XYM) != 0) {
writeDouble(check ? checkFinite(m) : m);
}
}
private void writeInt(int v) {
Bits.writeInt(buf, 0, v);
output.write(buf, 0, 4);
}
private void writeDouble(double v) {
v = toCanonicalDouble(v);
Bits.writeDouble(buf, 0, v);
output.write(buf, 0, 8);
}
}
/**
* Helper source object for EWKB reading.
*/
private static final class EWKBSource {
private final byte[] ewkb;
private int offset;
/**
* Whether current byte order is big-endian.
*/
boolean bigEndian;
/**
* Creates new instance of EWKB source.
*
* @param ewkb
* EWKB
*/
EWKBSource(byte[] ewkb) {
this.ewkb = ewkb;
}
/**
* Reads one byte.
*
* @return next byte
*/
byte readByte() {
return ewkb[offset++];
}
/**
* Reads a 32-bit integer using current byte order.
*
* @return next 32-bit integer
*/
int readInt() {
int result = bigEndian ? Bits.readInt(ewkb, offset) : Bits.readIntLE(ewkb, offset);
offset += 4;
return result;
}
/**
* Reads a 64-bit floating point using current byte order.
*
* @return next 64-bit floating point
*/
double readCoordinate() {
double v = bigEndian ? Bits.readDouble(ewkb, offset) : Bits.readDoubleLE(ewkb, offset);
offset += 8;
return toCanonicalDouble(v);
}
@Override
public String toString() {
String s = StringUtils.convertBytesToHex(ewkb);
int idx = offset * 2;
return new StringBuilder(s.length() + 3).append(s, 0, idx).append("<*>").append(s, idx, s.length())
.toString();
}
}
/**
* Geometry type mask that indicates presence of dimension Z.
*/
private static final int EWKB_Z = 0x8000_0000;
/**
* Geometry type mask that indicates presence of dimension M.
*/
private static final int EWKB_M = 0x4000_0000;
/**
* Geometry type mask that indicates presence of SRID.
*/
private static final int EWKB_SRID = 0x2000_0000;
/**
* Converts any supported EWKB to EWKB representation that is used by this
* class. Reduces dimension system to minimal possible and uses EWKB flags
* for dimension system indication. May also perform other changes.
*
* @param ewkb
* source EWKB
* @return canonical EWKB, may be the same as the source
*/
public static byte[] ewkb2ewkb(byte[] ewkb) {
// Determine dimension system first
DimensionSystemTarget dimensionTarget = new DimensionSystemTarget();
parseEWKB(ewkb, dimensionTarget);
// Write an EWKB
return ewkb2ewkb(ewkb, dimensionTarget.getDimensionSystem());
}
/**
* Converts any supported EWKB to EWKB representation that is used by this
* class. Reduces dimension system to minimal possible and uses EWKB flags
* for dimension system indication. May also perform other changes.
*
* @param ewkb
* source EWKB
* @param dimension
* dimension system
* @return canonical EWKB, may be the same as the source
*/
public static byte[] ewkb2ewkb(byte[] ewkb, int dimensionSystem) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
EWKBTarget target = new EWKBTarget(output, dimensionSystem);
parseEWKB(ewkb, target);
return output.toByteArray();
}
/**
* Parses a EWKB.
*
* @param ewkb
* EWKB representation
* @param target
* output target
*/
public static void parseEWKB(byte[] ewkb, Target target) {
try {
parseEWKB(new EWKBSource(ewkb), target, 0);
} catch (ArrayIndexOutOfBoundsException e) {
throw new IllegalArgumentException();
}
}
/**
* Parses a EWKB.
*
* @param source
* EWKB source
* @param target
* output target
* @param parentType
* type of parent geometry collection, or 0 for the root geometry
*/
private static void parseEWKB(EWKBSource source, Target target, int parentType) {
// Read byte order of a next geometry
switch (source.readByte()) {
case 0:
source.bigEndian = true;
break;
case 1:
source.bigEndian = false;
break;
default:
throw new IllegalArgumentException();
}
// Type contains type of a geometry and additional flags
int type = source.readInt();
// PostGIS extensions
boolean useZ = (type & EWKB_Z) != 0;
boolean useM = (type & EWKB_M) != 0;
int srid = (type & EWKB_SRID) != 0 ? source.readInt() : 0;
// Use only top-level SRID
if (parentType == 0) {
target.init(srid);
}
// OGC 06-103r4
type &= 0xffff;
switch (type / 1_000) {
case DIMENSION_SYSTEM_XYZ:
useZ = true;
break;
case DIMENSION_SYSTEM_XYZM:
useZ = true;
//$FALL-THROUGH$
case DIMENSION_SYSTEM_XYM:
useM = true;
}
type %= 1_000;
switch (type) {
case POINT:
if (parentType != 0 && parentType != MULTI_POINT && parentType != GEOMETRY_COLLECTION) {
throw new IllegalArgumentException();
}
target.startPoint();
addCoordinate(source, target, useZ, useM, 0, 1);
break;
case LINE_STRING: {
if (parentType != 0 && parentType != MULTI_LINE_STRING && parentType != GEOMETRY_COLLECTION) {
throw new IllegalArgumentException();
}
int numPoints = source.readInt();
if (numPoints < 0 || numPoints == 1) {
throw new IllegalArgumentException();
}
target.startLineString(numPoints);
for (int i = 0; i < numPoints; i++) {
addCoordinate(source, target, useZ, useM, i, numPoints);
}
break;
}
case POLYGON: {
if (parentType != 0 && parentType != MULTI_POLYGON && parentType != GEOMETRY_COLLECTION) {
throw new IllegalArgumentException();
}
int numInner = source.readInt() - 1;
if (numInner < 0) {
throw new IllegalArgumentException();
}
int size = source.readInt();
// Size may be 0 (EMPTY) or 4+
if (size < 0 || size >= 1 && size <= 3) {
throw new IllegalArgumentException();
}
if (size == 0 && numInner > 0) {
throw new IllegalArgumentException();
}
target.startPolygon(numInner, size);
if (size > 0) {
addRing(source, target, useZ, useM, size);
for (int i = 0; i < numInner; i++) {
size = source.readInt();
// Size may be 0 (EMPTY) or 4+
if (size < 0 || size >= 1 && size <= 3) {
throw new IllegalArgumentException();
}
target.startPolygonInner(size);
addRing(source, target, useZ, useM, size);
}
target.endNonEmptyPolygon();
}
break;
}
case MULTI_POINT:
case MULTI_LINE_STRING:
case MULTI_POLYGON:
case GEOMETRY_COLLECTION: {
if (parentType != 0 && parentType != GEOMETRY_COLLECTION) {
throw new IllegalArgumentException();
}
int numItems = source.readInt();
if (numItems < 0) {
throw new IllegalArgumentException();
}
target.startCollection(type, numItems);
for (int i = 0; i < numItems; i++) {
Target innerTarget = target.startCollectionItem(i, numItems);
parseEWKB(source, innerTarget, type);
target.endCollectionItem(innerTarget, i, numItems);
}
target.endCollection(type);
break;
}
default:
throw new IllegalArgumentException();
}
}
private static void addRing(EWKBSource source, Target target, boolean useZ, boolean useM, int size) {
// 0 or 4+ are valid
if (size >= 4) {
double startX = source.readCoordinate(), startY = source.readCoordinate();
target.addCoordinate(startX, startY, //
useZ ? source.readCoordinate() : Double.NaN, useM ? source.readCoordinate() : Double.NaN, //
0, size);
for (int i = 1; i < size - 1; i++) {
addCoordinate(source, target, useZ, useM, i, size);
}
double endX = source.readCoordinate(), endY = source.readCoordinate();
/*
* TODO OGC 06-103r4 determines points as equal if they have the
* same X and Y coordinates. Should we check Z and M here too?
*/
if (startX != endX || startY != endY) {
throw new IllegalArgumentException();
}
target.addCoordinate(endX, endY, //
useZ ? source.readCoordinate() : Double.NaN, useM ? source.readCoordinate() : Double.NaN, //
size - 1, size);
}
}
private static void addCoordinate(EWKBSource source, Target target, boolean useZ, boolean useM, int index,
int total) {
target.addCoordinate(source.readCoordinate(), source.readCoordinate(),
useZ ? source.readCoordinate() : Double.NaN, useM ? source.readCoordinate() : Double.NaN, //
index, total);
}
/**
* Converts an envelope to a WKB.
*
* @param envelope
* envelope, or null
* @return WKB, or null
*/
public static byte[] envelope2wkb(double[] envelope) {
if (envelope == null) {
return null;
}
byte[] result;
double minX = envelope[MIN_X], maxX = envelope[MAX_X], minY = envelope[MIN_Y], maxY = envelope[MAX_Y];
if (minX == maxX && minY == maxY) {
result = new byte[21];
result[4] = POINT;
Bits.writeDouble(result, 5, minX);
Bits.writeDouble(result, 13, minY);
} else if (minX == maxX || minY == maxY) {
result = new byte[41];
result[4] = LINE_STRING;
result[8] = 2;
Bits.writeDouble(result, 9, minX);
Bits.writeDouble(result, 17, minY);
Bits.writeDouble(result, 25, maxX);
Bits.writeDouble(result, 33, maxY);
} else {
result = new byte[93];
result[4] = POLYGON;
result[8] = 1;
result[12] = 5;
Bits.writeDouble(result, 13, minX);
Bits.writeDouble(result, 21, minY);
Bits.writeDouble(result, 29, minX);
Bits.writeDouble(result, 37, maxY);
Bits.writeDouble(result, 45, maxX);
Bits.writeDouble(result, 53, maxY);
Bits.writeDouble(result, 61, maxX);
Bits.writeDouble(result, 69, minY);
Bits.writeDouble(result, 77, minX);
Bits.writeDouble(result, 85, minY);
}
return result;
}
private EWKBUtils() {
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.util.geometry;
import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XY;
import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYM;
import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYZ;
import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYZM;
import static org.h2.util.geometry.GeometryUtils.GEOMETRY_COLLECTION;
import static org.h2.util.geometry.GeometryUtils.LINE_STRING;
import static org.h2.util.geometry.GeometryUtils.M;
import static org.h2.util.geometry.GeometryUtils.MULTI_LINE_STRING;
import static org.h2.util.geometry.GeometryUtils.MULTI_POINT;
import static org.h2.util.geometry.GeometryUtils.MULTI_POLYGON;
import static org.h2.util.geometry.GeometryUtils.POINT;
import static org.h2.util.geometry.GeometryUtils.POLYGON;
import static org.h2.util.geometry.GeometryUtils.X;
import static org.h2.util.geometry.GeometryUtils.Y;
import static org.h2.util.geometry.GeometryUtils.Z;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import org.h2.engine.SysProperties;
import org.h2.util.geometry.EWKBUtils.EWKBTarget;
import org.h2.util.geometry.GeometryUtils.DimensionSystemTarget;
import org.h2.util.geometry.GeometryUtils.Target;
/**
* EWKT format support for GEOMETRY data type.
*
* <p>
* This class provides limited support of EWKT. EWKT is based on Well-known Text
* Representation (WKT) from OGC 06-103r4 and includes additional <a href=
* "https://postgis.net/docs/using_postgis_dbmanagement.html#EWKB_EWKT">PostGIS
* extensions</a>. SRID support from EWKT is implemented.
* </p>
*/
public final class EWKTUtils {
/**
* Converter output target that writes a EWKT.
*/
public static final class EWKTTarget extends Target {
private final StringBuilder output;
private final int dimensionSystem;
private int type;
private boolean inMulti;
/**
* Creates a new EWKT output target.
*
* @param output
* output stream
* @param dimensionSystem
* dimension system to use
*/
public EWKTTarget(StringBuilder output, int dimensionSystem) {
this.output = output;
this.dimensionSystem = dimensionSystem;
}
@Override
protected void init(int srid) {
if (srid != 0) {
output.append("SRID=").append(srid).append(';');
}
}
@Override
protected void startPoint() {
writeHeader(POINT);
}
@Override
protected void startLineString(int numPoints) {
writeHeader(LINE_STRING);
if (numPoints == 0) {
output.append("EMPTY");
}
}
@Override
protected void startPolygon(int numInner, int numPoints) {
writeHeader(POLYGON);
if (numPoints == 0) {
output.append("EMPTY");
} else {
output.append('(');
}
}
@Override
protected void startPolygonInner(int numInner) {
output.append(numInner > 0 ? ", " : ", EMPTY");
}
@Override
protected void endNonEmptyPolygon() {
output.append(')');
}
@Override
protected void startCollection(int type, int numItems) {
writeHeader(type);
if (numItems == 0) {
output.append("EMPTY");
}
if (type != GEOMETRY_COLLECTION) {
inMulti = true;
}
}
private void writeHeader(int type) {
this.type = type;
if (inMulti) {
return;
}
switch (type) {
case POINT:
output.append("POINT");
break;
case LINE_STRING:
output.append("LINESTRING");
break;
case POLYGON:
output.append("POLYGON");
break;
case MULTI_POINT:
output.append("MULTIPOINT");
break;
case MULTI_LINE_STRING:
output.append("MULTILINESTRING");
break;
case MULTI_POLYGON:
output.append("MULTIPOLYGON");
break;
case GEOMETRY_COLLECTION:
output.append("GEOMETRYCOLLECTION");
break;
default:
throw new IllegalArgumentException();
}
switch (dimensionSystem) {
case DIMENSION_SYSTEM_XYZ:
output.append(" Z");
break;
case DIMENSION_SYSTEM_XYM:
output.append(" M");
break;
case DIMENSION_SYSTEM_XYZM:
output.append(" ZM");
}
output.append(' ');
}
@Override
protected Target startCollectionItem(int index, int total) {
if (index == 0) {
output.append('(');
} else {
output.append(", ");
}
return this;
}
@Override
protected void endCollectionItem(Target target, int index, int total) {
if (index + 1 == total) {
output.append(')');
}
}
@Override
protected void endCollection(int type) {
if (type != GEOMETRY_COLLECTION) {
inMulti = false;
}
}
@Override
protected void addCoordinate(double x, double y, double z, double m, int index, int total) {
if (type == POINT && Double.isNaN(x) && Double.isNaN(y) && Double.isNaN(z) && Double.isNaN(m)) {
output.append("EMPTY");
return;
}
if (index == 0) {
output.append('(');
} else {
output.append(", ");
}
writeDouble(x);
output.append(' ');
writeDouble(y);
dimensionZ: if ((dimensionSystem & DIMENSION_SYSTEM_XYZ) != 0) {
if (SysProperties.MIXED_GEOMETRIES) {
if (Double.isNaN(z)) {
if ((dimensionSystem & DIMENSION_SYSTEM_XYM) != 0) {
throw new IllegalArgumentException();
}
break dimensionZ;
}
}
output.append(' ');
writeDouble(z);
}
if ((dimensionSystem & DIMENSION_SYSTEM_XYM) != 0) {
output.append(' ');
writeDouble(m);
}
if (index + 1 == total) {
output.append(')');
}
}
private void writeDouble(double v) {
String s = Double.toString(GeometryUtils.checkFinite(v));
if (s.endsWith(".0")) {
output.append(s, 0, s.length() - 2);
} else {
int idx = s.indexOf(".0E");
if (idx < 0) {
output.append(s);
} else {
output.append(s, 0, idx).append(s, idx + 2, s.length());
}
}
}
}
/**
* Helper source object for EWKT reading.
*/
private static final class EWKTSource {
private final String ewkt;
private int offset;
EWKTSource(String ewkt) {
this.ewkt = ewkt;
}
int readSRID() {
int srid;
if (ewkt.startsWith("SRID=")) {
int idx = ewkt.indexOf(';', 5);
srid = Integer.parseInt(ewkt.substring(5, idx));
offset = idx + 1;
} else {
srid = 0;
}
return srid;
}
void read(char symbol) {
skipWS();
int len = ewkt.length();
if (offset >= len) {
throw new IllegalArgumentException();
}
if (ewkt.charAt(offset) != symbol) {
throw new IllegalArgumentException();
}
offset++;
}
int readType() {
skipWS();
int len = ewkt.length();
if (offset >= len) {
throw new IllegalArgumentException();
}
int result = 0;
char ch = ewkt.charAt(offset);
switch (ch) {
case 'P':
case 'p':
result = match("POINT", POINT);
if (result == 0) {
result = match("POLYGON", POLYGON);
}
break;
case 'L':
case 'l':
result = match("LINESTRING", LINE_STRING);
break;
case 'M':
case 'm':
if (match("MULTI", 1) != 0) {
result = match("POINT", MULTI_POINT);
if (result == 0) {
result = match("POLYGON", MULTI_POLYGON);
if (result == 0) {
result = match("LINESTRING", MULTI_LINE_STRING);
}
}
}
break;
case 'G':
case 'g':
result = match("GEOMETRYCOLLECTION", GEOMETRY_COLLECTION);
break;
}
if (result == 0) {
throw new IllegalArgumentException();
}
return result;
}
int readDimensionSystem() {
int o = offset;
skipWS();
int len = ewkt.length();
if (offset > len - 2) {
throw new IllegalArgumentException();
}
int result;
char ch = ewkt.charAt(offset);
switch (ch) {
case 'M':
case 'm':
result = DIMENSION_SYSTEM_XYM;
offset++;
break;
case 'Z':
case 'z':
offset++;
ch = ewkt.charAt(offset);
if (ch == 'M' || ch == 'm') {
offset++;
result = DIMENSION_SYSTEM_XYZM;
} else {
result = DIMENSION_SYSTEM_XYZ;
}
break;
default:
result = DIMENSION_SYSTEM_XY;
if (o != offset) {
// Token is already terminated by a whitespace
return result;
}
}
checkStringEnd(len);
return result;
}
boolean readEmpty() {
skipWS();
int len = ewkt.length();
if (offset >= len) {
throw new IllegalArgumentException();
}
if (ewkt.charAt(offset) == '(') {
offset++;
return false;
}
if (match("EMPTY", 1) != 0) {
checkStringEnd(len);
return true;
}
throw new IllegalArgumentException();
}
private int match(String token, int code) {
int l = token.length();
if (offset <= ewkt.length() - l && ewkt.regionMatches(true, offset, token, 0, l)) {
offset += l;
} else {
code = 0;
}
return code;
}
private void checkStringEnd(int len) {
if (offset < len) {
char ch = ewkt.charAt(offset);
if (ch > ' ' && ch != '(' && ch != ')' && ch != ',') {
throw new IllegalArgumentException();
}
}
}
public boolean hasCoordinate() {
skipWS();
if (offset >= ewkt.length()) {
return false;
}
return isNumberStart(ewkt.charAt(offset));
}
public double readCoordinate() {
skipWS();
int len = ewkt.length();
if (offset >= len) {
throw new IllegalArgumentException();
}
char ch = ewkt.charAt(offset);
if (!isNumberStart(ch)) {
throw new IllegalArgumentException();
}
int start = offset++;
while (offset < len && isNumberPart(ch = ewkt.charAt(offset))) {
offset++;
}
if (offset < len) {
if (ch > ' ' && ch != ')' && ch != ',') {
throw new IllegalArgumentException();
}
}
Double d = Double.parseDouble(ewkt.substring(start, offset));
return d == 0 ? 0 : d;
}
private static boolean isNumberStart(char ch) {
if (ch >= '0' && ch <= '9') {
return true;
}
switch (ch) {
case '+':
case '-':
case '.':
return true;
default:
return false;
}
}
private static boolean isNumberPart(char ch) {
if (ch >= '0' && ch <= '9') {
return true;
}
switch (ch) {
case '+':
case '-':
case '.':
case 'E':
case 'e':
return true;
default:
return false;
}
}
public boolean hasMoreCoordinates() {
skipWS();
if (offset >= ewkt.length()) {
throw new IllegalArgumentException();
}
switch (ewkt.charAt(offset)) {
case ',':
offset++;
return true;
case ')':
offset++;
return false;
default:
throw new IllegalArgumentException();
}
}
boolean hasData() {
skipWS();
return offset < ewkt.length();
}
int getItemCount() {
int result = 1;
int offset = this.offset, level = 0, len = ewkt.length();
while (offset < len) {
switch (ewkt.charAt(offset++)) {
case ',':
if (level == 0) {
result++;
}
break;
case '(':
level++;
break;
case ')':
if (--level < 0) {
return result;
}
}
}
throw new IllegalArgumentException();
}
private void skipWS() {
for (int len = ewkt.length(); offset < len && ewkt.charAt(offset) <= ' '; offset++) {
}
}
@Override
public String toString() {
return new StringBuilder(ewkt.length() + 3).append(ewkt, 0, offset).append("<*>")
.append(ewkt, offset, ewkt.length()).toString();
}
}
/**
* Converts EWKB to EWKT.
*
* @param ewkb
* source EWKB
* @return EWKT representation
*/
public static String ewkb2ewkt(byte[] ewkb) {
// Determine dimension system first
DimensionSystemTarget dimensionTarget = new DimensionSystemTarget();
EWKBUtils.parseEWKB(ewkb, dimensionTarget);
// Write an EWKT
return ewkb2ewkt(ewkb, dimensionTarget.getDimensionSystem());
}
/**
* Converts EWKB to EWKT.
*
* @param ewkb
* source EWKB
* @param dimension
* dimension system
* @return EWKT representation
*/
public static String ewkb2ewkt(byte[] ewkb, int dimensionSystem) {
StringBuilder output = new StringBuilder();
EWKTTarget target = new EWKTTarget(output, dimensionSystem);
EWKBUtils.parseEWKB(ewkb, target);
return output.toString();
}
/**
* Converts EWKT to EWKB.
*
* @param ewkt
* source EWKT
* @return EWKB representation
*/
public static byte[] ewkt2ewkb(String ewkt) {
// Determine dimension system first
DimensionSystemTarget dimensionTarget = new DimensionSystemTarget();
parseEWKT(ewkt, dimensionTarget);
// Write an EWKB
return ewkt2ewkb(ewkt, dimensionTarget.getDimensionSystem());
}
/**
* Converts EWKT to EWKB.
*
* @param ewkt
* source EWKT
* @param dimension
* dimension system
* @return EWKB representation
*/
public static byte[] ewkt2ewkb(String ewkt, int dimensionSystem) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
EWKBTarget target = new EWKBTarget(output, dimensionSystem);
parseEWKT(ewkt, target);
return output.toByteArray();
}
/**
* Parses a EWKB.
*
* @param source
* source EWKT
* @param target
* output target
*/
public static void parseEWKT(String ewkt, Target target) {
parseEWKT(new EWKTSource(ewkt), target, 0, 0);
}
/**
* Parses a EWKB.
*
* @param source
* EWKT source
* @param target
* output target
* @param parentType
* type of parent geometry collection, or 0 for the root geometry
* @param dimensionSystem
* dimension system of parent geometry
*/
private static void parseEWKT(EWKTSource source, Target target, int parentType, int dimensionSystem) {
if (parentType == 0) {
target.init(source.readSRID());
}
int type;
switch (parentType) {
default: {
type = source.readType();
dimensionSystem = source.readDimensionSystem();
break;
}
case MULTI_POINT:
type = POINT;
break;
case MULTI_LINE_STRING:
type = LINE_STRING;
break;
case MULTI_POLYGON:
type = POLYGON;
break;
}
switch (type) {
case POINT: {
if (parentType != 0 && parentType != MULTI_POINT && parentType != GEOMETRY_COLLECTION) {
throw new IllegalArgumentException();
}
boolean empty = source.readEmpty();
target.startPoint();
if (empty) {
target.addCoordinate(Double.NaN, Double.NaN, Double.NaN, Double.NaN, 0, 1);
} else {
addCoordinate(source, target, dimensionSystem, 0, 1);
source.read(')');
}
break;
}
case LINE_STRING: {
if (parentType != 0 && parentType != MULTI_LINE_STRING && parentType != GEOMETRY_COLLECTION) {
throw new IllegalArgumentException();
}
boolean empty = source.readEmpty();
if (empty) {
target.startLineString(0);
} else {
ArrayList<double[]> coordinates = new ArrayList<>();
do {
coordinates.add(readCoordinate(source, dimensionSystem));
} while (source.hasMoreCoordinates());
int numPoints = coordinates.size();
if (numPoints < 0 || numPoints == 1) {
throw new IllegalArgumentException();
}
target.startLineString(numPoints);
for (int i = 0; i < numPoints; i++) {
double[] c = coordinates.get(i);
target.addCoordinate(c[X], c[Y], c[Z], c[M], i, numPoints);
}
}
break;
}
case POLYGON: {
if (parentType != 0 && parentType != MULTI_POLYGON && parentType != GEOMETRY_COLLECTION) {
throw new IllegalArgumentException();
}
boolean empty = source.readEmpty();
if (empty) {
target.startPolygon(0, 0);
} else {
ArrayList<double[]> outer = readRing(source, dimensionSystem);
ArrayList<ArrayList<double[]>> inner = new ArrayList<>();
while (source.hasMoreCoordinates()) {
inner.add(readRing(source, dimensionSystem));
}
int numInner = inner.size();
int size = outer.size();
// Size may be 0 (EMPTY) or 4+
if (size >= 1 && size <= 3) {
throw new IllegalArgumentException();
}
if (size == 0 && numInner > 0) {
throw new IllegalArgumentException();
}
target.startPolygon(numInner, size);
if (size > 0) {
addRing(outer, target);
for (int i = 0; i < numInner; i++) {
ArrayList<double[]> ring = inner.get(i);
size = ring.size();
// Size may be 0 (EMPTY) or 4+
if (size >= 1 && size <= 3) {
throw new IllegalArgumentException();
}
target.startPolygonInner(size);
addRing(ring, target);
}
target.endNonEmptyPolygon();
}
}
break;
}
case MULTI_POINT:
parseCollection(source, target, MULTI_POINT, parentType, dimensionSystem);
break;
case MULTI_LINE_STRING:
parseCollection(source, target, MULTI_LINE_STRING, parentType, dimensionSystem);
break;
case MULTI_POLYGON:
parseCollection(source, target, MULTI_POLYGON, parentType, dimensionSystem);
break;
case GEOMETRY_COLLECTION:
parseCollection(source, target, GEOMETRY_COLLECTION, parentType, 0);
break;
default:
throw new IllegalArgumentException();
}
if (parentType == 0 && source.hasData()) {
throw new IllegalArgumentException();
}
}
private static void parseCollection(EWKTSource source, Target target, int type, int parentType,
int dimensionSystem) {
if (parentType != 0 && parentType != GEOMETRY_COLLECTION) {
throw new IllegalArgumentException();
}
if (source.readEmpty()) {
target.startCollection(type, 0);
} else {
if (type == MULTI_POINT && source.hasCoordinate()) {
parseMultiPointAlternative(source, target, dimensionSystem);
} else {
int numItems = source.getItemCount();
target.startCollection(type, numItems);
for (int i = 0; i < numItems; i++) {
if (i > 0) {
source.read(',');
}
Target innerTarget = target.startCollectionItem(i, numItems);
parseEWKT(source, innerTarget, type, dimensionSystem);
target.endCollectionItem(innerTarget, i, numItems);
}
source.read(')');
}
}
target.endCollection(type);
}
private static void parseMultiPointAlternative(EWKTSource source, Target target, int dimensionSystem) {
// Special case for MULTIPOINT (1 2, 3 4)
ArrayList<double[]> points = new ArrayList<>();
do {
points.add(readCoordinate(source, dimensionSystem));
} while (source.hasMoreCoordinates());
int numItems = points.size();
target.startCollection(MULTI_POINT, numItems);
for (int i = 0; i < points.size(); i++) {
Target innerTarget = target.startCollectionItem(i, numItems);
target.startPoint();
double[] c = points.get(i);
target.addCoordinate(c[X], c[Y], c[Z], c[M], 0, 1);
target.endCollectionItem(innerTarget, i, numItems);
}
}
private static ArrayList<double[]> readRing(EWKTSource source, int dimensionSystem) {
if (source.readEmpty()) {
return new ArrayList<>(0);
}
ArrayList<double[]> result = new ArrayList<>();
double[] c = readCoordinate(source, dimensionSystem);
double startX = c[X], startY = c[Y];
result.add(c);
while (source.hasMoreCoordinates()) {
result.add(readCoordinate(source, dimensionSystem));
}
int size = result.size();
if (size < 4) {
throw new IllegalArgumentException();
}
c = result.get(size - 1);
double endX = c[X], endY = c[Y];
/*
* TODO OGC 06-103r4 determines points as equal if they have the same X
* and Y coordinates. Should we check Z and M here too?
*/
if (startX != endX || startY != endY) {
throw new IllegalArgumentException();
}
return result;
}
private static void addRing(ArrayList<double[]> ring, Target target) {
for (int i = 0, size = ring.size(); i < size; i++) {
double[] coordinates = ring.get(i);
target.addCoordinate(coordinates[X], coordinates[Y], coordinates[Z], coordinates[M], i, size);
}
}
private static void addCoordinate(EWKTSource source, Target target, int dimensionSystem, int index, int total) {
double x = source.readCoordinate();
double y = source.readCoordinate();
double z = Double.NaN, m = Double.NaN;
if (source.hasCoordinate()) {
if (dimensionSystem == DIMENSION_SYSTEM_XYM) {
m = source.readCoordinate();
} else {
z = source.readCoordinate();
if (source.hasCoordinate()) {
m = source.readCoordinate();
}
}
}
target.addCoordinate(x, y, z, m, index, total);
}
private static double[] readCoordinate(EWKTSource source, int dimensionSystem) {
double x = source.readCoordinate();
double y = source.readCoordinate();
double z = Double.NaN, m = Double.NaN;
if (source.hasCoordinate()) {
if (dimensionSystem == DIMENSION_SYSTEM_XYM) {
m = source.readCoordinate();
} else {
z = source.readCoordinate();
if (source.hasCoordinate()) {
m = source.readCoordinate();
}
}
}
return new double[] { x, y, z, m };
}
private EWKTUtils() {
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.util.geometry;
/**
* Utilities for GEOMETRY data type.
*/
public final class GeometryUtils {
/**
* Converter output target.
*/
public static abstract class Target {
public Target() {
}
/**
* Initializes top-level target.
*
* @param srid
* SRID
*/
protected void init(int srid) {
}
/**
* Invoked before writing a POINT.
*/
protected void startPoint() {
}
/**
* Invoked before writing a LINESTRING.
*
* @param numPoints
* number of points in line string
*/
protected void startLineString(int numPoints) {
}
/**
* Invoked before writing a POLYGON.
*
* @param numInner
* number of inner polygons
* @param numPoints
* number of points in outer polygon
*/
protected void startPolygon(int numInner, int numPoints) {
}
/**
* Invoked before writing an inner polygon in POLYGON.
*
* @param numInner
* number of points in inner polygon
*/
protected void startPolygonInner(int numInner) {
}
/**
* Invoked after writing of non-empty POLYGON.
*/
protected void endNonEmptyPolygon() {
}
/**
* Invoked before writing of a collection.
*
* @param type
* type of collection, one of
* {@link GeometryUtils#MULTI_POINT},
* {@link GeometryUtils#MULTI_LINE_STRING},
* {@link GeometryUtils#MULTI_POLYGON},
* {@link GeometryUtils#GEOMETRY_COLLECTION}
* @param numItems
* number of items in this collection
*/
protected void startCollection(int type, int numItems) {
}
/**
* Invoked before writing of a collection item.
*
* @param index
* 0-based index of this item in the collection
* @param total
* total number of items in the collection
* @return output target that should be used for processing of this
* collection item. May return this target or an custom
* sub-target.
*/
protected Target startCollectionItem(int index, int total) {
return this;
}
/**
* Invoked after writing of a collection item. This method is invoked on
* the same target that was used for
* {@link #startCollectionItem(int, int)}.
*
* @param target
* the result of {@link #startCollectionItem(int, int)}
* @param index
* 0-based index of this item in the collection
* @param total
* total number of items in the collection
*/
protected void endCollectionItem(Target target, int index, int total) {
}
/**
* Invoked after writing of a collection.
*
* @param type
* type of collection, see {@link #startCollection(int, int)}
*/
protected void endCollection(int type) {
}
/**
* Invoked to add a coordinate to a geometry.
*
* @param x
* X coordinate
* @param y
* Y coordinate
* @param z
* Z coordinate (NaN if not used)
* @param m
* M coordinate (NaN if not used)
* @param index
* 0-based index of coordinate in the current sequence
* @param total
* total number of coordinates in the current sequence
*/
protected abstract void addCoordinate(double x, double y, double z, double m, int index, int total);
}
/**
* Converter output target that calculates an envelope.
*/
public static final class EnvelopeTarget extends Target {
/**
* Enables or disables the envelope calculation. Inner rings of polygons
* are not counted.
*/
private boolean enabled;
/**
* Whether envelope was set.
*/
private boolean set;
double minX, maxX, minY, maxY;
/**
* Creates a new envelope calculation target.
*/
public EnvelopeTarget() {
}
@Override
protected void startPoint() {
enabled = true;
}
@Override
protected void startLineString(int numPoints) {
enabled = true;
}
@Override
protected void startPolygon(int numInner, int numPoints) {
enabled = true;
}
@Override
protected void startPolygonInner(int numInner) {
enabled = false;
}
@Override
protected void addCoordinate(double x, double y, double z, double m, int index, int total) {
// POINT EMPTY has NaNs
if (enabled && !Double.isNaN(x) && !Double.isNaN(y)) {
if (!set) {
minX = maxX = x;
minY = maxY = y;
set = true;
} else {
if (minX > x) {
minX = x;
}
if (maxX < x) {
maxX = x;
}
if (minY > y) {
minY = y;
}
if (maxY < y) {
maxY = y;
}
}
}
}
/**
* Returns the envelope.
*
* @return the envelope, or null
*/
public double[] getEnvelope() {
return set ? new double[] { minX, maxX, minY, maxY } : null;
}
}
/**
* Converter output target that determines minimal dimension system for a
* geometry.
*/
public static final class DimensionSystemTarget extends Target {
private boolean hasZ;
private boolean hasM;
/**
* Creates a new dimension system determination target.
*/
public DimensionSystemTarget() {
}
@Override
protected void addCoordinate(double x, double y, double z, double m, int index, int total) {
if (!hasZ && !Double.isNaN(z)) {
hasZ = true;
}
if (!hasM && !Double.isNaN(m)) {
hasM = true;
}
}
/**
* Returns the minimal dimension system.
*
* @return the minimal dimension system
*/
public int getDimensionSystem() {
return (hasZ ? DIMENSION_SYSTEM_XYZ : 0) | (hasM ? DIMENSION_SYSTEM_XYM : 0);
}
}
/**
* Converter output target that calculates an envelope and determines the
* minimal dimension system.
*/
public static final class EnvelopeAndDimensionSystemTarget extends Target {
/**
* Enables or disables the envelope calculation. Inner rings of polygons
* are not counted.
*/
private boolean enabled;
/**
* Whether envelope was set.
*/
private boolean set;
double minX, maxX, minY, maxY;
private boolean hasZ;
private boolean hasM;
/**
* Creates a new envelope and dimension system calculation target.
*/
public EnvelopeAndDimensionSystemTarget() {
}
@Override
protected void startPoint() {
enabled = true;
}
@Override
protected void startLineString(int numPoints) {
enabled = true;
}
@Override
protected void startPolygon(int numInner, int numPoints) {
enabled = true;
}
@Override
protected void startPolygonInner(int numInner) {
enabled = false;
}
@Override
protected void addCoordinate(double x, double y, double z, double m, int index, int total) {
if (!hasZ && !Double.isNaN(z)) {
hasZ = true;
}
if (!hasM && !Double.isNaN(m)) {
hasM = true;
}
// POINT EMPTY has NaNs
if (enabled && !Double.isNaN(x) && !Double.isNaN(y)) {
if (!set) {
minX = maxX = x;
minY = maxY = y;
set = true;
} else {
if (minX > x) {
minX = x;
}
if (maxX < x) {
maxX = x;
}
if (minY > y) {
minY = y;
}
if (maxY < y) {
maxY = y;
}
}
}
}
/**
* Returns the envelope.
*
* @return the envelope, or null
*/
public double[] getEnvelope() {
return set ? new double[] { minX, maxX, minY, maxY } : null;
}
/**
* Returns the minimal dimension system.
*
* @return the minimal dimension system
*/
public int getDimensionSystem() {
return (hasZ ? DIMENSION_SYSTEM_XYZ : 0) | (hasM ? DIMENSION_SYSTEM_XYM : 0);
}
}
/**
* POINT geometry type.
*/
static final int POINT = 1;
/**
* LINESTRING geometry type.
*/
static final int LINE_STRING = 2;
/**
* POLYGON geometry type.
*/
static final int POLYGON = 3;
/**
* MULTIPOINT geometry type.
*/
static final int MULTI_POINT = 4;
/**
* MULTILINESTRING geometry type.
*/
static final int MULTI_LINE_STRING = 5;
/**
* MULTIPOLYGON geometry type.
*/
static final int MULTI_POLYGON = 6;
/**
* GEOMETRYCOLLECTION geometry type.
*/
static final int GEOMETRY_COLLECTION = 7;
/**
* Number of X coordinate.
*/
public static final int X = 0;
/**
* Number of Y coordinate.
*/
public static final int Y = 1;
/**
* Number of Z coordinate.
*/
public static final int Z = 2;
/**
* Number of M coordinate.
*/
public static final int M = 3;
/**
* Code of 2D (XY) dimension system.
*/
public static final int DIMENSION_SYSTEM_XY = 0;
/**
* Code of Z (XYZ) dimension system. Can also be used in bit masks to
* determine presence of dimension Z.
*/
public static final int DIMENSION_SYSTEM_XYZ = 1;
/**
* Code of M (XYM) dimension system. Can also be used in bit masks to
* determine presence of dimension M.
*/
public static final int DIMENSION_SYSTEM_XYM = 2;
/**
* Code of ZM (XYZM) dimension system. Can be also combined from
* {@link #DIMENSION_SYSTEM_XYZ} and {@link #DIMENSION_SYSTEM_XYM} using
* bitwise OR.
*/
public static final int DIMENSION_SYSTEM_XYZM = 3;
/**
* Minimum X coordinate index.
*/
public static final int MIN_X = 0;
/**
* Maximum X coordinate index.
*/
public static final int MAX_X = 1;
/**
* Minimum Y coordinate index.
*/
public static final int MIN_Y = 2;
/**
* Maximum Y coordinate index.
*/
public static final int MAX_Y = 3;
/**
* Calculates an envelope of a specified geometry.
*
* @param ewkb
* EWKB of a geometry
* @return envelope, or null
*/
public static double[] getEnvelope(byte[] ewkb) {
EnvelopeTarget target = new EnvelopeTarget();
EWKBUtils.parseEWKB(ewkb, target);
return target.getEnvelope();
}
/**
* Checks whether two envelopes intersect with each other.
*
* @param envelope1
* first envelope, or null
* @param envelope2
* second envelope, or null
* @return whether the specified envelopes intersects
*/
public static boolean intersects(double[] envelope1, double[] envelope2) {
return envelope1 != null && envelope2 != null //
&& envelope1[MAX_X] >= envelope2[MIN_X] //
&& envelope1[MIN_X] <= envelope2[MAX_X] //
&& envelope1[MAX_Y] >= envelope2[MIN_Y] //
&& envelope1[MIN_Y] <= envelope2[MAX_Y];
}
/**
* Returns union of two envelopes. This method does not modify the specified
* envelopes, but may return one of them as a result.
*
* @param envelope1
* first envelope, or null
* @param envelope2
* second envelope, or null
* @return union of two envelopes
*/
public static double[] union(double[] envelope1, double[] envelope2) {
if (envelope1 == null) {
return envelope2;
} else if (envelope2 == null) {
return envelope1;
}
double minX1 = envelope1[MIN_X], maxX1 = envelope1[MAX_X], minY1 = envelope1[MIN_Y], maxY1 = envelope1[MAX_Y];
double minX2 = envelope2[MIN_X], maxX2 = envelope2[MAX_X], minY2 = envelope2[MIN_Y], maxY2 = envelope2[MAX_Y];
boolean modified = false;
if (minX1 > minX2) {
minX1 = minX2;
modified = true;
}
if (maxX1 < maxX2) {
maxX1 = maxX2;
modified = true;
}
if (minY1 > minY2) {
minY1 = minY2;
modified = true;
}
if (maxY1 < maxY2) {
maxY1 = maxY2;
modified = true;
}
return modified ? new double[] { minX1, maxX1, minY1, maxY1 } : envelope1;
}
/**
* Normalizes all NaNs into single type on NaN and negative zero to positive
* zero.
*
* @param d
* double value
* @return normalized value
*/
static double toCanonicalDouble(double d) {
return Double.isNaN(d) ? Double.NaN : d == 0d ? 0d : d;
}
static double checkFinite(double d) {
// Do not push this negation down, it will break NaN rejection
if (!(Math.abs(d) <= Double.MAX_VALUE)) {
throw new IllegalArgumentException();
}
return d;
}
private GeometryUtils() {
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.util.geometry;
import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYM;
import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYZ;
import static org.h2.util.geometry.GeometryUtils.GEOMETRY_COLLECTION;
import static org.h2.util.geometry.GeometryUtils.LINE_STRING;
import static org.h2.util.geometry.GeometryUtils.M;
import static org.h2.util.geometry.GeometryUtils.MULTI_LINE_STRING;
import static org.h2.util.geometry.GeometryUtils.MULTI_POINT;
import static org.h2.util.geometry.GeometryUtils.MULTI_POLYGON;
import static org.h2.util.geometry.GeometryUtils.POINT;
import static org.h2.util.geometry.GeometryUtils.POLYGON;
import static org.h2.util.geometry.GeometryUtils.X;
import static org.h2.util.geometry.GeometryUtils.Y;
import static org.h2.util.geometry.GeometryUtils.Z;
import static org.h2.util.geometry.GeometryUtils.checkFinite;
import static org.h2.util.geometry.GeometryUtils.toCanonicalDouble;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Method;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
import org.h2.util.geometry.EWKBUtils.EWKBTarget;
import org.h2.util.geometry.GeometryUtils.DimensionSystemTarget;
import org.h2.util.geometry.GeometryUtils.Target;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.CoordinateSequenceFactory;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.geom.MultiPoint;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.PrecisionModel;
import org.locationtech.jts.geom.impl.CoordinateArraySequenceFactory;
import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory;
/**
* Utilities for Geometry data type from JTS library.
*/
public final class JTSUtils {
/**
* {@code true} if M dimension is supported by used version of JTS,
* {@code false} if M dimension is only partially supported (JTS 1.15).
*/
public static final boolean M_IS_SUPPORTED;
static final Method CREATE;
private static final Method GET_MEASURES;
static {
Method create, getMeasures;
try {
create = CoordinateSequenceFactory.class.getMethod("create", int.class, int.class, int.class);
getMeasures = CoordinateSequence.class.getMethod("getMeasures");
} catch (ReflectiveOperationException e) {
create = null;
getMeasures = null;
}
M_IS_SUPPORTED = create != null;
CREATE = create;
GET_MEASURES = getMeasures;
}
/**
* Converter output target that creates a JTS Geometry.
*/
public static final class GeometryTarget extends Target {
private final int dimensionSystem;
private GeometryFactory factory;
private int type;
private CoordinateSequence coordinates;
private CoordinateSequence[] innerCoordinates;
private int innerOffset;
private Geometry[] subgeometries;
/**
* Creates a new instance of JTS Geometry target.
*
* @param dimensionSystem
* dimension system to use
*/
public GeometryTarget(int dimensionSystem) {
this.dimensionSystem = dimensionSystem;
}
private GeometryTarget(int dimensionSystem, GeometryFactory factory) {
this.dimensionSystem = dimensionSystem;
this.factory = factory;
}
@Override
protected void init(int srid) {
factory = new GeometryFactory(new PrecisionModel(), srid,
(dimensionSystem & DIMENSION_SYSTEM_XYM) != 0 ? PackedCoordinateSequenceFactory.DOUBLE_FACTORY
: CoordinateArraySequenceFactory.instance());
}
@Override
protected void startPoint() {
type = POINT;
initCoordinates(1);
innerOffset = -1;
}
@Override
protected void startLineString(int numPoints) {
type = LINE_STRING;
initCoordinates(numPoints);
innerOffset = -1;
}
@Override
protected void startPolygon(int numInner, int numPoints) {
type = POLYGON;
initCoordinates(numPoints);
innerCoordinates = new CoordinateSequence[numInner];
innerOffset = -1;
}
@Override
protected void startPolygonInner(int numInner) {
innerCoordinates[++innerOffset] = createCoordinates(numInner);
}
@Override
protected void startCollection(int type, int numItems) {
this.type = type;
switch (type) {
case MULTI_POINT:
subgeometries = new Point[numItems];
break;
case MULTI_LINE_STRING:
subgeometries = new LineString[numItems];
break;
case MULTI_POLYGON:
subgeometries = new Polygon[numItems];
break;
case GEOMETRY_COLLECTION:
subgeometries = new Geometry[numItems];
break;
default:
throw new IllegalArgumentException();
}
}
@Override
protected Target startCollectionItem(int index, int total) {
return new GeometryTarget(dimensionSystem, factory);
}
@Override
protected void endCollectionItem(Target target, int index, int total) {
subgeometries[index] = ((GeometryTarget) target).getGeometry();
}
private void initCoordinates(int numPoints) {
coordinates = createCoordinates(numPoints);
}
private CoordinateSequence createCoordinates(int numPoints) {
if ((dimensionSystem & DIMENSION_SYSTEM_XYM) != 0) {
if (M_IS_SUPPORTED) {
try {
return (CoordinateSequence) CREATE.invoke(factory.getCoordinateSequenceFactory(), numPoints, 4,
1);
} catch (ReflectiveOperationException e) {
throw DbException.convert(e);
}
}
return factory.getCoordinateSequenceFactory().create(numPoints, 4);
} else {
return factory.getCoordinateSequenceFactory().create(numPoints, 3);
}
}
@Override
protected void addCoordinate(double x, double y, double z, double m, int index, int total) {
if (type == POINT && Double.isNaN(x) && Double.isNaN(y) && Double.isNaN(z) && Double.isNaN(m)) {
this.coordinates = createCoordinates(0);
return;
}
CoordinateSequence coordinates = innerOffset < 0 ? this.coordinates : innerCoordinates[innerOffset];
coordinates.setOrdinate(index, X, checkFinite(x));
coordinates.setOrdinate(index, Y, checkFinite(y));
coordinates.setOrdinate(index, Z,
(dimensionSystem & DIMENSION_SYSTEM_XYZ) != 0 ? SysProperties.MIXED_GEOMETRIES ? z : checkFinite(z)
: Double.NaN);
if ((dimensionSystem & DIMENSION_SYSTEM_XYM) != 0) {
coordinates.setOrdinate(index, M, checkFinite(m));
}
}
Geometry getGeometry() {
switch (type) {
case POINT:
return new Point(coordinates, factory);
case LINE_STRING:
return new LineString(coordinates, factory);
case POLYGON: {
LinearRing shell = new LinearRing(coordinates, factory);
int innerCount = innerCoordinates.length;
LinearRing[] holes = new LinearRing[innerCount];
for (int i = 0; i < innerCount; i++) {
holes[i] = new LinearRing(innerCoordinates[i], factory);
}
return new Polygon(shell, holes, factory);
}
case MULTI_POINT:
return new MultiPoint((Point[]) subgeometries, factory);
case MULTI_LINE_STRING:
return new MultiLineString((LineString[]) subgeometries, factory);
case MULTI_POLYGON:
return new MultiPolygon((Polygon[]) subgeometries, factory);
case GEOMETRY_COLLECTION:
return new GeometryCollection(subgeometries, factory);
default:
throw new IllegalStateException();
}
}
}
/**
* Converts EWKB to a JTS geometry object.
*
* @param ewkb
* source EWKB
* @return JTS geometry object
*/
public static Geometry ewkb2geometry(byte[] ewkb) {
// Determine dimension system first
DimensionSystemTarget dimensionTarget = new DimensionSystemTarget();
EWKBUtils.parseEWKB(ewkb, dimensionTarget);
// Generate a Geometry
return ewkb2geometry(ewkb, dimensionTarget.getDimensionSystem());
}
/**
* Converts EWKB to a JTS geometry object.
*
* @param ewkb
* source EWKB
* @param dimensionSystem
* dimension system
* @return JTS geometry object
*/
public static Geometry ewkb2geometry(byte[] ewkb, int dimensionSystem) {
GeometryTarget target = new GeometryTarget(dimensionSystem);
EWKBUtils.parseEWKB(ewkb, target);
return target.getGeometry();
}
/**
* Converts Geometry to EWKB.
*
* @param geometry
* source geometry
* @return EWKB representation
*/
public static byte[] geometry2ewkb(Geometry geometry) {
// Determine dimension system first
DimensionSystemTarget dimensionTarget = new DimensionSystemTarget();
parseGeometry(geometry, dimensionTarget);
// Write an EWKB
return geometry2ewkb(geometry, dimensionTarget.getDimensionSystem());
}
/**
* Converts Geometry to EWKB.
*
* @param geometry
* source geometry
* @param dimensionSystem
* dimension system
* @return EWKB representation
*/
public static byte[] geometry2ewkb(Geometry geometry, int dimensionSystem) {
// Write an EWKB
ByteArrayOutputStream output = new ByteArrayOutputStream();
EWKBTarget target = new EWKBTarget(output, dimensionSystem);
parseGeometry(geometry, target);
return output.toByteArray();
}
/**
* Parses a JTS Geometry object.
*
* @param geometry
* geometry to parse
* @param target
* output target
*/
public static void parseGeometry(Geometry geometry, Target target) {
parseGeometry(geometry, target, 0);
}
/**
* Parses a JTS Geometry object.
*
* @param geometry
* geometry to parse
* @param target
* output target
* @param parentType
* type of parent geometry collection, or 0 for the root geometry
*/
private static void parseGeometry(Geometry geometry, Target target, int parentType) {
if (parentType == 0) {
target.init(geometry.getSRID());
}
if (geometry instanceof Point) {
if (parentType != 0 && parentType != MULTI_POINT && parentType != GEOMETRY_COLLECTION) {
throw new IllegalArgumentException();
}
target.startPoint();
Point p = (Point) geometry;
if (p.isEmpty()) {
target.addCoordinate(Double.NaN, Double.NaN, Double.NaN, Double.NaN, 0, 1);
} else {
CoordinateSequence sequence = p.getCoordinateSequence();
addCoordinate(sequence, target, 0, 1, getMeasures(sequence));
}
} else if (geometry instanceof LineString) {
if (parentType != 0 && parentType != MULTI_LINE_STRING && parentType != GEOMETRY_COLLECTION) {
throw new IllegalArgumentException();
}
LineString ls = (LineString) geometry;
CoordinateSequence cs = ls.getCoordinateSequence();
int numPoints = cs.size();
if (numPoints < 0 || numPoints == 1) {
throw new IllegalArgumentException();
}
target.startLineString(numPoints);
int measures = getMeasures(cs);
for (int i = 0; i < numPoints; i++) {
addCoordinate(cs, target, i, numPoints, measures);
}
} else if (geometry instanceof Polygon) {
if (parentType != 0 && parentType != MULTI_POLYGON && parentType != GEOMETRY_COLLECTION) {
throw new IllegalArgumentException();
}
Polygon p = (Polygon) geometry;
int numInner = p.getNumInteriorRing();
if (numInner < 0) {
throw new IllegalArgumentException();
}
CoordinateSequence cs = p.getExteriorRing().getCoordinateSequence();
int size = cs.size();
// Size may be 0 (EMPTY) or 4+
if (size < 0 || size >= 1 && size <= 3) {
throw new IllegalArgumentException();
}
if (size == 0 && numInner > 0) {
throw new IllegalArgumentException();
}
target.startPolygon(numInner, size);
if (size > 0) {
int measures = getMeasures(cs);
addRing(cs, target, size, measures);
for (int i = 0; i < numInner; i++) {
cs = p.getInteriorRingN(i).getCoordinateSequence();
size = cs.size();
// Size may be 0 (EMPTY) or 4+
if (size < 0 || size >= 1 && size <= 3) {
throw new IllegalArgumentException();
}
target.startPolygonInner(size);
addRing(cs, target, size, measures);
}
target.endNonEmptyPolygon();
}
} else if (geometry instanceof GeometryCollection) {
if (parentType != 0 && parentType != GEOMETRY_COLLECTION) {
throw new IllegalArgumentException();
}
GeometryCollection gc = (GeometryCollection) geometry;
int type;
if (gc instanceof MultiPoint) {
type = MULTI_POINT;
} else if (gc instanceof MultiLineString) {
type = MULTI_LINE_STRING;
} else if (gc instanceof MultiPolygon) {
type = MULTI_POLYGON;
} else {
type = GEOMETRY_COLLECTION;
}
int numItems = gc.getNumGeometries();
if (numItems < 0) {
throw new IllegalArgumentException();
}
target.startCollection(type, numItems);
for (int i = 0; i < numItems; i++) {
Target innerTarget = target.startCollectionItem(i, numItems);
parseGeometry(gc.getGeometryN(i), innerTarget, type);
target.endCollectionItem(innerTarget, i, numItems);
}
target.endCollection(type);
} else {
throw new IllegalArgumentException();
}
}
private static void addRing(CoordinateSequence sequence, Target target, int size, int measures) {
// 0 or 4+ are valid
if (size >= 4) {
double startX = toCanonicalDouble(sequence.getX(0)), startY = toCanonicalDouble(sequence.getY(0));
addCoordinate(sequence, target, 0, size, startX, startY, measures);
for (int i = 1; i < size - 1; i++) {
addCoordinate(sequence, target, i, size, measures);
}
double endX = toCanonicalDouble(sequence.getX(size - 1)), //
endY = toCanonicalDouble(sequence.getY(size - 1));
/*
* TODO OGC 06-103r4 determines points as equal if they have the
* same X and Y coordinates. Should we check Z and M here too?
*/
if (startX != endX || startY != endY) {
throw new IllegalArgumentException();
}
addCoordinate(sequence, target, size - 1, size, endX, endY, measures);
}
}
private static void addCoordinate(CoordinateSequence sequence, Target target, int index, int total, int measures) {
addCoordinate(sequence, target, index, total, toCanonicalDouble(sequence.getX(index)),
toCanonicalDouble(sequence.getY(index)), measures);
}
private static void addCoordinate(CoordinateSequence sequence, Target target, int index, int total, double x,
double y, int measures) {
double m, z;
int d = sequence.getDimension();
if (M_IS_SUPPORTED) {
d -= measures;
z = d > 2 ? sequence.getOrdinate(index, Z) : Double.NaN;
m = measures >= 1 ? sequence.getOrdinate(index, d) : Double.NaN;
} else {
z = d >= 3 ? toCanonicalDouble(sequence.getOrdinate(index, Z)) : Double.NaN;
m = d >= 4 ? toCanonicalDouble(sequence.getOrdinate(index, M)) : Double.NaN;
}
target.addCoordinate(x, y, z, m, index, total);
}
private static int getMeasures(CoordinateSequence sequence) {
int m;
if (M_IS_SUPPORTED) {
try {
m = (int) GET_MEASURES.invoke(sequence);
} catch (ReflectiveOperationException e) {
throw DbException.convert(e);
}
} else {
m = 0;
}
return m;
}
private JTSUtils() {
}
}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!--
Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0, Version 1.0,
and under the Eclipse Public License, Version 1.0
Initial Developer: H2 Group
-->
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /><title>
Javadoc package documentation
</title></head><body style="font: 9pt/130% Tahoma, Arial, Helvetica, sans-serif; font-weight: normal;"><p>
Internal utility classes for GEOMETRY data type.
</p></body></html>
\ No newline at end of file
......@@ -898,7 +898,7 @@ public class DataType {
case Value.RESULT_SET:
return ResultSet.class.getName();
case Value.GEOMETRY:
return GEOMETRY_CLASS_NAME;
return GEOMETRY_CLASS != null ? GEOMETRY_CLASS_NAME : String.class.getName();
case Value.INTERVAL_YEAR:
case Value.INTERVAL_MONTH:
case Value.INTERVAL_DAY:
......
......@@ -1215,7 +1215,7 @@ public abstract class Value {
private ValueGeometry convertToGeometry() {
switch (getType()) {
case BYTES:
return ValueGeometry.get(getBytesNoCopy());
return ValueGeometry.getFromEWKB(getBytesNoCopy());
case JAVA_OBJECT:
Object object = JdbcUtils.deserialize(getBytesNoCopy(), getDataHandler());
if (DataType.isGeometry(object)) {
......
......@@ -8,22 +8,18 @@ package org.h2.value;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import org.h2.api.ErrorCode;
import org.h2.engine.Mode;
import org.h2.message.DbException;
import org.h2.util.Bits;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.CoordinateSequenceFilter;
import org.locationtech.jts.geom.Envelope;
import org.h2.util.geometry.EWKBUtils;
import org.h2.util.geometry.EWKTUtils;
import org.h2.util.geometry.GeometryUtils;
import org.h2.util.geometry.GeometryUtils.EnvelopeAndDimensionSystemTarget;
import org.h2.util.geometry.JTSUtils;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.PrecisionModel;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKBReader;
import org.locationtech.jts.io.WKBWriter;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.io.WKTWriter;
/**
* Implementation of the GEOMETRY data type.
......@@ -47,26 +43,33 @@ public class ValueGeometry extends Value {
private final int hashCode;
/**
* The value. Converted from WKB only on request as conversion from/to WKB
* cost a significant amount of CPU cycles.
* Dimension system. -1 if not known yet.
*/
private Geometry geometry;
private int dimensionSystem;
/**
* The envelope of the value. Calculated only on request.
*/
private Envelope envelope;
private double[] envelope;
/**
* The value. Converted from WKB only on request as conversion from/to WKB
* cost a significant amount of CPU cycles.
*/
private Object geometry;
/**
* Create a new geometry objects.
* Create a new geometry object.
*
* @param bytes the bytes (always known)
* @param geometry the geometry object (may be null)
* @param bytes the EWKB bytes
* @param dimensionSystem dimension system
* @param envelope the envelope
*/
private ValueGeometry(byte[] bytes, Geometry geometry) {
private ValueGeometry(byte[] bytes, int dimensionSystem, double[] envelope) {
this.bytes = bytes;
this.geometry = geometry;
this.hashCode = Arrays.hashCode(bytes);
this.dimensionSystem = dimensionSystem;
this.envelope = envelope;
}
/**
......@@ -77,29 +80,16 @@ public class ValueGeometry extends Value {
* @return the value
*/
public static ValueGeometry getFromGeometry(Object o) {
/*
* Do not pass untrusted source geometry object to a cache, use only its WKB
* representation. Geometries are not fully immutable.
*/
return get(convertToWKB((Geometry) o));
}
private static ValueGeometry get(Geometry g) {
byte[] bytes = convertToWKB(g);
return (ValueGeometry) Value.cache(new ValueGeometry(bytes, g));
}
private static byte[] convertToWKB(Geometry g) {
boolean includeSRID = g.getSRID() != 0;
int dimensionCount = getDimensionCount(g);
WKBWriter writer = new WKBWriter(dimensionCount, includeSRID);
return writer.write(g);
}
private static int getDimensionCount(Geometry geometry) {
ZVisitor finder = new ZVisitor();
geometry.apply(finder);
return finder.isFoundZ() ? 3 : 2;
try {
EnvelopeAndDimensionSystemTarget target = new EnvelopeAndDimensionSystemTarget();
Geometry g = (Geometry) o;
JTSUtils.parseGeometry(g, target);
int dimensionSystem = target.getDimensionSystem();
return (ValueGeometry) Value.cache(new ValueGeometry(JTSUtils.geometry2ewkb(g, dimensionSystem),
dimensionSystem, target.getEnvelope()));
} catch (RuntimeException ex) {
throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, String.valueOf(o));
}
}
/**
......@@ -110,21 +100,13 @@ public class ValueGeometry extends Value {
*/
public static ValueGeometry get(String s) {
try {
int srid;
if (s.startsWith("SRID=")) {
int idx = s.indexOf(';', 5);
srid = Integer.parseInt(s.substring(5, idx));
s = s.substring(idx + 1);
} else {
srid = 0;
}
/*
* No-arg WKTReader() constructor instantiates a new GeometryFactory and a new
* PrecisionModel anyway, so special case for srid == 0 is not needed.
*/
return get(new WKTReader(new GeometryFactory(new PrecisionModel(), srid)).read(s));
} catch (ParseException | StringIndexOutOfBoundsException | NumberFormatException ex) {
throw DbException.convert(ex);
EnvelopeAndDimensionSystemTarget target = new EnvelopeAndDimensionSystemTarget();
EWKTUtils.parseEWKT(s, target);
int dimensionSystem = target.getDimensionSystem();
return (ValueGeometry) Value.cache(new ValueGeometry(EWKTUtils.ewkt2ewkb(s, dimensionSystem),
dimensionSystem, target.getEnvelope()));
} catch (RuntimeException ex) {
throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, s);
}
}
......@@ -137,21 +119,48 @@ public class ValueGeometry extends Value {
*/
public static ValueGeometry get(String s, int srid) {
// This method is not used in H2, but preserved for H2GIS
return get(srid == 0 ? s : "SRID=" + srid + ';' + s);
}
/**
* Get or create a geometry value for the given internal EWKB representation.
*
* @param bytes the WKB representation of the geometry. May not be modified.
* @return the value
*/
public static ValueGeometry get(byte[] bytes) {
return (ValueGeometry) Value.cache(new ValueGeometry(bytes, -1, null));
}
/**
* Get or create a geometry value for the given EWKB value.
*
* @param bytes the WKB representation of the geometry
* @return the value
*/
public static ValueGeometry getFromEWKB(byte[] bytes) {
try {
return get(new WKTReader(new GeometryFactory(new PrecisionModel(), srid)).read(s));
} catch (ParseException ex) {
throw DbException.convert(ex);
EnvelopeAndDimensionSystemTarget target = new EnvelopeAndDimensionSystemTarget();
EWKBUtils.parseEWKB(bytes, target);
int dimensionSystem = target.getDimensionSystem();
return (ValueGeometry) Value.cache(new ValueGeometry(EWKBUtils.ewkb2ewkb(bytes, dimensionSystem),
dimensionSystem, target.getEnvelope()));
} catch (RuntimeException ex) {
throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, StringUtils.convertBytesToHex(bytes));
}
}
/**
* Get or create a geometry value for the given geometry.
* Creates a geometry value for the given envelope.
*
* @param bytes the WKB representation of the geometry
* @param envelope envelope. May not be modified.
* @return the value
*/
public static ValueGeometry get(byte[] bytes) {
return (ValueGeometry) Value.cache(new ValueGeometry(bytes, null));
public static Value fromEnvelope(double[] envelope) {
return envelope != null
? Value.cache(new ValueGeometry(EWKBUtils.envelope2wkb(envelope), GeometryUtils.DIMENSION_SYSTEM_XY,
envelope))
: ValueNull.INSTANCE;
}
/**
......@@ -161,53 +170,33 @@ public class ValueGeometry extends Value {
* @return a copy of the geometry object
*/
public Geometry getGeometry() {
Geometry geometry = getGeometryNoCopy();
Geometry copy = geometry.copy();
return copy;
}
public Geometry getGeometryNoCopy() {
if (geometry == null) {
try {
/*
* No-arg WKBReader() constructor instantiates a new GeometryFactory and a new
* PrecisionModel anyway, so special case for srid == 0 is not needed.
*/
geometry = new WKBReader(new GeometryFactory(new PrecisionModel(), getSRID())).read(bytes);
} catch (ParseException ex) {
geometry = JTSUtils.ewkb2geometry(bytes, getDimensionSystem());
} catch (RuntimeException ex) {
throw DbException.convert(ex);
}
}
return geometry;
return ((Geometry) geometry).copy();
}
private void calculateInfo() {
if (dimensionSystem < 0) {
EnvelopeAndDimensionSystemTarget target = new EnvelopeAndDimensionSystemTarget();
EWKBUtils.parseEWKB(bytes, target);
envelope = target.getEnvelope();
dimensionSystem = target.getDimensionSystem();
}
}
/**
* Return the SRID (Spatial Reference Identifier).
* Return a minimal dimension system that can be used for this geometry.
*
* @return spatial reference identifier
* @return dimension system
*/
public int getSRID() {
if (bytes.length >= 9) {
boolean bigEndian;
switch (bytes[0]) {
case 0:
bigEndian = true;
break;
case 1:
bigEndian = false;
break;
default:
return 0;
}
if ((bytes[bigEndian ? 1 : 4] & 0x20) != 0) {
int srid = Bits.readInt(bytes, 5);
if (!bigEndian) {
srid = Integer.reverseBytes(srid);
}
return srid;
}
}
return 0;
public int getDimensionSystem() {
calculateInfo();
return dimensionSystem;
}
/**
......@@ -215,10 +204,8 @@ public class ValueGeometry extends Value {
*
* @return envelope of this geometry
*/
public Envelope getEnvelopeNoCopy() {
if (envelope == null) {
envelope = getGeometryNoCopy().getEnvelopeInternal();
}
public double[] getEnvelopeNoCopy() {
calculateInfo();
return envelope;
}
......@@ -230,7 +217,7 @@ public class ValueGeometry extends Value {
* @return true if the two overlap
*/
public boolean intersectsBoundingBox(ValueGeometry r) {
return getEnvelopeNoCopy().intersects(r.getEnvelopeNoCopy());
return GeometryUtils.intersects(getEnvelopeNoCopy(), r.getEnvelopeNoCopy());
}
/**
......@@ -240,10 +227,7 @@ public class ValueGeometry extends Value {
* @return the union of this geometry envelope and another geometry envelope
*/
public Value getEnvelopeUnion(ValueGeometry r) {
GeometryFactory gf = new GeometryFactory();
Envelope mergedEnvelope = new Envelope(getEnvelopeNoCopy());
mergedEnvelope.expandToInclude(r.getEnvelopeNoCopy());
return get(gf.toGeometry(mergedEnvelope));
return fromEnvelope(GeometryUtils.union(getEnvelopeNoCopy(), r.getEnvelopeNoCopy()));
}
@Override
......@@ -253,13 +237,13 @@ public class ValueGeometry extends Value {
@Override
public String getSQL() {
// Using bytes is faster than converting EWKB to Geometry then EWKT.
// Using bytes is faster than converting to EWKT.
return "X'" + StringUtils.convertBytesToHex(getBytesNoCopy()) + "'::Geometry";
}
@Override
public int compareTypeSafe(Value v, CompareMode mode) {
return getGeometryNoCopy().compareTo(((ValueGeometry) v).getGeometryNoCopy());
return Bits.compareNotNullUnsigned(bytes, ((ValueGeometry) v).bytes);
}
@Override
......@@ -279,23 +263,25 @@ public class ValueGeometry extends Value {
@Override
public Object getObject() {
return getGeometry();
if (DataType.GEOMETRY_CLASS != null) {
return getGeometry();
}
return getEWKT();
}
@Override
public byte[] getBytes() {
return Utils.cloneByteArray(getEWKB());
return Utils.cloneByteArray(bytes);
}
@Override
public byte[] getBytesNoCopy() {
return getEWKB();
return bytes;
}
@Override
public void set(PreparedStatement prep, int parameterIndex)
throws SQLException {
prep.setObject(parameterIndex, getGeometryNoCopy());
public void set(PreparedStatement prep, int parameterIndex) throws SQLException {
prep.setBytes(parameterIndex, bytes);
}
@Override
......@@ -305,15 +291,12 @@ public class ValueGeometry extends Value {
@Override
public int getMemory() {
return getEWKB().length * 20 + 24;
return bytes.length * 20 + 24;
}
@Override
public boolean equals(Object other) {
// The JTS library only does half-way support for 3D coordinates, so
// their equals method only checks the first two coordinates.
return other instanceof ValueGeometry &&
Arrays.equals(getEWKB(), ((ValueGeometry) other).getEWKB());
return other instanceof ValueGeometry && Arrays.equals(bytes, ((ValueGeometry) other).bytes);
}
/**
......@@ -322,12 +305,7 @@ public class ValueGeometry extends Value {
* @return the extended well-known text
*/
public String getEWKT() {
String wkt = new WKTWriter(3).write(getGeometryNoCopy());
int srid = getSRID();
return srid == 0
? wkt
// "SRID=-2147483648;".length() == 17
: new StringBuilder(wkt.length() + 17).append("SRID=").append(srid).append(';').append(wkt).toString();
return EWKTUtils.ewkb2ewkt(bytes, getDimensionSystem());
}
/**
......@@ -347,40 +325,4 @@ public class ValueGeometry extends Value {
return super.convertTo(targetType, precision, mode, column, null);
}
/**
* A visitor that checks if there is a Z coordinate.
*/
static class ZVisitor implements CoordinateSequenceFilter {
private boolean foundZ;
public boolean isFoundZ() {
return foundZ;
}
/**
* Performs an operation on a coordinate in a CoordinateSequence.
*
* @param coordinateSequence the object to which the filter is applied
* @param i the index of the coordinate to apply the filter to
*/
@Override
public void filter(CoordinateSequence coordinateSequence, int i) {
if (!Double.isNaN(coordinateSequence.getOrdinate(i, 2))) {
foundZ = true;
}
}
@Override
public boolean isDone() {
return foundZ;
}
@Override
public boolean isGeometryChanged() {
return false;
}
}
}
......@@ -188,6 +188,7 @@ import org.h2.test.unit.TestFileLockProcess;
import org.h2.test.unit.TestFileLockSerialized;
import org.h2.test.unit.TestFileSystem;
import org.h2.test.unit.TestFtp;
import org.h2.test.unit.TestGeometryUtils;
import org.h2.test.unit.TestIntArray;
import org.h2.test.unit.TestIntIntHashMap;
import org.h2.test.unit.TestIntPerfectHash;
......@@ -961,6 +962,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
addTest(new TestDbException());
addTest(new TestFile());
addTest(new TestFtp());
addTest(new TestGeometryUtils());
addTest(new TestInterval());
addTest(new TestIntArray());
addTest(new TestIntIntHashMap());
......
......@@ -608,16 +608,17 @@ public class TestSpatial extends TestDb {
* Test serialization of Z and SRID values.
*/
private void testWKB() {
String ewkt = "SRID=27572;POLYGON ((67 13 6, 67 18 5, 59 18 4, 59 13 6, 67 13 6))";
String ewkt = "SRID=27572;POLYGON Z ((67 13 6, 67 18 5, 59 18 4, 59 13 6, 67 13 6))";
ValueGeometry geom3d = ValueGeometry.get(ewkt);
assertEquals(ewkt, geom3d.getString());
ValueGeometry copy = ValueGeometry.get(geom3d.getBytes());
assertEquals(6, copy.getGeometry().getCoordinates()[0].z);
assertEquals(5, copy.getGeometry().getCoordinates()[1].z);
assertEquals(4, copy.getGeometry().getCoordinates()[2].z);
Geometry g = copy.getGeometry();
assertEquals(6, g.getCoordinates()[0].z);
assertEquals(5, g.getCoordinates()[1].z);
assertEquals(4, g.getCoordinates()[2].z);
// Test SRID
copy = ValueGeometry.get(geom3d.getBytes());
assertEquals(27572, copy.getGeometry().getSRID());
assertEquals(27572, g.getSRID());
Point point = new GeometryFactory().createPoint((new Coordinate(1.1d, 1.2d)));
// SRID 0
......@@ -697,13 +698,6 @@ public class TestSpatial extends TestDb {
ValueGeometry valueGeometry3 = ValueGeometry.getFromGeometry(geometry);
assertEquals(valueGeometry, valueGeometry3);
assertEquals(geometry.getSRID(), valueGeometry3.getGeometry().getSRID());
// Check illegal geometry (no WKB representation)
try {
ValueGeometry.get("POINT EMPTY");
fail("expected this to throw IllegalArgumentException");
} catch (IllegalArgumentException ex) {
// expected
}
}
/**
......
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.unit;
import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XY;
import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYM;
import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYZ;
import static org.h2.util.geometry.GeometryUtils.M;
import static org.h2.util.geometry.GeometryUtils.MAX_X;
import static org.h2.util.geometry.GeometryUtils.MAX_Y;
import static org.h2.util.geometry.GeometryUtils.MIN_X;
import static org.h2.util.geometry.GeometryUtils.MIN_Y;
import static org.h2.util.geometry.GeometryUtils.X;
import static org.h2.util.geometry.GeometryUtils.Y;
import static org.h2.util.geometry.GeometryUtils.Z;
import java.io.ByteArrayOutputStream;
import java.lang.ProcessBuilder.Redirect;
import java.util.Random;
import org.h2.test.TestBase;
import org.h2.util.StringUtils;
import org.h2.util.geometry.EWKBUtils;
import org.h2.util.geometry.EWKBUtils.EWKBTarget;
import org.h2.util.geometry.EWKTUtils;
import org.h2.util.geometry.EWKTUtils.EWKTTarget;
import org.h2.util.geometry.GeometryUtils;
import org.h2.util.geometry.GeometryUtils.DimensionSystemTarget;
import org.h2.util.geometry.GeometryUtils.EnvelopeAndDimensionSystemTarget;
import org.h2.util.geometry.GeometryUtils.Target;
import org.h2.util.geometry.JTSUtils;
import org.h2.util.geometry.JTSUtils.GeometryTarget;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.io.WKBWriter;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.io.WKTWriter;
/**
* Tests the classes from org.h2.util.geometry package.
*/
public class TestGeometryUtils extends TestBase {
private static final byte[][] NON_FINITE = { //
// XY
StringUtils.convertHexToBytes("0000000001" //
+ "0000000000000000" //
+ "7ff8000000000000"), //
// XY
StringUtils.convertHexToBytes("0000000001" //
+ "7ff8000000000000" //
+ "0000000000000000"), //
// XYZ
StringUtils.convertHexToBytes("0080000001" //
+ "0000000000000000" //
+ "0000000000000000" //
+ "7ff8000000000000"), //
// XYM
StringUtils.convertHexToBytes("0040000001" //
+ "0000000000000000" //
+ "0000000000000000" //
+ "7ff8000000000000") };
private static final int[] NON_FINITE_DIMENSIONS = { //
DIMENSION_SYSTEM_XY, //
DIMENSION_SYSTEM_XY, //
DIMENSION_SYSTEM_XYZ, //
DIMENSION_SYSTEM_XYM };
private static final String MIXED_WKT = "LINESTRING (1 2, 3 4 5)";
private static final String MIXED_WKT_Z = "LINESTRING Z (1 2, 3 4 5)";
private static final byte[] MIXED_WKB = StringUtils.convertHexToBytes(""
// BOM (BigEndian)
+ "00"
// Z | LINESTRING
+ "80000002"
// 2 items
+ "00000002"
// 1.0
+ "3ff0000000000000"
// 2.0
+ "4000000000000000"
// NaN
+ "7ff8000000000000"
// 3.0
+ "4008000000000000"
// 4.0
+ "4010000000000000"
// 5.0
+ "4014000000000000");
/**
* May be used to run only this test and may be launched by this test in a
* subprocess.
*
* @param a
* if empty run this test only
*/
public static void main(String... a) throws Exception {
TestGeometryUtils test = (TestGeometryUtils) TestBase.createCaller().init();
if (a.length == 0) {
test.test();
} else {
test.testMixedGeometriesAcceptImpl();
}
}
@Override
public void test() throws Exception {
testPoint();
testLineString();
testPolygon();
testMultiPoint();
testMultiLineString();
testMultiPolygon();
testGeometryCollection();
testEmptyPoint();
testDimensionM();
testDimensionZM();
testFiniteOnly();
testSRID();
testIntersectionAndUnion();
testMixedGeometries();
testMixedGeometriesAccept();
}
private void testPoint() throws Exception {
testGeometry("POINT (1 2)", 2);
testGeometry("POINT (-1.3 15)", 2);
testGeometry("POINT (-1E32 1.000001)", "POINT (-1E32 1.000001)",
"POINT (-100000000000000000000000000000000 1.000001)", 2);
testGeometry("POINT Z (2.7 -3 34)", 3);
assertEquals("POINT Z (1 2 3)", EWKTUtils.ewkb2ewkt(EWKTUtils.ewkt2ewkb("POINTZ(1 2 3)")));
assertEquals("POINT Z (1 2 3)", EWKTUtils.ewkb2ewkt(EWKTUtils.ewkt2ewkb("pointz(1 2 3)")));
}
private void testLineString() throws Exception {
testGeometry("LINESTRING (-1 -2, 10 1, 2 20)", 2);
testGeometry("LINESTRING (1 2, 1 3)", 2);
testGeometry("LINESTRING (1 2, 2 2)", 2);
testGeometry("LINESTRING EMPTY", 2);
testGeometry("LINESTRING Z (-1 -2 -3, 10 15.7 3)", 3);
}
private void testPolygon() throws Exception {
testGeometry("POLYGON ((-1 -2, 10 1, 2 20, -1 -2))", 2);
testGeometry("POLYGON EMPTY", 2);
testGeometry("POLYGON ((-1 -2, 10 1, 2 20, -1 -2), (0.5 0.5, 1 0.5, 1 1, 0.5 0.5))", 2);
// TODO is EMPTY inner ring valid?
testGeometry("POLYGON ((-1 -2, 10 1, 2 20, -1 -2), EMPTY)", 2);
testGeometry("POLYGON Z ((-1 -2 7, 10 1 7, 2 20 7, -1 -2 7), (0.5 0.5 7, 1 0.5 7, 1 1 7, 0.5 0.5 7))", 3);
}
private void testMultiPoint() throws Exception {
testGeometry("MULTIPOINT ((1 2), (3 4))", 2);
// Alternative syntax
testGeometry("MULTIPOINT (1 2, 3 4)", "MULTIPOINT ((1 2), (3 4))", "MULTIPOINT ((1 2), (3 4))", 2);
testGeometry("MULTIPOINT (1 2)", "MULTIPOINT ((1 2))", "MULTIPOINT ((1 2))", 2);
testGeometry("MULTIPOINT EMPTY", 2);
testGeometry("MULTIPOINT Z ((1 2 0.5), (3 4 -3))", 3);
}
private void testMultiLineString() throws Exception {
testGeometry("MULTILINESTRING ((1 2, 3 4, 5 7))", 2);
testGeometry("MULTILINESTRING ((1 2, 3 4, 5 7), (-1 -1, 0 0, 2 2, 4 6.01))", 2);
testGeometry("MULTILINESTRING EMPTY", 2);
testGeometry("MULTILINESTRING Z ((1 2 0.5, 3 4 -3, 5 7 10))", 3);
}
private void testMultiPolygon() throws Exception {
testGeometry("MULTIPOLYGON (((-1 -2, 10 1, 2 20, -1 -2)))", 2);
testGeometry("MULTIPOLYGON (((-1 -2, 10 1, 2 20, -1 -2)), ((1 2, 2 2, 3 3, 1 2)))", 2);
testGeometry("MULTIPOLYGON EMPTY", 2);
testGeometry("MULTIPOLYGON (((-1 -2, 10 1, 2 20, -1 -2), (0.5 0.5, 1 0.5, 1 1, 0.5 0.5)))", 2);
testGeometry("MULTIPOLYGON Z (((-1 -2 7, 10 1 7, 2 20 7, -1 -2 7), (0.5 1 7, 1 0.5 7, 1 1 7, 0.5 1 7)))", 3);
}
private void testGeometryCollection() throws Exception {
testGeometry("GEOMETRYCOLLECTION (POINT (1 2))", 2);
testGeometry("GEOMETRYCOLLECTION (POINT (1 2), " //
+ "MULTILINESTRING ((1 2, 3 4, 5 7), (-1 -1, 0 0, 2 2, 4 6.01)), " //
+ "POINT (100 130))", 2);
testGeometry("GEOMETRYCOLLECTION EMPTY", 2);
testGeometry(
"GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (1 3)), MULTIPOINT ((4 8)), GEOMETRYCOLLECTION EMPTY)",
2);
testGeometry("GEOMETRYCOLLECTION Z (POINT Z (1 2 3))", 3);
}
private void testGeometry(String wkt, int numOfDimensions) throws Exception {
testGeometry(wkt, wkt, wkt, numOfDimensions);
}
private void testGeometry(String wkt, String h2Wkt, String jtsWkt, int numOfDimensions) throws Exception {
Geometry geometryFromJTS = new WKTReader().read(wkt);
byte[] wkbFromJTS = new WKBWriter(numOfDimensions).write(geometryFromJTS);
// Test WKB->WKT conversion
assertEquals(h2Wkt, EWKTUtils.ewkb2ewkt(wkbFromJTS));
// Test WKT->WKB conversion
assertEquals(wkbFromJTS, EWKTUtils.ewkt2ewkb(wkt));
// Test WKB->WKB no-op normalization
assertEquals(wkbFromJTS, EWKBUtils.ewkb2ewkb(wkbFromJTS));
// Test WKB->Geometry conversion
Geometry geometryFromH2 = JTSUtils.ewkb2geometry(wkbFromJTS);
String got = new WKTWriter(numOfDimensions).write(geometryFromH2);
if (!jtsWkt.equals(got)) {
if (!jtsWkt.replaceAll(" Z", "").equals(got)) { // JTS 1.15
assertEquals(jtsWkt.replaceAll(" Z ", " Z"), got); // JTS 1.16
}
}
// Test Geometry->WKB conversion
assertEquals(wkbFromJTS, JTSUtils.geometry2ewkb(geometryFromJTS));
// Test Envelope
Envelope envelopeFromJTS = geometryFromJTS.getEnvelopeInternal();
testEnvelope(envelopeFromJTS, GeometryUtils.getEnvelope(wkbFromJTS));
EnvelopeAndDimensionSystemTarget target = new EnvelopeAndDimensionSystemTarget();
EWKBUtils.parseEWKB(wkbFromJTS, target);
testEnvelope(envelopeFromJTS, target.getEnvelope());
// Test dimensions
testDimensions(numOfDimensions > 2 ? GeometryUtils.DIMENSION_SYSTEM_XYZ : GeometryUtils.DIMENSION_SYSTEM_XY,
wkbFromJTS);
}
private void testEnvelope(Envelope envelopeFromJTS, double[] envelopeFromH2) {
if (envelopeFromJTS.isNull()) {
assertNull(envelopeFromH2);
assertNull(EWKBUtils.envelope2wkb(envelopeFromH2));
} else {
assertEquals(envelopeFromJTS.getMinX(), envelopeFromH2[0]);
assertEquals(envelopeFromJTS.getMaxX(), envelopeFromH2[1]);
assertEquals(envelopeFromJTS.getMinY(), envelopeFromH2[2]);
assertEquals(envelopeFromJTS.getMaxY(), envelopeFromH2[3]);
assertEquals(new WKBWriter(2).write(new GeometryFactory().toGeometry(envelopeFromJTS)),
EWKBUtils.envelope2wkb(envelopeFromH2));
}
}
private void testEmptyPoint() {
String ewkt = "POINT EMPTY";
byte[] ewkb = EWKTUtils.ewkt2ewkb(ewkt);
assertEquals(StringUtils.convertHexToBytes("00000000017ff80000000000007ff8000000000000"), ewkb);
assertEquals(ewkt, EWKTUtils.ewkb2ewkt(ewkb));
assertNull(GeometryUtils.getEnvelope(ewkb));
Point p = (Point) JTSUtils.ewkb2geometry(ewkb);
assertTrue(p.isEmpty());
assertEquals(ewkt, new WKTWriter().write(p));
assertEquals(ewkb, JTSUtils.geometry2ewkb(p));
}
private void testDimensionM() throws Exception {
byte[] ewkb = EWKTUtils.ewkt2ewkb("POINT M (1 2 3)");
assertEquals("POINT M (1 2 3)", EWKTUtils.ewkb2ewkt(ewkb));
assertEquals("POINT M (1 2 3)", EWKTUtils.ewkb2ewkt(EWKTUtils.ewkt2ewkb("POINTM(1 2 3)")));
assertEquals("POINT M (1 2 3)", EWKTUtils.ewkb2ewkt(EWKTUtils.ewkt2ewkb("pointm(1 2 3)")));
Point p = (Point) JTSUtils.ewkb2geometry(ewkb);
CoordinateSequence cs = p.getCoordinateSequence();
testDimensionMCheckPoint(cs);
assertEquals(ewkb, JTSUtils.geometry2ewkb(p));
testDimensions(GeometryUtils.DIMENSION_SYSTEM_XYM, ewkb);
if (JTSUtils.M_IS_SUPPORTED) {
p = (Point) new WKTReader().read("POINT M (1 2 3)");
cs = p.getCoordinateSequence();
assertEquals(3, cs.getDimension());
assertEquals(1, (int) cs.getClass().getMethod("getMeasures").invoke(cs));
assertEquals(1, cs.getOrdinate(0, 0));
assertEquals(2, cs.getOrdinate(0, 1));
assertEquals(3, cs.getOrdinate(0, 2));
ewkb = JTSUtils.geometry2ewkb(p);
assertEquals("POINT M (1 2 3)", EWKTUtils.ewkb2ewkt(ewkb));
p = (Point) JTSUtils.ewkb2geometry(ewkb);
cs = p.getCoordinateSequence();
testDimensionMCheckPoint(cs);
assertEquals(1, (int) cs.getClass().getMethod("getMeasures").invoke(cs));
}
}
private void testDimensionMCheckPoint(CoordinateSequence cs) {
assertEquals(4, cs.getDimension());
assertEquals(1, cs.getOrdinate(0, X));
assertEquals(2, cs.getOrdinate(0, Y));
assertEquals(Double.NaN, cs.getOrdinate(0, Z));
assertEquals(3, cs.getOrdinate(0, M));
}
private void testDimensionZM() throws Exception {
byte[] ewkb = EWKTUtils.ewkt2ewkb("POINT ZM (1 2 3 4)");
assertEquals("POINT ZM (1 2 3 4)", EWKTUtils.ewkb2ewkt(ewkb));
assertEquals("POINT ZM (1 2 3 4)", EWKTUtils.ewkb2ewkt(EWKTUtils.ewkt2ewkb("POINTZM(1 2 3 4)")));
assertEquals("POINT ZM (1 2 3 4)", EWKTUtils.ewkb2ewkt(EWKTUtils.ewkt2ewkb("pointzm(1 2 3 4)")));
Point p = (Point) JTSUtils.ewkb2geometry(ewkb);
CoordinateSequence cs = p.getCoordinateSequence();
testDimensionZMCheckPoint(cs);
assertEquals(ewkb, JTSUtils.geometry2ewkb(p));
testDimensions(GeometryUtils.DIMENSION_SYSTEM_XYZM, ewkb);
if (JTSUtils.M_IS_SUPPORTED) {
p = (Point) new WKTReader().read("POINT ZM (1 2 3 4)");
cs = p.getCoordinateSequence();
testDimensionZMCheckPoint(cs);
assertEquals(1, (int) cs.getClass().getMethod("getMeasures").invoke(cs));
ewkb = JTSUtils.geometry2ewkb(p);
assertEquals("POINT ZM (1 2 3 4)", EWKTUtils.ewkb2ewkt(ewkb));
p = (Point) JTSUtils.ewkb2geometry(ewkb);
cs = p.getCoordinateSequence();
testDimensionZMCheckPoint(cs);
assertEquals(1, (int) cs.getClass().getMethod("getMeasures").invoke(cs));
}
}
private void testDimensionZMCheckPoint(CoordinateSequence cs) {
assertEquals(4, cs.getDimension());
assertEquals(1, cs.getOrdinate(0, X));
assertEquals(2, cs.getOrdinate(0, Y));
assertEquals(3, cs.getOrdinate(0, Z));
assertEquals(4, cs.getOrdinate(0, M));
}
private void testFiniteOnly() {
for (int i = 0; i < NON_FINITE.length; i++) {
testFiniteOnly(NON_FINITE[i], new EWKBTarget(new ByteArrayOutputStream(), NON_FINITE_DIMENSIONS[i]));
}
for (int i = 0; i < NON_FINITE.length; i++) {
testFiniteOnly(NON_FINITE[i], new EWKTTarget(new StringBuilder(), NON_FINITE_DIMENSIONS[i]));
}
for (int i = 0; i < NON_FINITE.length; i++) {
testFiniteOnly(NON_FINITE[i], new GeometryTarget(NON_FINITE_DIMENSIONS[i]));
}
}
private void testFiniteOnly(byte[] ewkb, Target target) {
try {
EWKBUtils.parseEWKB(ewkb, target);
fail(target.getClass().getName() + ' ' + StringUtils.convertBytesToHex(ewkb));
} catch (IllegalArgumentException e) {
// Expected
}
}
private void testSRID() throws Exception {
byte[] ewkb = EWKTUtils.ewkt2ewkb("SRID=10;GEOMETRYCOLLECTION (POINT (1 2))");
assertEquals(StringUtils.convertHexToBytes(""
// ******** Geometry collection ********
// BOM (BigEndian)
+ "00"
// Only top-level object has a SRID
// type (SRID | POINT)
+ "20000007"
// SRID = 10
+ "0000000a"
// 1 item
+ "00000001"
// ******** Point ********
// BOM (BigEndian)
+ "00"
// type (POINT)
+ "00000001"
// 1.0
+ "3ff0000000000000"
// 2.0
+ "4000000000000000"), ewkb);
assertEquals("SRID=10;GEOMETRYCOLLECTION (POINT (1 2))", EWKTUtils.ewkb2ewkt(ewkb));
GeometryCollection gc = (GeometryCollection) JTSUtils.ewkb2geometry(ewkb);
assertEquals(10, gc.getSRID());
assertEquals(10, gc.getGeometryN(0).getSRID());
assertEquals(ewkb, JTSUtils.geometry2ewkb(gc));
}
private void testDimensions(int expected, byte[] ewkb) {
DimensionSystemTarget dst = new DimensionSystemTarget();
EWKBUtils.parseEWKB(ewkb, dst);
assertEquals(expected, dst.getDimensionSystem());
EnvelopeAndDimensionSystemTarget envelopeAndDimensionTarget = new EnvelopeAndDimensionSystemTarget();
EWKBUtils.parseEWKB(ewkb, envelopeAndDimensionTarget);
assertEquals(expected, envelopeAndDimensionTarget.getDimensionSystem());
}
private void testIntersectionAndUnion() {
double[] zero = new double[4];
assertFalse(GeometryUtils.intersects(null, null));
assertFalse(GeometryUtils.intersects(null, zero));
assertFalse(GeometryUtils.intersects(zero, null));
assertNull(GeometryUtils.union(null, null));
assertEquals(zero, GeometryUtils.union(null, zero));
assertEquals(zero, GeometryUtils.union(zero, null));
// These 30 values with fixed seed 0 are enough to cover all remaining
// cases
Random r = new Random(0);
for (int i = 0; i < 30; i++) {
double[] envelope1 = getEnvelope(r);
double[] envelope2 = getEnvelope(r);
Envelope e1 = convert(envelope1);
Envelope e2 = convert(envelope2);
assertEquals(e1.intersects(e2), GeometryUtils.intersects(envelope1, envelope2));
e1.expandToInclude(e2);
assertEquals(e1, convert(GeometryUtils.union(envelope1, envelope2)));
}
}
private static Envelope convert(double[] envelope) {
return new Envelope(envelope[MIN_X], envelope[MAX_X], envelope[MIN_Y], envelope[MAX_Y]);
}
private static double[] getEnvelope(Random r) {
double minX = r.nextDouble();
double maxX = r.nextDouble();
if (minX > maxX) {
double t = minX;
minX = maxX;
maxX = t;
}
double minY = r.nextDouble();
double maxY = r.nextDouble();
if (minY > maxY) {
double t = minY;
minY = maxY;
maxY = t;
}
return new double[] { minX, maxX, minY, maxY };
}
private void testMixedGeometries() throws Exception {
try {
EWKTUtils.ewkt2ewkb(MIXED_WKT);
fail();
} catch (IllegalArgumentException ex) {
// Expected
}
try {
EWKTUtils.ewkb2ewkt(MIXED_WKB);
fail();
} catch (IllegalArgumentException ex) {
// Expected
}
try {
JTSUtils.ewkb2geometry(MIXED_WKB);
fail();
} catch (IllegalArgumentException ex) {
// Expected
}
Geometry g = new WKTReader().read(MIXED_WKT);
try {
JTSUtils.geometry2ewkb(g);
fail();
} catch (IllegalArgumentException ex) {
// Expected
}
}
private void testMixedGeometriesAccept() throws Exception {
ProcessBuilder pb = new ProcessBuilder().redirectError(Redirect.INHERIT);
pb.command(getJVM(), "-cp", getClassPath(), "-ea", "-Dh2.mixedGeometries=true", getClass().getName(), "dummy");
assertEquals(0, pb.start().waitFor());
}
private void testMixedGeometriesAcceptImpl() throws Exception {
assertEquals(MIXED_WKB, EWKTUtils.ewkt2ewkb(MIXED_WKT));
assertEquals(MIXED_WKT_Z, EWKTUtils.ewkb2ewkt(MIXED_WKB));
Geometry g = new WKTReader().read(MIXED_WKT);
assertEquals(MIXED_WKB, JTSUtils.geometry2ewkb(g));
LineString ls = (LineString) JTSUtils.ewkb2geometry(MIXED_WKB);
CoordinateSequence cs = ls.getCoordinateSequence();
assertEquals(2, cs.size());
assertEquals(3, cs.getDimension());
assertEquals(1, cs.getOrdinate(0, X));
assertEquals(2, cs.getOrdinate(0, Y));
assertEquals(Double.NaN, cs.getOrdinate(0, Z));
assertEquals(3, cs.getOrdinate(1, X));
assertEquals(4, cs.getOrdinate(1, Y));
assertEquals(5, cs.getOrdinate(1, Z));
}
}
......@@ -47,6 +47,7 @@ public class TestUtils extends TestBase {
testIOUtils();
testSortTopN();
testSortTopNRandom();
testWriteReadInt();
testWriteReadLong();
testGetNonPrimitiveClass();
testGetNonPrimitiveClass();
......@@ -94,23 +95,62 @@ public class TestUtils extends TestBase {
}
}
private void testWriteReadInt() {
byte[] buff = new byte[4];
for (int x : new int[]{Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 1, -1,
Short.MIN_VALUE, Short.MAX_VALUE}) {
testIntImpl1(buff, x);
}
Random r = new Random(1);
for (int i = 0; i < 1000; i++) {
testIntImpl1(buff, r.nextInt());
}
}
private void testIntImpl1(byte[] buff, int x) {
int r = Integer.reverseBytes(x);
Bits.writeInt(buff, 0, x);
testIntImpl2(buff, x, r);
Bits.writeIntLE(buff, 0, x);
testIntImpl2(buff, r, x);
}
private void testIntImpl2(byte[] buff, int x, int r) {
assertEquals(x, Bits.readInt(buff, 0));
assertEquals(r, Bits.readIntLE(buff, 0));
}
private void testWriteReadLong() {
byte[] buff = new byte[8];
for (long x : new long[]{Long.MIN_VALUE, Long.MAX_VALUE, 0, 1, -1,
Integer.MIN_VALUE, Integer.MAX_VALUE}) {
Bits.writeLong(buff, 0, x);
long y = Bits.readLong(buff, 0);
assertEquals(x, y);
testLongImpl1(buff, x);
}
Random r = new Random(1);
for (int i = 0; i < 1000; i++) {
long x = r.nextLong();
Bits.writeLong(buff, 0, x);
long y = Bits.readLong(buff, 0);
assertEquals(x, y);
testLongImpl1(buff, r.nextLong());
}
}
private void testLongImpl1(byte[] buff, long x) {
long r = Long.reverseBytes(x);
Bits.writeLong(buff, 0, x);
testLongImpl2(buff, x, r);
Bits.writeLongLE(buff, 0, x);
testLongImpl2(buff, r, x);
Bits.writeDouble(buff, 0, Double.longBitsToDouble(x));
testLongImpl2(buff, x, r);
Bits.writeDoubleLE(buff, 0, Double.longBitsToDouble(x));
testLongImpl2(buff, r, x);
}
private void testLongImpl2(byte[] buff, long x, long r) {
assertEquals(x, Bits.readLong(buff, 0));
assertEquals(r, Bits.readLongLE(buff, 0));
assertEquals(Double.longBitsToDouble(x), Bits.readDouble(buff, 0));
assertEquals(Double.longBitsToDouble(r), Bits.readDoubleLE(buff, 0));
}
private void testSortTopN() {
Comparator<Integer> comp = new Comparator<Integer>() {
@Override
......
......@@ -792,3 +792,6 @@ converging smth rng curs casts unmapping unmapper
immediate hhmmss scheduled hhmm prematurely postponed arranges subexpression subexpressions encloses plane caution
minxf maxxf minyf maxyf bminxf bmaxxf bminyf bmaxyf
minxd maxxd minyd maxyd bminxd bmaxxd bminyd bmaxyd
interior envelopes multilinestring multipoint packed exterior normalization awkward determination subgeometries
xym normalizes coord setz xyzm geometrycollection multipolygon mixup rings polygons rejection finite
pointzm pointz pointm dimensionality
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论