未验证 提交 6d3ac410 编写于 作者: S Serge Rider 提交者: GitHub

Merge pull request #7368 from stefanuhrig/hana_spatial

HANA spatial types support
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2019 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Stefan Uhrig - initial implementation
*/
package org.jkiss.dbeaver.ui.gis.panel;
import java.io.IOException;
import org.jkiss.dbeaver.model.data.DBDContent;
import org.jkiss.dbeaver.model.data.DBDDisplayFormat;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
public class DBDContentAdapter extends TypeAdapter<DBDContent> {
@Override
public DBDContent read(JsonReader reader) throws IOException {
throw new IOException("Reading is not supported");
}
@Override
public void write(JsonWriter writer, DBDContent content) throws IOException {
if (content == null) {
writer.nullValue();
return;
}
String value = content.getDisplayString(DBDDisplayFormat.UI);
writer.value(value);
}
}
......@@ -37,6 +37,7 @@ import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
import org.jkiss.dbeaver.model.data.DBDContent;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.gis.*;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
......@@ -80,7 +81,8 @@ public class GISLeafletViewer implements IGeometryValueEditor {
private static final String PROP_FLIP_COORDINATES = "gis.flipCoords";
private static final String PROP_SRID = "gis.srid";
private static final Gson gson = new GsonBuilder().create();
private static final Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(DBDContent.class, new DBDContentAdapter()).create();
private final IValueController valueController;
private final Browser browser;
......@@ -239,6 +241,17 @@ public class GISLeafletViewer implements IGeometryValueEditor {
scriptFile = File.createTempFile("view", "gis.html", tempDir);
}
int attributeSrid = GisConstants.SRID_SIMPLE;
if (valueController != null && valueController.getValueType() instanceof GisAttribute) {
try {
attributeSrid = ((GisAttribute) valueController.getValueType())
.getAttributeGeometrySRID(new VoidProgressMonitor());
} catch (DBCException e) {
log.error(e);
}
}
List<String> geomValues = new ArrayList<>();
List<String> geomTipValues = new ArrayList<>();
boolean showMap = false;
......@@ -256,6 +269,9 @@ public class GISLeafletViewer implements IGeometryValueEditor {
}
Object targetValue = value.getRawValue();
int srid = sourceSRID == 0 ? value.getSRID() : sourceSRID;
if (srid == GisConstants.SRID_SIMPLE) {
srid = attributeSrid;
}
if (srid == GisConstants.SRID_SIMPLE) {
showMap = false;
actualSourceSRID = srid;
......@@ -292,15 +308,6 @@ public class GISLeafletViewer implements IGeometryValueEditor {
geomTipValues.add(gson.toJson(value.getProperties()));
}
}
if (actualSourceSRID == GisConstants.SRID_SIMPLE) {
if (valueController != null && valueController.getValueType() instanceof GisAttribute) {
try {
actualSourceSRID = ((GisAttribute) valueController.getValueType()).getAttributeGeometrySRID(new VoidProgressMonitor());
} catch (DBCException e) {
log.error(e);
}
}
}
this.defaultSRID = actualSourceSRID;
String geomValuesString = String.join(",", geomValues);
String geomTipValuesString = String.join(",", geomTipValues);
......
......@@ -2,10 +2,12 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %Bundle-Name
Bundle-SymbolicName: org.jkiss.dbeaver.ext.hana;singleton:=true
Bundle-Version: 1.1.5.qualifier
Bundle-Version: 1.2.0.qualifier
Bundle-Release-Date: 20191202
Require-Bundle: org.jkiss.dbeaver.model,
org.jkiss.dbeaver.ext.generic
org.jkiss.dbeaver.ext.generic,
org.jkiss.dbeaver.data.gis;visibility:=reexport,
org.jkiss.bundle.gis;visibility:=reexport
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-Vendor: %Bundle-Vendor
......
......@@ -118,4 +118,18 @@
<manager class="org.jkiss.dbeaver.ext.hana.edit.HANATableColumnManager" objectType="org.jkiss.dbeaver.ext.hana.model.HANATableColumn"/>
</extension>
<extension point="org.jkiss.dbeaver.dataTypeProvider">
<provider
class="org.jkiss.dbeaver.ext.hana.model.data.HANAValueHandlerProvider"
description="%provider.data.type.hana.description"
id="org.jkiss.dbeaver.ext.hana.model.data.HANAValueHandlerProvider"
label="%provider.data.type.hana.name">
<datasource id="hana"/>
<type name="ST_Geometry"/>
<type name="ST_Point"/>
</provider>
</extension>
</plugin>
......@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>org.jkiss.dbeaver.ext.hana</artifactId>
<version>1.1.5-SNAPSHOT</version>
<version>1.2.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
......@@ -51,6 +51,10 @@ public class HANADataSource extends GenericDataSource implements DBCQueryPlanner
private static final Log log = Log.getLog(HANADataSource.class);
private static final String PROP_APPLICATION_NAME = "SESSIONVARIABLE:APPLICATION";
private static final String PROP_READONLY = "READONLY";
private static final String PROP_SPATIAL_OUTPUT_REPRESENTATION = "SESSIONVARIABLE:SPATIAL_OUTPUT_REPRESENTATION";
private static final String VALUE_SPATIAL_OUTPUT_REPRESENTATION = "EWKB";
private static final String PROP_SPATIAL_WKB_EMPTY_POINT_REPRESENTATION = "SESSIONVARIABLE:SPATIAL_WKB_EMPTY_POINT_REPRESENTATION";
private static final String VALUE_SPATIAL_WKB_EMPTY_POINT_REPRESENTATION = "NAN_COORDINATES";
private HashMap<String, String> sysViewColumnUnits;
......@@ -112,6 +116,10 @@ public class HANADataSource extends GenericDataSource implements DBCQueryPlanner
if (getContainer().isConnectionReadOnly()) {
props.put(PROP_READONLY, "TRUE");
}
// Represent geometries as EWKB (instead of as WKB) so that we can extract the SRID
props.put(PROP_SPATIAL_OUTPUT_REPRESENTATION, VALUE_SPATIAL_OUTPUT_REPRESENTATION);
// Represent empty points using NaN-coordinates
props.put(PROP_SPATIAL_WKB_EMPTY_POINT_REPRESENTATION, VALUE_SPATIAL_WKB_EMPTY_POINT_REPRESENTATION);
return props;
}
......
......@@ -16,13 +16,28 @@
*/
package org.jkiss.dbeaver.ext.hana.model;
import java.sql.SQLException;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.ext.generic.model.GenericTable;
import org.jkiss.dbeaver.ext.generic.model.GenericTableBase;
import org.jkiss.dbeaver.ext.generic.model.GenericTableColumn;
import org.jkiss.dbeaver.model.DBPNamedObject2;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCPreparedStatement;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession;
import org.jkiss.dbeaver.model.gis.GisAttribute;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
public class HANATableColumn extends GenericTableColumn implements DBPNamedObject2, GisAttribute {
private static final int FLAT_EARTH_SRID_START = 1000000000;
public class HANATableColumn extends GenericTableColumn implements DBPNamedObject2 {
private static final int FLAT_EARTH_SRID_END = 2000000000;
private GeometryInfo geometryInfo;
public HANATableColumn(GenericTable table) {
super(table);
......@@ -32,5 +47,54 @@ public class HANATableColumn extends GenericTableColumn implements DBPNamedObjec
super(table, columnName, typeName, valueType, sourceType, ordinalPosition, columnSize, charLength, scale, precision, radix, notNull, remarks, defaultValue, autoIncrement, autoGenerated);
}
private static class GeometryInfo {
private String type;
private int srid = -1;
}
@Override
public int getAttributeGeometrySRID(DBRProgressMonitor monitor) throws DBCException {
readGeometryInfo(monitor);
return geometryInfo.srid;
}
@Override
public String getAttributeGeometryType(DBRProgressMonitor monitor) throws DBCException {
readGeometryInfo(monitor);
return geometryInfo.type;
}
private void readGeometryInfo(DBRProgressMonitor monitor) throws DBCException {
if (geometryInfo != null) {
return;
}
GeometryInfo gi = new GeometryInfo();
try (JDBCSession session = DBUtils.openMetaSession(monitor, this, "Load table inheritance info")) {
try (JDBCPreparedStatement dbStat = session
.prepareStatement("SELECT SRS_ID, DATA_TYPE_NAME FROM PUBLIC.ST_GEOMETRY_COLUMNS "
+ "WHERE SCHEMA_NAME=? AND TABLE_NAME=? AND COLUMN_NAME=?")) {
dbStat.setString(1, getTable().getSchema().getName());
dbStat.setString(2, getTable().getName());
dbStat.setString(3, getName());
try (JDBCResultSet dbResult = dbStat.executeQuery()) {
if (dbResult.next()) {
gi.srid = dbResult.getInt(1);
// HANA does not distinguish between geometry and geography type. Instead, HANA has additional
// SRS's for round-earth spatial reference systems with an offset of 1,000,000,000 that emulate
// a flat earth. If the SRID is in such a range, we have to substract the SRID to find the
// actual SRID that works with leaflet.
if ((FLAT_EARTH_SRID_START <= gi.srid) && (gi.srid < FLAT_EARTH_SRID_END)) {
gi.srid -= FLAT_EARTH_SRID_START;
}
gi.type = dbResult.getString(2);
}
}
}
} catch (SQLException e) {
throw new DBCException("Error reading geometry info", e);
}
geometryInfo = gi;
}
}
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2019 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Stefan Uhrig - initial implementation
*/
package org.jkiss.dbeaver.ext.hana.model.data;
import java.sql.SQLException;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.ext.hana.model.data.wkb.HANAWKBParser;
import org.jkiss.dbeaver.ext.hana.model.data.wkb.HANAWKBParserException;
import org.jkiss.dbeaver.ext.hana.model.data.wkb.HANAWKBWriter;
import org.jkiss.dbeaver.ext.hana.model.data.wkb.HANAWKBWriterException;
import org.jkiss.dbeaver.model.data.DBDDisplayFormat;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.exec.DBCSession;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCPreparedStatement;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession;
import org.jkiss.dbeaver.model.gis.DBGeometry;
import org.jkiss.dbeaver.model.gis.GisAttribute;
import org.jkiss.dbeaver.model.impl.jdbc.data.handlers.JDBCAbstractValueHandler;
import org.jkiss.dbeaver.model.struct.DBSTypedObject;
import org.locationtech.jts.geom.Geometry;
public class HANAGeometryValueHandler extends JDBCAbstractValueHandler {
public static final HANAGeometryValueHandler INSTANCE = new HANAGeometryValueHandler();
@Override
protected Object fetchColumnValue(DBCSession session, JDBCResultSet resultSet, DBSTypedObject type, int index)
throws DBCException, SQLException {
byte[] wkb = resultSet.getBytes(index);
if (wkb == null) {
return null;
}
HANAWKBParser parser = new HANAWKBParser();
try {
Geometry g = parser.parse(wkb);
return new DBGeometry(g);
} catch (HANAWKBParserException e) {
throw new DBCException(e, session.getDataSource());
}
}
@Override
protected void bindParameter(JDBCSession session, JDBCPreparedStatement statement, DBSTypedObject paramType,
int paramIndex, Object value) throws DBCException, SQLException {
Object geometry = value;
int srid = 0;
if (geometry instanceof DBGeometry) {
srid = ((DBGeometry) geometry).getSRID();
geometry = ((DBGeometry) geometry).getRawValue();
}
if (srid == 0 && paramType instanceof GisAttribute) {
srid = ((GisAttribute) paramType).getAttributeGeometrySRID(session.getProgressMonitor());
}
if (geometry == null) {
statement.setNull(paramIndex, paramType.getTypeID());
} else if (geometry instanceof Geometry) {
Geometry g = (Geometry) geometry;
if (g.getSRID() == 0) {
g.setSRID(srid);
}
try {
statement.setBytes(paramIndex, HANAWKBWriter.write(g, HANAXyzmModeFinder.findXyzmMode(g)));
} catch (HANAWKBWriterException e) {
throw new DBCException(e, session.getDataSource());
}
} else {
throw new DBCException("Could not bind the value because the value type is not a known geometry type");
}
}
@NotNull
@Override
public Class<?> getValueObjectType(@NotNull DBSTypedObject attribute) {
return DBGeometry.class;
}
@Override
public Object getValueFromObject(@NotNull DBCSession session, @NotNull DBSTypedObject type, Object object,
boolean copy) throws DBCException {
if (object == null) {
return new DBGeometry();
} else if (object instanceof DBGeometry) {
if (copy) {
return ((DBGeometry) object).copy();
} else {
return object;
}
} else if (object instanceof Geometry) {
return new DBGeometry((Geometry) object);
} else if (object instanceof byte[]) {
byte[] wkb = (byte[]) object;
HANAWKBParser parser = new HANAWKBParser();
try {
Geometry g = parser.parse(wkb);
return new DBGeometry(g);
} catch (HANAWKBParserException e) {
throw new DBCException(e, session.getDataSource());
}
} else {
throw new DBCException(
"Could not get geometry value from object because the object type is not a known geometry type");
}
}
@NotNull
@Override
public String getValueDisplayString(@NotNull DBSTypedObject column, Object value,
@NotNull DBDDisplayFormat format) {
if (value instanceof DBGeometry && format == DBDDisplayFormat.NATIVE) {
return "'" + value.toString() + "'";
}
return super.getValueDisplayString(column, value, format);
}
}
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2019 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Stefan Uhrig - initial implementation
*/
package org.jkiss.dbeaver.ext.hana.model.data;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.data.DBDPreferences;
import org.jkiss.dbeaver.model.data.DBDValueHandler;
import org.jkiss.dbeaver.model.data.DBDValueHandlerProvider;
import org.jkiss.dbeaver.model.struct.DBSTypedObject;
public class HANAValueHandlerProvider implements DBDValueHandlerProvider {
@Nullable
@Override
public DBDValueHandler getValueHandler(DBPDataSource dataSource, DBDPreferences preferences,
DBSTypedObject typedObject) {
switch (typedObject.getTypeName()) {
case "ST_GEOMETRY":
case "ST_POINT":
return HANAGeometryValueHandler.INSTANCE;
default:
return null;
}
}
}
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2019 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Stefan Uhrig - initial implementation
*/
package org.jkiss.dbeaver.ext.hana.model.data;
import org.jkiss.dbeaver.ext.hana.model.data.wkb.XyzmMode;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
/**
* Find the XYZM mode of a geometry instance.
*
* JTS coordinate sequences are often three-dimensional, but their coordinates
* don't have a third ordinate (i.e. it is NaN). This class checks the
* coordinate sequences and checks if all points have a z-coordinate and if any
* point has a m-coordinate to find the proper XYZM mode.
*/
public class HANAXyzmModeFinder {
private boolean allHaveZ = true;
private boolean someHaveM = false;
/**
* Finds the XYZM mode of a geometry.
*
* @param g
* A geometry. Must not be null.
* @return Returns the appropriate XYZM mode.
*/
public static XyzmMode findXyzmMode(Geometry g) {
HANAXyzmModeFinder instance = new HANAXyzmModeFinder();
return instance.inspect(g);
}
private HANAXyzmModeFinder() {
}
private XyzmMode inspect(Geometry g) {
inspectGeometry(g);
if (allHaveZ && someHaveM) {
return XyzmMode.XYZM;
} else if (allHaveZ) {
return XyzmMode.XYZ;
} else if (someHaveM) {
return XyzmMode.XYM;
} else {
return XyzmMode.XY;
}
}
private void inspectGeometry(Geometry g) {
if (g instanceof Point) {
inspectPoint((Point) g);
} else if (g instanceof LineString) {
inspectLineString((LineString) g);
} else if (g instanceof Polygon) {
inspectPolygon((Polygon) g);
} else if (g instanceof GeometryCollection) {
inspectCollection((GeometryCollection) g);
}
throw new AssertionError();
}
private void inspectPoint(Point p) {
inspectSequence(p.getCoordinateSequence());
}
private void inspectLineString(LineString ls) {
inspectSequence(ls.getCoordinateSequence());
}
private void inspectPolygon(Polygon pg) {
inspectLineString(pg.getExteriorRing());
int numHoles = pg.getNumInteriorRing();
for (int i = 0; i < numHoles; ++i) {
inspectLineString(pg.getInteriorRingN(i));
}
}
private void inspectCollection(GeometryCollection gc) {
int numGeometries = gc.getNumGeometries();
for (int i = 0; i < numGeometries; ++i) {
inspectGeometry(gc.getGeometryN(i));
}
}
private void inspectSequence(CoordinateSequence cs) {
int size = cs.size();
if (size == 0) {
if (!cs.hasZ()) {
allHaveZ = false;
}
if (cs.hasM()) {
someHaveM = true;
}
}
for (int i = 0; i < size; ++i) {
if (!Double.isFinite(cs.getZ(i))) {
allHaveZ = false;
}
if (Double.isFinite(cs.getM(i))) {
someHaveM = true;
}
}
}
}
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2019 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Stefan Uhrig - initial implementation
*/
package org.jkiss.dbeaver.ext.hana.model.data.wkb;
/**
* The geometry types supported by HANA.
*/
public enum GeometryType {
POINT(1), LINESTRING(2), POLYGON(3), MULTIPOINT(4), MULTILINESTRING(5), MULTIPOLYGON(6), GEOMETRYCOLLECTION(
7), CIRCULARSTRING(8);
private int typeCode;
GeometryType(int typeCode) {
this.typeCode = typeCode;
}
public int getTypeCode() {
return typeCode;
}
public static GeometryType getFromCode(int code) {
for (GeometryType type : GeometryType.values()) {
if (type.typeCode == code) {
return type;
}
}
return null;
}
}
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2019 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Stefan Uhrig - initial implementation
*/
package org.jkiss.dbeaver.ext.hana.model.data.wkb;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.text.MessageFormat;
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;
/**
* A parser for the well-known-binary created by HANA.
*
* The JTS parser cannot be used to parse 3- or 4-dimensional geometries because
* of different type code conventions. HANA offsets type codes by multiples of
* 1000 (as described in the relevant OGC and SQL/MM standard) while JTS expects
* specific bits to be set.
*/
public class HANAWKBParser {
private static final byte XDR = 0;
private static final byte NDR = 1;
private static final int TYPE_MASK = 0xFFFFF;
private static final int XYZM_MODE_DIV = 1000;
private static final int XYZM_MODE_XY = 0;
private static final int XYZM_MODE_XYZ = 1;
private static final int XYZM_MODE_XYM = 2;
private static final int XYZM_MODE_XYZM = 3;
private static final int EWKB_FLAG = 0x20000000;
private GeometryFactory factory;
private ByteBuffer data;
private XyzmMode xyzmMode;
private int dimension;
public Geometry parse(byte[] wkb) throws HANAWKBParserException {
data = ByteBuffer.wrap(wkb);
try {
readAndSetByteOrder();
int typeCode = data.getInt();
boolean isEwkb = (typeCode & EWKB_FLAG) != 0;
if (isEwkb) {
typeCode -= EWKB_FLAG;
}
GeometryType type = getGeometryType(typeCode);
xyzmMode = getXyzmMode(typeCode);
dimension = xyzmMode.getCoordinatesPerPoint();
int srid = isEwkb ? data.getInt() : 0;
factory = new GeometryFactory(new PrecisionModel(), srid);
Geometry geometry = parseGeometryOfType(type);
if (data.hasRemaining()) {
throw new HANAWKBParserException("There is unparsed WKB data left");
}
return geometry;
} catch (BufferUnderflowException e) {
throw new HANAWKBParserException("WKB is too short", e);
}
}
private Geometry parseGeometryOfType(GeometryType type) throws HANAWKBParserException {
switch (type) {
case POINT:
return parsePoint();
case LINESTRING:
return parseLineString();
case POLYGON:
return parsePolygon();
case MULTIPOINT:
return parseMultiPoint();
case MULTILINESTRING:
return parseMultiLineString();
case MULTIPOLYGON:
return parseMultiPolygon();
case GEOMETRYCOLLECTION:
return parseGeometryCollection();
case CIRCULARSTRING:
throw new HANAWKBParserException("Circular strings are not supported by JTS");
default:
throw new AssertionError();
}
}
private Point parsePoint() {
double x = data.getDouble();
double y = data.getDouble();
double z = Double.NaN;
double m = Double.NaN;
if (xyzmMode.hasZ()) {
z = data.getDouble();
}
if (xyzmMode.hasM()) {
m = data.getDouble();
}
CoordinateSequenceFactory csf = factory.getCoordinateSequenceFactory();
if (Double.isNaN(x)) {
CoordinateSequence cs = csf.create(0, dimension, xyzmMode.hasM() ? 1 : 0);
return factory.createPoint(cs);
}
CoordinateSequence cs = csf.create(1, dimension, xyzmMode.hasM() ? 1 : 0);
cs.getCoordinate(0).setX(x);
cs.getCoordinate(0).setY(y);
if (xyzmMode.hasZ()) {
cs.getCoordinate(0).setZ(z);
}
if (xyzmMode.hasM()) {
cs.getCoordinate(0).setM(m);
}
return factory.createPoint(cs);
}
private LineString parseLineString() {
CoordinateSequence cs = readCoordinateSequence();
return factory.createLineString(cs);
}
private Polygon parsePolygon() {
int numRings = data.getInt();
if (numRings == 0) {
return factory.createPolygon((LinearRing) null);
}
LinearRing shell = parseLinearRing();
if (numRings == 1) {
return factory.createPolygon(shell);
}
LinearRing[] holes = new LinearRing[numRings - 1];
for (int i = 1; i < numRings; ++i) {
holes[i - 1] = parseLinearRing();
}
return factory.createPolygon(shell, holes);
}
private MultiPoint parseMultiPoint() {
CoordinateSequence cs = readCoordinateSequence();
return factory.createMultiPoint(cs);
}
private MultiLineString parseMultiLineString() throws HANAWKBParserException {
int numLineStrings = data.getInt();
LineString[] lineStrings = new LineString[numLineStrings];
for (int i = 0; i < numLineStrings; ++i) {
lineStrings[i] = (LineString) parseSubGeometry();
}
return factory.createMultiLineString(lineStrings);
}
private MultiPolygon parseMultiPolygon() throws HANAWKBParserException {
int numPolygons = data.getInt();
Polygon[] polygons = new Polygon[numPolygons];
for (int i = 0; i < numPolygons; ++i) {
polygons[i] = (Polygon) parseSubGeometry();
}
return factory.createMultiPolygon(polygons);
}
private GeometryCollection parseGeometryCollection() throws HANAWKBParserException {
int numGeometries = data.getInt();
Geometry[] geometries = new Geometry[numGeometries];
for (int i = 0; i < numGeometries; ++i) {
geometries[i] = parseSubGeometry();
}
return factory.createGeometryCollection(geometries);
}
private Geometry parseSubGeometry() throws HANAWKBParserException {
readAndSetByteOrder();
int typeCode = data.getInt();
GeometryType type = getGeometryType(typeCode);
return parseGeometryOfType(type);
}
private LinearRing parseLinearRing() {
CoordinateSequence cs = readCoordinateSequence();
return factory.createLinearRing(cs);
}
private CoordinateSequence readCoordinateSequence() {
CoordinateSequenceFactory csf = factory.getCoordinateSequenceFactory();
int numPoints = data.getInt();
CoordinateSequence cs = csf.create(numPoints, dimension, xyzmMode.hasM() ? 1 : 0);
switch (xyzmMode) {
case XY:
for (int i = 0; i < numPoints; ++i) {
cs.getCoordinate(i).setX(data.getDouble());
cs.getCoordinate(i).setY(data.getDouble());
}
break;
case XYZ:
for (int i = 0; i < numPoints; ++i) {
cs.getCoordinate(i).setX(data.getDouble());
cs.getCoordinate(i).setY(data.getDouble());
cs.getCoordinate(i).setZ(data.getDouble());
}
break;
case XYM:
for (int i = 0; i < numPoints; ++i) {
cs.getCoordinate(i).setX(data.getDouble());
cs.getCoordinate(i).setY(data.getDouble());
cs.getCoordinate(i).setM(data.getDouble());
}
break;
case XYZM:
for (int i = 0; i < numPoints; ++i) {
cs.getCoordinate(i).setX(data.getDouble());
cs.getCoordinate(i).setY(data.getDouble());
cs.getCoordinate(i).setZ(data.getDouble());
cs.getCoordinate(i).setM(data.getDouble());
}
break;
default:
throw new AssertionError();
}
return cs;
}
private GeometryType getGeometryType(int typeCode) throws HANAWKBParserException {
int wkbType = typeCode & TYPE_MASK;
wkbType = wkbType % XYZM_MODE_DIV;
GeometryType type = GeometryType.getFromCode(wkbType);
if (type == null) {
throw new HANAWKBParserException(MessageFormat.format("Unknown WKB type {0}", wkbType));
}
return type;
}
private XyzmMode getXyzmMode(int typeCode) throws HANAWKBParserException {
int wkbType = typeCode & TYPE_MASK;
int xyzmFlag = wkbType / XYZM_MODE_DIV;
switch (xyzmFlag) {
case XYZM_MODE_XY:
return XyzmMode.XY;
case XYZM_MODE_XYZ:
return XyzmMode.XYZ;
case XYZM_MODE_XYM:
return XyzmMode.XYM;
case XYZM_MODE_XYZM:
return XyzmMode.XYZM;
default:
throw new HANAWKBParserException(MessageFormat.format("Invalid XYZM-mode {0}", xyzmFlag));
}
}
private void readAndSetByteOrder() throws HANAWKBParserException {
byte order = data.get();
switch (order) {
case XDR:
data.order(ByteOrder.BIG_ENDIAN);
break;
case NDR:
data.order(ByteOrder.LITTLE_ENDIAN);
break;
default:
throw new HANAWKBParserException(MessageFormat.format("Invalid BOM value {0}", order));
}
}
}
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2019 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Stefan Uhrig - initial implementation
*/
package org.jkiss.dbeaver.ext.hana.model.data.wkb;
/**
* Exception thrown if WKB parsing fails.
*/
public class HANAWKBParserException extends Exception {
private static final long serialVersionUID = 1L;
public HANAWKBParserException(String message) {
super(message);
}
public HANAWKBParserException(Throwable cause) {
super(cause);
}
public HANAWKBParserException(String message, Throwable cause) {
super(message, cause);
}
}
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2019 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Stefan Uhrig - initial implementation
*/
package org.jkiss.dbeaver.ext.hana.model.data.wkb;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.text.MessageFormat;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
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;
/**
* A well-known binary writer.
*
* The JTS WKB writer cannot be used as it rejects empty points.
*/
public class HANAWKBWriter {
private static final int HEADER_SIZE = 5;
private static final int COUNT_SIZE = 4;
private static final int COORD_SIZE = 8;
private static final byte NDR = 1;
private static final int Z_OFFSET = 1000;
private static final int M_OFFSET = 2000;
public static byte[] write(Geometry geometry, XyzmMode xyzmMode) throws HANAWKBWriterException {
if (geometry == null) {
return null;
}
int size = computeGeometrySize(geometry, xyzmMode);
ByteBuffer buffer = ByteBuffer.allocate(size);
buffer.order(ByteOrder.LITTLE_ENDIAN);
writeGeometry(geometry, xyzmMode, buffer);
return buffer.array();
}
private static int computeGeometrySize(Geometry geometry, XyzmMode xyzmMode) throws HANAWKBWriterException {
if (geometry instanceof Point) {
return computePointSize(xyzmMode);
} else if (geometry instanceof LineString) {
return computeLineStringSize((LineString) geometry, xyzmMode);
} else if (geometry instanceof Polygon) {
return computePolygonSize((Polygon) geometry, xyzmMode);
} else if (geometry instanceof MultiPoint) {
return computeMultiPointSize((MultiPoint) geometry, xyzmMode);
} else if (geometry instanceof MultiLineString) {
return computeMultiLineStringSize((MultiLineString) geometry, xyzmMode);
} else if (geometry instanceof MultiPolygon) {
return computeMultiPolygonSize((MultiPolygon) geometry, xyzmMode);
} else if (geometry instanceof GeometryCollection) {
return computeGeometryCollectionSize((GeometryCollection) geometry, xyzmMode);
} else {
throw new HANAWKBWriterException(
MessageFormat.format("Unsupported geometry type {0}", geometry.getGeometryType()));
}
}
private static int computePointSize(XyzmMode xyzmMode) {
return HEADER_SIZE + xyzmMode.getCoordinatesPerPoint() * COORD_SIZE;
}
private static int computeLineStringSize(LineString lineString, XyzmMode xyzmMode) {
return HEADER_SIZE + COUNT_SIZE + lineString.getNumPoints() * xyzmMode.getCoordinatesPerPoint() * COORD_SIZE;
}
private static int computePolygonSize(Polygon polygon, XyzmMode xyzmMode) {
int size = HEADER_SIZE + COUNT_SIZE;
LineString shell = polygon.getExteriorRing();
if ((shell == null) || (shell.getNumPoints() == 0)) {
return size;
}
int pointSize = xyzmMode.getCoordinatesPerPoint() * COORD_SIZE;
size += COUNT_SIZE + shell.getNumPoints() * pointSize;
int numHoles = polygon.getNumInteriorRing();
for (int i = 0; i < numHoles; ++i) {
size += COUNT_SIZE + polygon.getInteriorRingN(i).getNumPoints() * pointSize;
}
return size;
}
private static int computeMultiPointSize(MultiPoint multiPoint, XyzmMode xyzmMode) {
return HEADER_SIZE + COUNT_SIZE + multiPoint.getNumPoints() * xyzmMode.getCoordinatesPerPoint() * COORD_SIZE;
}
private static int computeMultiLineStringSize(MultiLineString multiLineString, XyzmMode xyzmMode) {
int size = HEADER_SIZE + COUNT_SIZE;
for (int i = 0; i < multiLineString.getNumGeometries(); ++i) {
size += computeLineStringSize((LineString) multiLineString.getGeometryN(i), xyzmMode);
}
return size;
}
private static int computeMultiPolygonSize(MultiPolygon multiPolygon, XyzmMode xyzmMode) {
int size = HEADER_SIZE + COUNT_SIZE;
for (int i = 0; i < multiPolygon.getNumGeometries(); ++i) {
size += computePolygonSize((Polygon) multiPolygon.getGeometryN(i), xyzmMode);
}
return size;
}
private static int computeGeometryCollectionSize(GeometryCollection geometryCollection, XyzmMode xyzmMode)
throws HANAWKBWriterException {
int size = HEADER_SIZE + COUNT_SIZE;
for (int i = 0; i < geometryCollection.getNumGeometries(); ++i) {
size += computeGeometrySize(geometryCollection.getGeometryN(i), xyzmMode);
}
return size;
}
private static void writeGeometry(Geometry geometry, XyzmMode xyzmMode, ByteBuffer buffer)
throws HANAWKBWriterException {
if (geometry instanceof Point) {
writePoint((Point) geometry, xyzmMode, buffer);
} else if (geometry instanceof LineString) {
writeLineString((LineString) geometry, xyzmMode, buffer);
} else if (geometry instanceof Polygon) {
writePolygon((Polygon) geometry, xyzmMode, buffer);
} else if (geometry instanceof MultiPoint) {
writeMultiPoint((MultiPoint) geometry, xyzmMode, buffer);
} else if (geometry instanceof MultiLineString) {
writeMultiLineString((MultiLineString) geometry, xyzmMode, buffer);
} else if (geometry instanceof MultiPolygon) {
writeMultiPolygon((MultiPolygon) geometry, xyzmMode, buffer);
} else if (geometry instanceof GeometryCollection) {
writeGeometryCollection((GeometryCollection) geometry, xyzmMode, buffer);
} else {
throw new HANAWKBWriterException(
MessageFormat.format("Unsupported geometry type {0}", geometry.getGeometryType()));
}
}
private static void writePoint(Point point, XyzmMode xyzmMode, ByteBuffer buffer) {
writeHeader(GeometryType.POINT, xyzmMode, buffer);
CoordinateSequence cs = point.getCoordinateSequence();
if (cs.size() == 0) {
for (int i = 0; i < xyzmMode.getCoordinatesPerPoint(); ++i) {
buffer.putDouble(Double.NaN);
}
} else {
buffer.putDouble(cs.getX(0));
buffer.putDouble(cs.getY(0));
if (xyzmMode.hasZ()) {
buffer.putDouble(cs.getZ(0));
}
if (xyzmMode.hasM()) {
buffer.putDouble(cs.getM(0));
}
}
}
private static void writeLineString(LineString lineString, XyzmMode xyzmMode, ByteBuffer buffer) {
writeHeader(GeometryType.LINESTRING, xyzmMode, buffer);
writeCoordinateSequence(lineString.getCoordinateSequence(), xyzmMode, buffer);
}
private static void writePolygon(Polygon polygon, XyzmMode xyzmMode, ByteBuffer buffer) {
writeHeader(GeometryType.POLYGON, xyzmMode, buffer);
LineString shell = polygon.getExteriorRing();
if ((shell == null) || (shell.getNumPoints() == 0)) {
buffer.putInt(0);
return;
}
int numHoles = polygon.getNumInteriorRing();
buffer.putInt(1 + numHoles);
writeCoordinateSequence(shell.getCoordinateSequence(), xyzmMode, buffer);
for (int i = 0; i < numHoles; ++i) {
LineString hole = polygon.getInteriorRingN(0);
writeCoordinateSequence(hole.getCoordinateSequence(), xyzmMode, buffer);
}
}
private static void writeMultiPoint(MultiPoint multiPoint, XyzmMode xyzmMode, ByteBuffer buffer) {
writeHeader(GeometryType.MULTIPOINT, xyzmMode, buffer);
int numPoints = multiPoint.getNumPoints();
buffer.putInt(numPoints);
for (int i = 0; i < numPoints; ++i) {
writePoint((Point) multiPoint.getGeometryN(i), xyzmMode, buffer);
}
}
private static void writeMultiLineString(MultiLineString multiLineString, XyzmMode xyzmMode, ByteBuffer buffer) {
writeHeader(GeometryType.MULTILINESTRING, xyzmMode, buffer);
int numLineStrings = multiLineString.getNumGeometries();
buffer.putInt(numLineStrings);
for (int i = 0; i < numLineStrings; ++i) {
writeLineString((LineString) multiLineString.getGeometryN(i), xyzmMode, buffer);
}
}
private static void writeMultiPolygon(MultiPolygon multiPolygon, XyzmMode xyzmMode, ByteBuffer buffer) {
writeHeader(GeometryType.MULTIPOLYGON, xyzmMode, buffer);
int numPolygons = multiPolygon.getNumGeometries();
buffer.putInt(numPolygons);
for (int i = 0; i < numPolygons; ++i) {
writePolygon((Polygon) multiPolygon.getGeometryN(i), xyzmMode, buffer);
}
}
private static void writeGeometryCollection(GeometryCollection geometryCollection, XyzmMode xyzmMode,
ByteBuffer buffer) throws HANAWKBWriterException {
writeHeader(GeometryType.GEOMETRYCOLLECTION, xyzmMode, buffer);
int numGeometries = geometryCollection.getNumGeometries();
buffer.putInt(numGeometries);
for (int i = 0; i < numGeometries; ++i) {
writeGeometry(geometryCollection.getGeometryN(i), xyzmMode, buffer);
}
}
private static void writeCoordinateSequence(CoordinateSequence cs, XyzmMode xyzmMode, ByteBuffer buffer) {
int numPoints = cs.size();
buffer.putInt(numPoints);
for (int i = 0; i < numPoints; ++i) {
buffer.putDouble(cs.getX(i));
buffer.putDouble(cs.getY(i));
if (xyzmMode.hasZ()) {
buffer.putDouble(cs.getZ(i));
}
if (xyzmMode.hasM()) {
buffer.putDouble(cs.getM(i));
}
}
}
private static void writeHeader(GeometryType geometryType, XyzmMode xyzmMode, ByteBuffer buffer) {
buffer.put(NDR);
int typeCode = geometryType.getTypeCode();
if (xyzmMode.hasZ()) {
typeCode += Z_OFFSET;
}
if (xyzmMode.hasM()) {
typeCode += M_OFFSET;
}
buffer.putInt(typeCode);
}
}
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2019 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Stefan Uhrig - initial implementation
*/
package org.jkiss.dbeaver.ext.hana.model.data.wkb;
/**
* Exception thrown if WKB writing fails.
*/
public class HANAWKBWriterException extends Exception {
private static final long serialVersionUID = 1L;
public HANAWKBWriterException(String message) {
super(message);
}
public HANAWKBWriterException(Throwable cause) {
super(cause);
}
public HANAWKBWriterException(String message, Throwable cause) {
super(message, cause);
}
}
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2019 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Stefan Uhrig - initial implementation
*/
package org.jkiss.dbeaver.ext.hana.model.data.wkb;
/**
* The dimension modes supported by HANA.
*/
public enum XyzmMode {
XY(2, false, false), XYZ(3, true, false), XYM(3, false, true), XYZM(4, true, true);
private int coordinatesPerPoint;
private boolean hasZ;
private boolean hasM;
XyzmMode(int coordinatesPerPoint, boolean hasZ, boolean hasM) {
this.coordinatesPerPoint = coordinatesPerPoint;
this.hasZ = hasZ;
this.hasM = hasM;
}
public int getCoordinatesPerPoint() {
return coordinatesPerPoint;
}
public boolean hasZ() {
return hasZ;
}
public boolean hasM() {
return hasM;
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册