提交 9ca18311 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Add utilities for GEOMETRY data type

上级 5f682ece
/*
* 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.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 java.io.ByteArrayOutputStream;
import org.h2.util.Bits;
import org.h2.util.StringUtils;
import org.h2.util.geometry.GeometryUtils.DimensionSystemTarget;
import org.h2.util.geometry.GeometryUtils.Target;
/**
* Utilities for GEOMETRY data type.
*/
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 level;
/**
* 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 startPoint(int srid) {
writeHeader(POINT, srid);
}
@Override
protected void startLineString(int srid, int numPoints) {
writeHeader(LINE_STRING, srid);
writeInt(numPoints);
}
@Override
protected void startPolygon(int srid, int numInner, int numPoints) {
writeHeader(POLYGON, srid);
writeInt(numInner + 1);
writeInt(numPoints);
}
@Override
protected void startPolygonInner(int numInner) {
writeInt(numInner);
}
@Override
protected void startCollection(int type, int srid, int numItems) {
writeHeader(type, srid);
writeInt(numItems);
}
private void writeHeader(int type, int srid) {
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;
}
// Never write SRID in inner objects
if (level > 0) {
srid = 0;
} else if (srid != 0) {
type |= EWKB_SRID;
}
output.write(0);
writeInt(type);
if (srid != 0) {
writeInt(srid);
}
}
@Override
protected Target startCollectionItem(int index, int total) {
level++;
return this;
}
@Override
protected void endCollectionItem(Target target, int index, int total) {
level--;
}
@Override
protected void addCoordinate(double x, double y, double z, double m, int index, int total) {
writeDouble(x);
writeDouble(y);
if ((dimensionSystem & DIMENSION_SYSTEM_XYZ) != 0) {
writeDouble(z);
}
if ((dimensionSystem & DIMENSION_SYSTEM_XYM) != 0) {
writeDouble(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.writeLong(buf, 0, Double.doubleToLongBits(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 = Bits.readInt(ewkb, offset);
offset += 4;
return bigEndian ? result : Integer.reverseBytes(result);
}
/**
* Reads a 64-bit floating point using current byte order.
*
* @return next 64-bit floating point
*/
double readCoordinate() {
long v = Bits.readLong(ewkb, offset);
offset += 8;
if (!bigEndian) {
Long.reverseBytes(v);
}
return toCanonicalDouble(Double.longBitsToDouble(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();
parseEKWB(ewkb, dimensionTarget);
// Write an EWKB
ByteArrayOutputStream output = new ByteArrayOutputStream();
EWKBTarget target = new EWKBTarget(output, dimensionTarget.getDimensionSystem());
parseEKWB(ewkb, target);
return output.toByteArray();
}
/**
* Parses a EWKB.
*
* @param ewkb
* EWKB representation
* @param target
* output target
*/
public static void parseEKWB(byte[] ewkb, Target target) {
parseEKWB(new EWKBSource(ewkb), target, 0, 0);
}
/**
* Parses a EWKB.
*
* @param source
* EWKB source
* @param target
* output target
* @param parentType
* type of parent geometry collection, or 0 for the root geometry
* @param parentSrid
* SRID of a parent geometry collection, or any value for the
* root geometry (will be determined from the EWKB instead)
*/
private static void parseEKWB(EWKBSource source, Target target, int parentType, int parentSrid) {
try {
// 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;
// Preserve parent SRID unconditionally
if (parentType != 0) {
srid = parentSrid;
}
// 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(srid);
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(srid, 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(srid, 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, srid, numItems);
for (int i = 0; i < numItems; i++) {
Target innerTarget = target.startCollectionItem(i, numItems);
parseEKWB(source, innerTarget, type, srid);
target.endCollectionItem(innerTarget, i, numItems);
}
target.endCollection(type);
break;
}
default:
throw new IllegalArgumentException();
}
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
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);
}
/**
* 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;
}
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_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.util.StringUtils;
import org.h2.util.geometry.EWKBUtils.EWKBTarget;
import org.h2.util.geometry.GeometryUtils.DimensionSystemTarget;
import org.h2.util.geometry.GeometryUtils.Target;
/**
* Utilities for GEOMETRY data type.
*/
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 level;
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 startPoint(int srid) {
writeHeader(POINT, srid);
}
@Override
protected void startLineString(int srid, int numPoints) {
writeHeader(LINE_STRING, srid);
if (numPoints == 0) {
output.append("EMPTY");
}
}
@Override
protected void startPolygon(int srid, int numInner, int numPoints) {
writeHeader(POLYGON, srid);
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 srid, int numItems) {
writeHeader(type, srid);
if (numItems == 0) {
output.append("EMPTY");
}
if (type != GEOMETRY_COLLECTION) {
inMulti = true;
}
}
private void writeHeader(int type, int srid) {
// Never write SRID in inner objects
if (level == 0 && srid != 0) {
output.append("SRID=").append(srid).append(';');
}
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) {
level++;
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(')');
}
level--;
}
@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 (index == 0) {
output.append('(');
} else {
output.append(", ");
}
writeDouble(x);
output.append(' ');
writeDouble(y);
if ((dimensionSystem & DIMENSION_SYSTEM_XYZ) != 0) {
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(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;
int srid;
EWKTSource(String ewkt) {
this.ewkt = ewkt;
if (ewkt.startsWith("SRID=")) {
int idx = ewkt.indexOf(';', 5);
srid = Integer.parseInt(ewkt.substring(5, idx));
offset = idx + 1;
} else {
srid = 0;
}
}
void read(char symbol) {
skipWS();
int len = ewkt.length();
if (offset >= len) {
throw new IllegalArgumentException();
}
if (ewkt.charAt(offset) != symbol) {
throw new IllegalArgumentException();
}
offset++;
}
boolean readEmpty(boolean empty) {
if (empty) {
return true;
}
skipWS();
int len = ewkt.length();
if (offset >= len) {
throw new IllegalArgumentException();
}
if (ewkt.charAt(offset) == '(') {
offset++;
return false;
}
if (!readWord().equals("EMPTY")) {
throw new IllegalArgumentException();
}
return true;
}
String readWord() {
return readWordImpl(true);
}
String tryReadWord() {
return readWordImpl(false);
}
private String readWordImpl(boolean required) {
skipWS();
int len = ewkt.length();
if (offset >= len) {
if (required) {
throw new IllegalArgumentException();
} else {
return null;
}
}
char ch = ewkt.charAt(offset);
if (!isLatinLetter(ch)) {
if (required) {
throw new IllegalArgumentException();
} else {
return null;
}
}
int start = offset++;
while (offset < len && isLatinLetter(ch = ewkt.charAt(offset))) {
offset++;
}
if (offset < len) {
if (ch > ' ' && ch != '(' && ch != ')' && ch != ',') {
throw new IllegalArgumentException();
}
}
return StringUtils.toUpperEnglish(ewkt.substring(start, offset));
}
private static boolean isLatinLetter(char ch) {
return ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z';
}
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 '.':
case 'N':
case 'n':
return true;
default:
return false;
}
}
private static boolean isNumberPart(char ch) {
if (ch >= '0' && ch <= '9') {
return true;
}
switch (ch) {
case '+':
case '-':
case '.':
case 'A':
case 'E':
case 'N':
case 'a':
case 'e':
case 'n':
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.parseEKWB(ewkb, dimensionTarget);
// Write an EWKT
StringBuilder output = new StringBuilder();
EWKTTarget target = new EWKTTarget(output, dimensionTarget.getDimensionSystem());
EWKBUtils.parseEKWB(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
ByteArrayOutputStream output = new ByteArrayOutputStream();
EWKBTarget target = new EWKBTarget(output, dimensionTarget.getDimensionSystem());
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, false, false);
}
/**
* 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 useZ
* parent geometry uses dimension Z
* @param useM
* parent geometry uses dimension M
*/
private static void parseEWKT(EWKTSource source, Target target, int parentType, boolean useZ, boolean useM) {
String type;
boolean empty = false;
switch (parentType) {
default: {
type = source.readWord();
if (type.endsWith("M")) {
useM = true;
if (type.endsWith("ZM")) {
useZ = true;
type = type.substring(0, type.length() - 2);
} else {
type = type.substring(0, type.length() - 1);
}
} else if (type.endsWith("Z")) {
useZ = true;
type = type.substring(0, type.length() - 1);
} else {
String s = source.tryReadWord();
if (s != null) {
switch (s) {
case "Z":
useZ = true;
break;
case "M":
useM = true;
break;
case "ZM":
useZ = useM = true;
break;
case "EMPTY":
empty = true;
break;
default:
throw new IllegalArgumentException();
}
}
}
break;
}
case MULTI_POINT:
type = "POINT";
break;
case MULTI_LINE_STRING:
type = "LINESTRING";
break;
case MULTI_POLYGON:
type = "POLYGON";
break;
}
switch (type) {
case "POINT":
if (parentType != 0 && parentType != MULTI_POINT && parentType != GEOMETRY_COLLECTION || empty) {
throw new IllegalArgumentException();
}
target.startPoint(source.srid);
source.read('(');
addCoordinate(source, target, useZ, useM, 0, 1);
source.read(')');
break;
case "LINESTRING": {
if (parentType != 0 && parentType != MULTI_LINE_STRING && parentType != GEOMETRY_COLLECTION) {
throw new IllegalArgumentException();
}
empty = source.readEmpty(empty);
if (empty) {
target.startLineString(source.srid, 0);
} else {
ArrayList<double[]> coordinates = new ArrayList<>();
do {
coordinates.add(readCoordinate(source, useZ, useM));
} while (source.hasMoreCoordinates());
int numPoints = coordinates.size();
if (numPoints < 0 || numPoints == 1) {
throw new IllegalArgumentException();
}
target.startLineString(source.srid, 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();
}
empty = source.readEmpty(empty);
if (empty) {
target.startPolygon(source.srid, 0, 0);
} else {
ArrayList<double[]> outer = readRing(source, useZ, useM);
ArrayList<ArrayList<double[]>> inner = new ArrayList<>();
while (source.hasMoreCoordinates()) {
inner.add(readRing(source, useZ, useM));
}
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(source.srid, numInner, size);
if (size > 0) {
addRing(outer, target, useZ, useM);
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, useZ, useM);
}
target.endNonEmptyPolygon();
}
}
break;
}
case "MULTIPOINT":
parseCollection(source, target, MULTI_POINT, parentType, empty, useZ, useM);
break;
case "MULTILINESTRING":
parseCollection(source, target, MULTI_LINE_STRING, parentType, empty, useZ, useM);
break;
case "MULTIPOLYGON":
parseCollection(source, target, MULTI_POLYGON, parentType, empty, useZ, useM);
break;
case "GEOMETRYCOLLECTION":
parseCollection(source, target, GEOMETRY_COLLECTION, parentType, empty, useZ, useM);
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, boolean empty,
boolean useZ, boolean useM) {
if (parentType != 0 && parentType != GEOMETRY_COLLECTION) {
throw new IllegalArgumentException();
}
if (source.readEmpty(empty)) {
target.startCollection(type, source.srid, 0);
} else {
if (type == MULTI_POINT && source.hasCoordinate()) {
parseMultiPointAlternative(source, target, useZ, useM);
} else {
int numItems = source.getItemCount();
target.startCollection(type, source.srid, numItems);
for (int i = 0; i < numItems; i++) {
if (i > 0) {
source.read(',');
}
Target innerTarget = target.startCollectionItem(i, numItems);
parseEWKT(source, innerTarget, type, useZ, useM);
target.endCollectionItem(innerTarget, i, numItems);
}
source.read(')');
}
}
target.endCollection(type);
}
private static void parseMultiPointAlternative(EWKTSource source, Target target, boolean useZ, boolean useM) {
// Special case for MULTIPOINT (1 2, 3 4)
ArrayList<double[]> points = new ArrayList<>();
do {
points.add(readCoordinate(source, useZ, useM));
} while (source.hasMoreCoordinates());
int numItems = points.size();
target.startCollection(MULTI_POINT, source.srid, numItems);
for (int i = 0; i < points.size(); i++) {
Target innerTarget = target.startCollectionItem(i, numItems);
target.startPoint(source.srid);
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, boolean useZ, boolean useM) {
if (source.readEmpty(false)) {
return new ArrayList<>(0);
}
ArrayList<double[]> result = new ArrayList<>();
double[] c = readCoordinate(source, useZ, useM);
double startX = c[X], startY = c[Y];
result.add(c);
while (source.hasMoreCoordinates()) {
result.add(readCoordinate(source, useZ, useM));
}
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, boolean useZ, boolean useM) {
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, boolean useZ, boolean useM, int index,
int total) {
double x = source.readCoordinate();
double y = source.readCoordinate();
double z = Double.NaN, m = Double.NaN;
if (source.hasCoordinate()) {
if (!useZ && useM) {
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, boolean useZ, boolean useM) {
double x = source.readCoordinate();
double y = source.readCoordinate();
double z = Double.NaN, m = Double.NaN;
if (source.hasCoordinate()) {
if (!useZ && useM) {
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;
import org.h2.util.Bits;
/**
* Utilities for GEOMETRY data type.
*/
public final class GeometryUtils {
/**
* Converter output target.
*/
public static abstract class Target {
public Target() {
}
/**
* Invoked before writing a POINT.
*
* @param srid
* SRID
*/
protected void startPoint(int srid) {
}
/**
* Invoked before writing a LINESTRING.
*
* @param srid
* SRID
* @param numPoints
* number of points in line string
*/
protected void startLineString(int srid, int numPoints) {
}
/**
* Invoked before writing a POLYGON.
*
* @param srid
* SRID
* @param numInner
* number of inner polygons
* @param numPoints
* number of points in outer polygon
*/
protected void startPolygon(int srid, 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 srid
* SRID
* @param numItems
* number of items in this collection
*/
protected void startCollection(int type, int srid, 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, 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(int srid) {
enabled = true;
}
@Override
protected void startLineString(int srid, int numPoints) {
enabled = true;
}
@Override
protected void startPolygon(int srid, 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 (enabled) {
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(int srid) {
enabled = true;
}
@Override
protected void startLineString(int srid, int numPoints) {
enabled = true;
}
@Override
protected void startPolygon(int srid, 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;
}
if (enabled) {
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) {
EnvelopeAndDimensionSystemTarget target = new EnvelopeAndDimensionSystemTarget();
EWKBUtils.parseEKWB(ewkb, target);
return target.getEnvelope();
}
/**
* 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.writeLong(result, 5, Double.doubleToRawLongBits(minX));
Bits.writeLong(result, 13, Double.doubleToRawLongBits(minY));
} else if (minX == maxX || minY == maxY) {
result = new byte[41];
result[4] = LINE_STRING;
result[8] = 2;
Bits.writeLong(result, 9, Double.doubleToRawLongBits(minX));
Bits.writeLong(result, 17, Double.doubleToRawLongBits(minY));
Bits.writeLong(result, 25, Double.doubleToRawLongBits(maxX));
Bits.writeLong(result, 33, Double.doubleToRawLongBits(maxY));
} else {
result = new byte[93];
result[4] = POLYGON;
result[8] = 1;
result[12] = 5;
Bits.writeLong(result, 13, Double.doubleToRawLongBits(minX));
Bits.writeLong(result, 21, Double.doubleToRawLongBits(minY));
Bits.writeLong(result, 29, Double.doubleToRawLongBits(minX));
Bits.writeLong(result, 37, Double.doubleToRawLongBits(maxY));
Bits.writeLong(result, 45, Double.doubleToRawLongBits(maxX));
Bits.writeLong(result, 53, Double.doubleToRawLongBits(maxY));
Bits.writeLong(result, 61, Double.doubleToRawLongBits(maxX));
Bits.writeLong(result, 69, Double.doubleToRawLongBits(minY));
Bits.writeLong(result, 77, Double.doubleToRawLongBits(minX));
Bits.writeLong(result, 85, Double.doubleToRawLongBits(minY));
}
return result;
}
/**
* 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];
if (minX1 > minX2) {
minX1 = minX2;
}
if (maxX1 < maxX2) {
maxX1 = maxX2;
}
if (minY1 > minY2) {
minY1 = minY2;
}
if (maxY1 < maxY2) {
maxY1 = maxY2;
}
return new double[] { minX1, maxX1, minY1, maxY1 };
}
/**
* 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;
}
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.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.toCanonicalDouble;
import java.io.ByteArrayOutputStream;
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.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 {
/**
* 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;
}
@Override
protected void startPoint(int srid) {
init(POINT, srid);
initCoordinates(1);
innerOffset = -1;
}
@Override
protected void startLineString(int srid, int numPoints) {
init(LINE_STRING, srid);
initCoordinates(numPoints);
innerOffset = -1;
}
@Override
protected void startPolygon(int srid, int numInner, int numPoints) {
init(POLYGON, srid);
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 srid, int numItems) {
init(type, srid);
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);
}
@Override
protected void endCollectionItem(Target target, int index, int total) {
subgeometries[index] = ((GeometryTarget) target).getGeometry();
}
private void init(int type, int srid) {
factory = new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING), srid,
(dimensionSystem & DIMENSION_SYSTEM_XYM) != 0 ? PackedCoordinateSequenceFactory.DOUBLE_FACTORY
: CoordinateArraySequenceFactory.instance());
this.type = type;
}
private void initCoordinates(int numPoints) {
coordinates = createCoordinates(numPoints);
}
private CoordinateSequence createCoordinates(int numPoints) {
return factory.getCoordinateSequenceFactory().create(numPoints,
(dimensionSystem & DIMENSION_SYSTEM_XYM) != 0 ? 4 : 3);
}
@Override
protected void addCoordinate(double x, double y, double z, double m, int index, int total) {
CoordinateSequence coordinates = innerOffset < 0 ? this.coordinates : innerCoordinates[innerOffset];
coordinates.setOrdinate(index, X, x);
coordinates.setOrdinate(index, Y, y);
coordinates.setOrdinate(index, Z, z);
if ((dimensionSystem & DIMENSION_SYSTEM_XYM) != 0) {
coordinates.setOrdinate(index, M, 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.parseEKWB(ewkb, dimensionTarget);
// Generate a Geometry
GeometryTarget target = new GeometryTarget(dimensionTarget.getDimensionSystem());
EWKBUtils.parseEKWB(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
ByteArrayOutputStream output = new ByteArrayOutputStream();
EWKBTarget target = new EWKBTarget(output, dimensionTarget.getDimensionSystem());
parseGeometry(geometry, target);
return output.toByteArray();
}
/**
* Parses a JTS Geometry object.
*
* @param geometry
* geometry to parse
* @param target
* output target
*/
private static void parseGeometry(Geometry geometry, Target target) {
parseGeometry(geometry, target, 0, 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
* @param parentSrid
* SRID of a parent geometry collection, or any value for the
* root geometry (will be determined from the EWKB instead)
*/
private static void parseGeometry(Geometry geometry, Target target, int parentType, int parentSrid) {
int srid = geometry.getSRID();
// Preserve parent SRID unconditionally
if (parentType != 0) {
srid = parentSrid;
}
if (geometry instanceof Point) {
if (parentType != 0 && parentType != MULTI_POINT && parentType != GEOMETRY_COLLECTION) {
throw new IllegalArgumentException();
}
target.startPoint(srid);
Point p = (Point) geometry;
addCoordinate(p.getCoordinateSequence(), target, 0, 1);
} 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(srid, numPoints);
for (int i = 0; i < numPoints; i++) {
addCoordinate(cs, target, i, numPoints);
}
} 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(srid, numInner, size);
if (size > 0) {
addRing(cs, target, size);
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);
}
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, srid, numItems);
for (int i = 0; i < numItems; i++) {
Target innerTarget = target.startCollectionItem(i, numItems);
parseGeometry(gc.getGeometryN(i), innerTarget, type, srid);
target.endCollectionItem(innerTarget, i, numItems);
}
target.endCollection(type);
} else {
throw new IllegalArgumentException();
}
}
private static void addRing(CoordinateSequence sequence, Target target, int size) {
// 0 or 4+ are valid
if (size >= 4) {
int d = sequence.getDimension();
boolean useZ = d >= 3, useM = d >= 4;
double startX = toCanonicalDouble(sequence.getOrdinate(0, X)),
startY = toCanonicalDouble(sequence.getOrdinate(0, Y));
target.addCoordinate(startX, startY, //
useZ ? toCanonicalDouble(sequence.getOrdinate(0, Z)) : Double.NaN,
useM ? toCanonicalDouble(sequence.getOrdinate(0, M)) : Double.NaN, //
0, size);
for (int i = 1; i < size - 1; i++) {
addCoordinate(sequence, target, i, size);
}
double endX = toCanonicalDouble(sequence.getOrdinate(size - 1, X)),
endY = toCanonicalDouble(sequence.getOrdinate(size - 1, 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();
}
target.addCoordinate(endX, endY, //
useZ ? toCanonicalDouble(sequence.getOrdinate(size - 1, Z)) : Double.NaN,
useM ? toCanonicalDouble(sequence.getOrdinate(size - 1, M)) : Double.NaN, //
size - 1, size);
}
}
private static void addCoordinate(CoordinateSequence sequence, Target target, int index, int total) {
int d = sequence.getDimension();
target.addCoordinate(toCanonicalDouble(sequence.getOrdinate(index, X)),
toCanonicalDouble(sequence.getOrdinate(index, Y)),
d >= 3 ? toCanonicalDouble(sequence.getOrdinate(index, Z)) : Double.NaN,
d >= 4 ? toCanonicalDouble(sequence.getOrdinate(index, M)) : Double.NaN, //
index, total);
}
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
......@@ -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;
......@@ -960,6 +961,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());
......
/*
* 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.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.util.Locale;
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.EWKTUtils;
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.JTSUtils;
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.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 {
/**
* Run just this test.
*
* @param a
* ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
@Override
public void test() throws Exception {
testPoint();
testLineString();
testPolygon();
testMultiPoint();
testMultiLineString();
testMultiPolygon();
testGeometryCollection();
testDimensionM();
testDimensionZM();
testSRID();
testIntersectionAndUnion();
}
private void testPoint() throws Exception {
testGeometry("POINT (1 2)", 2);
testGeometry("POINT (-1.3 NaN)", 2);
testGeometry("POINT (-1E32 NaN)", "POINT (-1E32 NaN)", "POINT (-100000000000000000000000000000000 NaN)", 2);
testGeometry("POINT Z (2.7 -3 34)", 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 (1 NaN, 2 NaN)", 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);
// JTS has a bug with NaN on non-English locales
Locale l = Locale.getDefault();
Locale.setDefault(Locale.ENGLISH);
assertEquals(jtsWkt.replaceAll(" Z", ""), new WKTWriter(numOfDimensions).write(geometryFromH2));
Locale.setDefault(l);
// 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.parseEKWB(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(GeometryUtils.envelope2wkb(envelopeFromH2));
} else {
double minX = envelopeFromJTS.getMinX(), maxX = envelopeFromJTS.getMaxX();
double minY = envelopeFromJTS.getMinY(), maxY = envelopeFromJTS.getMaxY();
assertEquals(minX, envelopeFromH2[0]);
assertEquals(maxX, envelopeFromH2[1]);
assertEquals(minY, envelopeFromH2[2]);
assertEquals(maxY, envelopeFromH2[3]);
// TODO determine what to do with NaNs in dimensions X and Y
if (!Double.isNaN(minX) && !Double.isNaN(maxX) && !Double.isNaN(minY) && !Double.isNaN(maxY)) {
assertEquals(new WKBWriter(2).write(new GeometryFactory().toGeometry(envelopeFromJTS)),
GeometryUtils.envelope2wkb(envelopeFromH2));
}
}
}
private void testDimensionM() {
byte[] ewkb = EWKTUtils.ewkt2ewkb("POINT M (1 2 3)");
assertEquals("POINT M (1 2 3)", EWKTUtils.ewkb2ewkt(ewkb));
Point p = (Point) JTSUtils.ewkb2geometry(ewkb);
CoordinateSequence cs = p.getCoordinateSequence();
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));
assertEquals(ewkb, JTSUtils.geometry2ewkb(p));
testDimensions(GeometryUtils.DIMENSION_SYSTEM_XYM, ewkb);
}
private void testDimensionZM() {
byte[] ewkb = EWKTUtils.ewkt2ewkb("POINT ZM (1 2 3 4)");
assertEquals("POINT ZM (1 2 3 4)", EWKTUtils.ewkb2ewkt(ewkb));
Point p = (Point) JTSUtils.ewkb2geometry(ewkb);
CoordinateSequence cs = p.getCoordinateSequence();
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));
assertEquals(ewkb, JTSUtils.geometry2ewkb(p));
testDimensions(GeometryUtils.DIMENSION_SYSTEM_XYZM, ewkb);
}
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.parseEKWB(ewkb, dst);
assertEquals(expected, dst.getDimensionSystem());
EnvelopeAndDimensionSystemTarget envelopeAndDimensionTarget = new EnvelopeAndDimensionSystemTarget();
EWKBUtils.parseEKWB(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 };
}
}
......@@ -792,3 +792,5 @@ 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 ekwb normalizes coord setz xyzm geometrycollection multipolygon mixup rings polygons
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论