org.h2.util.geometry.GeoJsonUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of h2-mvstore Show documentation
Show all versions of h2-mvstore Show documentation
Fork of h2database to maintain Java 8 compatibility
The newest version!
/*
* Copyright 2004-2023 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (https://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 java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import org.h2.api.ErrorCode;
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.h2.util.json.JSONArray;
import org.h2.util.json.JSONByteArrayTarget;
import org.h2.util.json.JSONBytesSource;
import org.h2.util.json.JSONNull;
import org.h2.util.json.JSONNumber;
import org.h2.util.json.JSONObject;
import org.h2.util.json.JSONString;
import org.h2.util.json.JSONValue;
import org.h2.util.json.JSONValueTarget;
/**
* GeoJson format support for GEOMETRY data type.
*/
public final class GeoJsonUtils {
/**
* 0-based type names of geometries, subtract 1 from type code to get index
* in this array.
*/
static final String[] TYPES = { //
"Point", //
"LineString", //
"Polygon", //
"MultiPoint", //
"MultiLineString", //
"MultiPolygon", //
"GeometryCollection", //
};
/**
* Converter output target that writes a GeoJson.
*/
public static final class GeoJsonTarget extends Target {
private final JSONByteArrayTarget output;
private final int dimensionSystem;
private int type;
private boolean inMulti, inMultiLine, wasEmpty;
/**
* Creates new GeoJson output target.
*
* @param output
* output JSON target
* @param dimensionSystem
* dimension system to use
*/
public GeoJsonTarget(JSONByteArrayTarget output, int dimensionSystem) {
if (dimensionSystem == DIMENSION_SYSTEM_XYM) {
throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1,
"M (XYM) dimension system is not supported in GeoJson");
}
this.output = output;
this.dimensionSystem = dimensionSystem;
}
@Override
protected void startPoint() {
type = POINT;
wasEmpty = false;
}
@Override
protected void startLineString(int numPoints) {
writeHeader(LINE_STRING);
if (numPoints == 0) {
output.endArray();
}
}
@Override
protected void startPolygon(int numInner, int numPoints) {
writeHeader(POLYGON);
if (numPoints == 0) {
output.endArray();
} else {
output.startArray();
}
}
@Override
protected void startPolygonInner(int numInner) {
output.startArray();
if (numInner == 0) {
output.endArray();
}
}
@Override
protected void endNonEmptyPolygon() {
output.endArray();
}
@Override
protected void startCollection(int type, int numItems) {
writeHeader(type);
if (type != GEOMETRY_COLLECTION) {
inMulti = true;
if (type == MULTI_LINE_STRING || type == MULTI_POLYGON) {
inMultiLine = true;
}
}
}
@Override
protected Target startCollectionItem(int index, int total) {
if (inMultiLine) {
output.startArray();
}
return this;
}
@Override
protected void endObject(int type) {
switch (type) {
case MULTI_POINT:
case MULTI_LINE_STRING:
case MULTI_POLYGON:
inMultiLine = inMulti = false;
//$FALL-THROUGH$
case GEOMETRY_COLLECTION:
output.endArray();
}
if (!inMulti && !wasEmpty) {
output.endObject();
}
}
private void writeHeader(int type) {
this.type = type;
wasEmpty = false;
if (!inMulti) {
writeStartObject(type);
}
}
@Override
protected void addCoordinate(double x, double y, double z, double m, int index, int total) {
if (type == POINT) {
if (Double.isNaN(x) && Double.isNaN(y) && Double.isNaN(z) && Double.isNaN(m)) {
wasEmpty = true;
output.valueNull();
return;
}
if (!inMulti) {
writeStartObject(POINT);
}
}
output.startArray();
writeDouble(x);
writeDouble(y);
if ((dimensionSystem & DIMENSION_SYSTEM_XYZ) != 0) {
writeDouble(z);
}
if ((dimensionSystem & DIMENSION_SYSTEM_XYM) != 0) {
writeDouble(m);
}
output.endArray();
if (type != POINT && index + 1 == total) {
output.endArray();
}
}
private void writeStartObject(int type) {
output.startObject();
output.member("type");
output.valueString(TYPES[type - 1]);
output.member(type != GEOMETRY_COLLECTION ? "coordinates" : "geometries");
if (type != POINT) {
output.startArray();
}
}
private void writeDouble(double v) {
output.valueNumber(BigDecimal.valueOf(GeometryUtils.checkFinite(v)).stripTrailingZeros());
}
}
/**
* Converts EWKB with known dimension system to GeoJson.
*
* @param ewkb
* geometry object in EWKB format
* @param dimensionSystem
* dimension system of the specified object, may be the same or
* smaller than its real dimension system. M dimension system is
* not supported.
* @return GeoJson representation of the specified geometry
* @throws DbException
* on unsupported dimension system
*/
public static byte[] ewkbToGeoJson(byte[] ewkb, int dimensionSystem) {
JSONByteArrayTarget output = new JSONByteArrayTarget();
GeoJsonTarget target = new GeoJsonTarget(output, dimensionSystem);
EWKBUtils.parseEWKB(ewkb, target);
return output.getResult();
}
/**
* Converts EWKB with known dimension system to GeoJson.
*
* @param json
* geometry object in GeoJson format
* @param srid
* the SRID of geometry
* @return GeoJson representation of the specified geometry
* @throws DbException
* on unsupported dimension system
*/
public static byte[] geoJsonToEwkb(byte[] json, int srid) {
JSONValue v = JSONBytesSource.parse(json, new JSONValueTarget());
DimensionSystemTarget dst = new DimensionSystemTarget();
parse(v, dst);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
EWKBTarget target = new EWKBTarget(baos, dst.getDimensionSystem());
target.init(srid);
parse(v, target);
return baos.toByteArray();
}
private static void parse(JSONValue v, GeometryUtils.Target target) {
if (v instanceof JSONNull) {
target.startPoint();
target.addCoordinate(Double.NaN, Double.NaN, Double.NaN, Double.NaN, 0, 1);
target.endObject(POINT);
} else if (v instanceof JSONObject) {
JSONObject o = (JSONObject) v;
JSONValue t = o.getFirst("type");
if (!(t instanceof JSONString)) {
throw new IllegalArgumentException();
}
switch (((JSONString) t).getString()) {
case "Point":
parse(o, target, POINT);
break;
case "LineString":
parse(o, target, LINE_STRING);
break;
case "Polygon":
parse(o, target, POLYGON);
break;
case "MultiPoint":
parse(o, target, MULTI_POINT);
break;
case "MultiLineString":
parse(o, target, MULTI_LINE_STRING);
break;
case "MultiPolygon":
parse(o, target, MULTI_POLYGON);
break;
case "GeometryCollection":
parseGeometryCollection(o, target);
break;
default:
throw new IllegalArgumentException();
}
} else {
throw new IllegalArgumentException();
}
}
private static void parse(JSONObject o, Target target, int type) {
JSONValue t = o.getFirst("coordinates");
if (!(t instanceof JSONArray)) {
throw new IllegalArgumentException();
}
JSONArray a = (JSONArray) t;
switch (type) {
case POINT:
target.startPoint();
parseCoordinate(a, target, 0, 1);
target.endObject(POINT);
break;
case LINE_STRING: {
parseLineString(a, target);
break;
}
case POLYGON: {
parsePolygon(a, target);
break;
}
case MULTI_POINT: {
JSONValue[] points = a.getArray();
int numPoints = points.length;
target.startCollection(MULTI_POINT, numPoints);
for (int i = 0; i < numPoints; i++) {
target.startPoint();
parseCoordinate(points[i], target, 0, 1);
target.endObject(POINT);
target.endCollectionItem(target, MULTI_POINT, i, numPoints);
}
target.endObject(MULTI_POINT);
break;
}
case MULTI_LINE_STRING: {
JSONValue[] strings = a.getArray();
int numStrings = strings.length;
target.startCollection(MULTI_LINE_STRING, numStrings);
for (int i = 0; i < numStrings; i++) {
JSONValue string = strings[i];
if (!(string instanceof JSONArray)) {
throw new IllegalArgumentException();
}
parseLineString((JSONArray) string, target);
target.endCollectionItem(target, MULTI_LINE_STRING, i, numStrings);
}
target.endObject(MULTI_LINE_STRING);
break;
}
case MULTI_POLYGON: {
JSONValue[] polygons = a.getArray();
int numPolygons = polygons.length;
target.startCollection(MULTI_POLYGON, numPolygons);
for (int i = 0; i < numPolygons; i++) {
JSONValue string = polygons[i];
if (!(string instanceof JSONArray)) {
throw new IllegalArgumentException();
}
parsePolygon((JSONArray) string, target);
target.endCollectionItem(target, MULTI_POLYGON, i, numPolygons);
}
target.endObject(MULTI_POLYGON);
break;
}
default:
throw new IllegalArgumentException();
}
}
private static void parseGeometryCollection(JSONObject o, Target target) {
JSONValue t = o.getFirst("geometries");
if (!(t instanceof JSONArray)) {
throw new IllegalArgumentException();
}
JSONArray a = (JSONArray) t;
JSONValue[] geometries = a.getArray();
int numGeometries = geometries.length;
target.startCollection(GEOMETRY_COLLECTION, numGeometries);
for (int i = 0; i < numGeometries; i++) {
JSONValue geometry = geometries[i];
parse(geometry, target);
target.endCollectionItem(target, GEOMETRY_COLLECTION, i, numGeometries);
}
target.endObject(GEOMETRY_COLLECTION);
}
private static void parseLineString(JSONArray a, Target target) {
JSONValue[] points = a.getArray();
int numPoints = points.length;
target.startLineString(numPoints);
for (int i = 0; i < numPoints; i++) {
parseCoordinate(points[i], target, i, numPoints);
}
target.endObject(LINE_STRING);
}
private static void parsePolygon(JSONArray a, Target target) {
JSONValue[] rings = a.getArray();
int numRings = rings.length;
if (numRings == 0) {
target.startPolygon(0, 0);
} else {
JSONValue ring = rings[0];
if (!(ring instanceof JSONArray)) {
throw new IllegalArgumentException();
}
JSONValue[] points = ((JSONArray) ring).getArray();
target.startPolygon(numRings - 1, points.length);
parseRing(points, target);
for (int i = 1; i < numRings; i++) {
ring = rings[i];
if (!(ring instanceof JSONArray)) {
throw new IllegalArgumentException();
}
points = ((JSONArray) ring).getArray();
target.startPolygonInner(points.length);
parseRing(points, target);
}
target.endNonEmptyPolygon();
}
target.endObject(POLYGON);
}
private static void parseRing(JSONValue[] points, Target target) {
int numPoints = points.length;
for (int i = 0; i < numPoints; i++) {
parseCoordinate(points[i], target, i, numPoints);
}
}
private static void parseCoordinate(JSONValue v, Target target, int index, int total) {
if (v instanceof JSONNull) {
target.addCoordinate(Double.NaN, Double.NaN, Double.NaN, Double.NaN, 0, 1);
return;
}
if (!(v instanceof JSONArray)) {
throw new IllegalArgumentException();
}
JSONValue[] values = ((JSONArray) v).getArray();
int length = values.length;
if (length < 2) {
throw new IllegalArgumentException();
}
target.addCoordinate(readCoordinate(values, X), readCoordinate(values, Y), readCoordinate(values, Z),
readCoordinate(values, M), index, total);
}
private static double readCoordinate(JSONValue[] values, int index) {
if (index >= values.length) {
return Double.NaN;
}
JSONValue v = values[index];
if (!(v instanceof JSONNumber)) {
throw new IllegalArgumentException();
}
return ((JSONNumber) v).getBigDecimal().doubleValue();
}
private GeoJsonUtils() {
}
}