diff --git a/h2/src/main/org/h2/api/SpatialDriver.java b/h2/src/main/org/h2/api/SpatialDriver.java new file mode 100644 index 000000000..f3cb6d24f --- /dev/null +++ b/h2/src/main/org/h2/api/SpatialDriver.java @@ -0,0 +1,21 @@ +/* + * Copyright 2004-2016 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.api; + +/** + * Interface which will be used by h2 to create the geometry factory. + * + * @author Steve Hruda + */ +public interface SpatialDriver { + + /** + * A new {@link ValueGeometryFactory} instance. + * + * @return a new {@link ValueGeometryFactory} instance + */ + public ValueGeometryFactory createGeometryFactory(); +} diff --git a/h2/src/main/org/h2/api/ValueGeometryFactory.java b/h2/src/main/org/h2/api/ValueGeometryFactory.java new file mode 100644 index 000000000..9cc3a282c --- /dev/null +++ b/h2/src/main/org/h2/api/ValueGeometryFactory.java @@ -0,0 +1,120 @@ +/* + * Copyright 2004-2015 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.api; + +import org.h2.message.DbException; +import org.h2.value.Value; +import org.h2.value.ValueGeometry; + +import com.vividsolutions.jts.geom.Geometry; + +/** + * Interface of a factory which provides methods for the conversion of a + * geometry object of a framework like JTS into a {@link ValueGeometry}. + * + * @author Steve Hruda + * @param the type of your {@link ValueGeometry} implementation + * @param the type of the frameworks geometry object + */ +public interface ValueGeometryFactory, S> { + + /** + * Get or create a geometry value for the given geometry. + * + * @param g + * the geometry object + * @return the value + */ + public T get(Object g); + + /** + * Get or create a geometry value for the given byte array. + * @param g + * the geometry object as a byte array + * @return the value + */ + public T get(byte[] g); + + /** + * Get or create a geometry value for the given {@link Value}. + * @param g + * the geometry as {@link Value} + * @return the value + */ + public T get(Value g); + + /** + * Returns the type of the used geometry framework. + * @return the type of the used geometry framework. + */ + public Class getGeometryType(); + + /** + * Returns true if the given object is an instance + * of the used geometry framework, false otherwise. + * @param g + * the geometry + * @return true if the given object is an instance + * of the used geometry framework, false otherwise. + */ + public boolean isGeometryTypeSupported(Object g); + + /** + * Creates a geometry instance by using the given byte array + * representation. + * + * @param bytes the byte array representation of the geometry + * @return a new geometry instance + * @throws DbException - if an exception occurs during the creation + */ + public S getGeometry(byte[] bytes) throws DbException; + + /** + * Creates a geometry instance by using the given string + * representation. + * + * @param s the string representation of the geometry + * @return a new geometry instance + * @throws DbException - if an exception occurs during the creation + */ + public S getGeometry(String s) throws DbException; + + /** + * Get or create a geometry value for the given geometry. + * + * @param s the WKT representation of the geometry + * @param srid the srid of the object + * @return the value + * @throws DbException - if an exception occurs during the creation + */ + public S getGeometry(String s, int srid) throws DbException; + + /** + * Get or create a geometry value for the given geometry. + * + * @param g + * the geometry object + * @return the value + */ + public T get(Geometry g); + + /** + * Get or create a geometry value for the given geometry. + * + * @param s the WKT representation of the geometry + * @return the value + */ + public T get(String s); + + /** + * Get or create a geometry value for the given geometry. + * + * @param s the WKT representation of the geometry + * @param srid the srid of the object + * @return the value + */ + public T get(String s, int srid); +} diff --git a/h2/src/main/org/h2/expression/Comparison.java b/h2/src/main/org/h2/expression/Comparison.java index 3caaa836f..12503a1c8 100644 --- a/h2/src/main/org/h2/expression/Comparison.java +++ b/h2/src/main/org/h2/expression/Comparison.java @@ -293,9 +293,7 @@ static boolean compareNotNull(Database database, Value l, Value r, result = database.compare(l, r) < 0; break; case SPATIAL_INTERSECTS: { - ValueGeometry lg = (ValueGeometry) l.convertTo(Value.GEOMETRY); - ValueGeometry rg = (ValueGeometry) r.convertTo(Value.GEOMETRY); - result = lg.intersectsBoundingBox(rg); + result = Value.getGeometryFactory().get(l).intersectsBoundingBox(Value.getGeometryFactory().get(r)); break; } default: diff --git a/h2/src/main/org/h2/index/IndexCursor.java b/h2/src/main/org/h2/index/IndexCursor.java index 32783bf48..0ddcd8847 100644 --- a/h2/src/main/org/h2/index/IndexCursor.java +++ b/h2/src/main/org/h2/index/IndexCursor.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.HashSet; + import org.h2.engine.Session; import org.h2.expression.Comparison; import org.h2.message.DbException; @@ -19,7 +20,6 @@ import org.h2.table.Table; import org.h2.table.TableFilter; import org.h2.value.Value; -import org.h2.value.ValueGeometry; import org.h2.value.ValueNull; /** @@ -185,10 +185,9 @@ private SearchRow getSpatialSearchRow(SearchRow row, int columnId, Value v) { // if an object needs to overlap with both a and b, // then it needs to overlap with the the union of a and b // (not the intersection) - ValueGeometry vg = (ValueGeometry) row.getValue(columnId). - convertTo(Value.GEOMETRY); - v = ((ValueGeometry) v.convertTo(Value.GEOMETRY)). - getEnvelopeUnion(vg); + + v= Value.getGeometryFactory().get(v).getEnvelopeUnion( + Value.getGeometryFactory().get(row.getValue(columnId))); } if (columnId < 0) { row.setKey(v.getLong()); diff --git a/h2/src/main/org/h2/index/SpatialTreeIndex.java b/h2/src/main/org/h2/index/SpatialTreeIndex.java index 00351f6a4..b72072f13 100644 --- a/h2/src/main/org/h2/index/SpatialTreeIndex.java +++ b/h2/src/main/org/h2/index/SpatialTreeIndex.java @@ -23,10 +23,6 @@ import org.h2.table.TableFilter; import org.h2.value.Value; import org.h2.value.ValueGeometry; -import org.h2.value.ValueNull; - -import com.vividsolutions.jts.geom.Envelope; -import com.vividsolutions.jts.geom.Geometry; /** * This is an index based on a MVR-TreeMap. @@ -126,22 +122,15 @@ public void add(Session session, Row row) { if (closed) { throw DbException.throwInternalError(); } - treeMap.add(getKey(row), row.getKey()); + treeMap.add(getEnvelope(row), row.getKey()); } - private SpatialKey getKey(SearchRow row) { - if (row == null) { + private SpatialKey getEnvelope(SearchRow row) { + if (row == null) { return null; } Value v = row.getValue(columnIds[0]); - if (v == ValueNull.INSTANCE) { - return null; - } - Geometry g = ((ValueGeometry) v.convertTo(Value.GEOMETRY)).getGeometryNoCopy(); - Envelope env = g.getEnvelopeInternal(); - return new SpatialKey(row.getKey(), - (float) env.getMinX(), (float) env.getMaxX(), - (float) env.getMinY(), (float) env.getMaxY()); + return ((ValueGeometry) v.convertTo(Value.GEOMETRY)).getSpatialKey(row.getKey()); } @Override @@ -149,7 +138,7 @@ public void remove(Session session, Row row) { if (closed) { throw DbException.throwInternalError(); } - if (!treeMap.remove(getKey(row), row.getKey())) { + if (!treeMap.remove(getEnvelope(row), row.getKey())) { throw DbException.throwInternalError("row not found"); } } @@ -174,7 +163,7 @@ public Cursor findByGeometry(TableFilter filter, SearchRow intersection) { return find(filter.getSession()); } return new SpatialCursor( - treeMap.findIntersectingKeys(getKey(intersection)), table, + treeMap.findIntersectingKeys(getEnvelope(intersection)), table, filter.getSession()); } diff --git a/h2/src/main/org/h2/mvstore/db/MVSpatialIndex.java b/h2/src/main/org/h2/mvstore/db/MVSpatialIndex.java index 7be701dd0..f6c8889f6 100644 --- a/h2/src/main/org/h2/mvstore/db/MVSpatialIndex.java +++ b/h2/src/main/org/h2/mvstore/db/MVSpatialIndex.java @@ -32,10 +32,6 @@ import org.h2.value.Value; import org.h2.value.ValueGeometry; import org.h2.value.ValueLong; -import org.h2.value.ValueNull; - -import com.vividsolutions.jts.geom.Envelope; -import com.vividsolutions.jts.geom.Geometry; /** * This is an index based on a MVRTreeMap. @@ -125,7 +121,7 @@ public void close(Session session) { @Override public void add(Session session, Row row) { TransactionMap map = getMap(session); - SpatialKey key = getKey(row); + SpatialKey key = getEnvelope(row); if (indexType.isUnique()) { // this will detect committed entries only RTreeCursor cursor = spatialMap.findContainedKeys(key); @@ -164,9 +160,17 @@ public void add(Session session, Row row) { } } + private SpatialKey getEnvelope(SearchRow row) { + if (row == null) { + return null; + } + Value v = row.getValue(columnIds[0]); + return ((ValueGeometry) v.convertTo(Value.GEOMETRY)).getSpatialKey(row.getKey()); + } + @Override public void remove(Session session, Row row) { - SpatialKey key = getKey(row); + SpatialKey key = getEnvelope(row); TransactionMap map = getMap(session); try { Value old = map.remove(key); @@ -204,26 +208,12 @@ public Cursor findByGeometry(TableFilter filter, SearchRow intersection) { return find(session); } Iterator cursor = - spatialMap.findIntersectingKeys(getKey(intersection)); + spatialMap.findIntersectingKeys(getEnvelope(intersection)); TransactionMap map = getMap(session); Iterator it = map.wrapIterator(cursor, false); return new MVStoreCursor(session, it); } - private SpatialKey getKey(SearchRow row) { - if (row == null) { - return null; - } - Value v = row.getValue(columnIds[0]); - if (v == ValueNull.INSTANCE) { - return null; - } - Geometry g = ((ValueGeometry) v.convertTo(Value.GEOMETRY)).getGeometryNoCopy(); - Envelope env = g.getEnvelopeInternal(); - return new SpatialKey(row.getKey(), - (float) env.getMinX(), (float) env.getMaxX(), - (float) env.getMinY(), (float) env.getMaxY()); - } /** * Get the row with the given index key. diff --git a/h2/src/main/org/h2/mvstore/db/ValueDataType.java b/h2/src/main/org/h2/mvstore/db/ValueDataType.java index 76bc4ce59..1be63d99c 100644 --- a/h2/src/main/org/h2/mvstore/db/ValueDataType.java +++ b/h2/src/main/org/h2/mvstore/db/ValueDataType.java @@ -576,7 +576,7 @@ private Object readValue(ByteBuffer buff) { int len = readVarInt(buff); byte[] b = DataUtils.newBytes(len); buff.get(b, 0, len); - return ValueGeometry.get(b); + return Value.getGeometryFactory().get(b); } case SPATIAL_KEY_2D: return getSpatialDataType().read(buff); diff --git a/h2/src/main/org/h2/store/Data.java b/h2/src/main/org/h2/store/Data.java index 38b869931..7ff9a8bf1 100644 --- a/h2/src/main/org/h2/store/Data.java +++ b/h2/src/main/org/h2/store/Data.java @@ -36,7 +36,6 @@ import org.h2.value.ValueDecimal; import org.h2.value.ValueDouble; import org.h2.value.ValueFloat; -import org.h2.value.ValueGeometry; import org.h2.value.ValueInt; import org.h2.value.ValueJavaObject; import org.h2.value.ValueLob; @@ -773,7 +772,7 @@ public Value readValue() { int len = readVarInt(); byte[] b = DataUtils.newBytes(len); read(b, 0, len); - return ValueGeometry.get(b); + return Value.getGeometryFactory().get(b); } case Value.JAVA_OBJECT: { int len = readVarInt(); diff --git a/h2/src/main/org/h2/value/DataType.java b/h2/src/main/org/h2/value/DataType.java index 1efa47a2f..a0580fccd 100644 --- a/h2/src/main/org/h2/value/DataType.java +++ b/h2/src/main/org/h2/value/DataType.java @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.UUID; - import org.h2.api.ErrorCode; import org.h2.engine.Constants; import org.h2.engine.SessionInterface; @@ -49,15 +48,6 @@ public class DataType { */ public static final int TYPE_RESULT_SET = -10; - /** - * The Geometry class. This object is null if the jts jar file is not in the - * classpath. - */ - public static final Class GEOMETRY_CLASS; - - private static final String GEOMETRY_CLASS_NAME = - "com.vividsolutions.jts.geom.Geometry"; - /** * The list of types. An ArrayList so that Tomcat doesn't set it to null * when clearing references. @@ -172,17 +162,6 @@ public class DataType { */ public int memory; - static { - Class g; - try { - g = JdbcUtils.loadUserClass(GEOMETRY_CLASS_NAME); - } catch (Exception e) { - // class is not in the classpath - ignore - g = null; - } - GEOMETRY_CLASS = g; - } - static { for (int i = 0; i < Value.TYPE_COUNT; i++) { TYPES_BY_VALUE_TYPE.add(null); @@ -657,10 +636,11 @@ public static Value readValue(SessionInterface session, ResultSet rs, } case Value.GEOMETRY: { Object x = rs.getObject(columnIndex); - if (x == null) { + if (x == null || !Value.getGeometryFactory().isGeometryTypeSupported(x)) { return ValueNull.INSTANCE; } - return ValueGeometry.getFromGeometry(x); + + return Value.getGeometryFactory().get(x); } default: throw DbException.throwInternalError("type="+type); @@ -740,7 +720,7 @@ public static String getTypeClassName(int type) { case Value.RESULT_SET: return ResultSet.class.getName(); case Value.GEOMETRY: - return GEOMETRY_CLASS_NAME; + return Value.getGeometryFactory().getGeometryType().getName(); default: throw DbException.throwInternalError("type="+type); } @@ -938,7 +918,7 @@ public static int getTypeFromClass(Class x) { } else if (Object[].class.isAssignableFrom(x)) { // this includes String[] and so on return Value.ARRAY; - } else if (isGeometryClass(x)) { + } else if (Value.getGeometryFactory().getGeometryType().isAssignableFrom(x)) { return Value.GEOMETRY; } else { return Value.JAVA_OBJECT; @@ -1034,39 +1014,13 @@ public static Value convertToValue(SessionInterface session, Object x, return ValueArray.get(x.getClass().getComponentType(), v); } else if (x instanceof Character) { return ValueStringFixed.get(((Character) x).toString()); - } else if (isGeometry(x)) { - return ValueGeometry.getFromGeometry(x); + } else if (Value.getGeometryFactory().isGeometryTypeSupported(x)) { + return Value.getGeometryFactory().get(x); } else { return ValueJavaObject.getNoCopy(x, null, session.getDataHandler()); } } - /** - * Check whether a given class matches the Geometry class. - * - * @param x the class - * @return true if it is a Geometry class - */ - public static boolean isGeometryClass(Class x) { - if (x == null || GEOMETRY_CLASS == null) { - return false; - } - return GEOMETRY_CLASS.isAssignableFrom(x); - } - - /** - * Check whether a given object is a Geometry object. - * - * @param x the the object - * @return true if it is a Geometry object - */ - public static boolean isGeometry(Object x) { - if (x == null) { - return false; - } - return isGeometryClass(x.getClass()); - } - /** * Get a data type object from a type name. * diff --git a/h2/src/main/org/h2/value/JTSSpatialDriver.java b/h2/src/main/org/h2/value/JTSSpatialDriver.java new file mode 100644 index 000000000..21920894e --- /dev/null +++ b/h2/src/main/org/h2/value/JTSSpatialDriver.java @@ -0,0 +1,25 @@ +/* + * Copyright 2004-2016 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.value; + +import org.h2.api.SpatialDriver; +import org.h2.api.ValueGeometryFactory; + +/** + * The {@link SpatialDriver} implementation for the JTS geometry framework. + * + * @author Steve Hruda, 2016 + */ +public class JTSSpatialDriver implements SpatialDriver{ + + /** + * @see org.h2.api.SpatialDriver#createGeometryFactory() + */ + @Override + public ValueGeometryFactory createGeometryFactory() { + return new JTSValueGeometryFactory(); + } +} \ No newline at end of file diff --git a/h2/src/main/org/h2/value/JTSValueGeometry.java b/h2/src/main/org/h2/value/JTSValueGeometry.java new file mode 100644 index 000000000..aaabe377e --- /dev/null +++ b/h2/src/main/org/h2/value/JTSValueGeometry.java @@ -0,0 +1,72 @@ +/* + * Copyright 2004-2014 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.value; + +import java.util.Arrays; + +import org.h2.mvstore.rtree.SpatialKey; + +import com.vividsolutions.jts.geom.Envelope; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.geom.GeometryFactory; +import com.vividsolutions.jts.io.WKTWriter; + +/** + * The {@link ValueGeometry} implementation for the JTS geometry framework. + * + * @author Steve Hruda + */ +public class JTSValueGeometry extends ValueGeometry { + + JTSValueGeometry(byte[] bytes, Geometry geometry) { + super(bytes, geometry); + } + + @Override + public Geometry getGeometry() { + return (Geometry) getGeometryNoCopy().clone(); + } + + @Override + public boolean _intersectsBoundingBox(ValueGeometry r) { + // the Geometry object caches the envelope + return getGeometryNoCopy().getEnvelopeInternal().intersects( + r.getGeometryNoCopy().getEnvelopeInternal()); + } + + @Override + public Value _getEnvelopeUnion(ValueGeometry r) { + GeometryFactory gf = new GeometryFactory(); + Envelope mergedEnvelope = new Envelope(getGeometryNoCopy().getEnvelopeInternal()); + mergedEnvelope.expandToInclude(r.getGeometryNoCopy().getEnvelopeInternal()); + return getGeometryFactory().get(gf.toGeometry(mergedEnvelope)); + } + + @Override + public boolean equals(Object other) { + return other instanceof ValueGeometry + && Arrays.equals(getBytes(), ((JTSValueGeometry) other).getBytes()); + } + + @Override + protected int compareSecure(Value v, CompareMode mode) { + Geometry g = ((JTSValueGeometry) v).getGeometryNoCopy(); + return getGeometryNoCopy().compareTo(g); + } + + @Override + public String getString() { + return new WKTWriter(3).write(getGeometryNoCopy()); + } + + @Override + public SpatialKey getSpatialKey(long id) { + Envelope env = getGeometryNoCopy().getEnvelopeInternal(); + return new SpatialKey(id, + (float) env.getMinX(), (float) env.getMaxX(), + (float) env.getMinY(), (float) env.getMaxY()); + } +} diff --git a/h2/src/main/org/h2/value/JTSValueGeometryFactory.java b/h2/src/main/org/h2/value/JTSValueGeometryFactory.java new file mode 100644 index 000000000..41add4d12 --- /dev/null +++ b/h2/src/main/org/h2/value/JTSValueGeometryFactory.java @@ -0,0 +1,158 @@ +/* + * Copyright 2004-2014 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.value; + +import org.h2.api.ValueGeometryFactory; +import org.h2.message.DbException; + +import com.vividsolutions.jts.geom.CoordinateSequence; +import com.vividsolutions.jts.geom.CoordinateSequenceFilter; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.geom.GeometryFactory; +import com.vividsolutions.jts.geom.PrecisionModel; +import com.vividsolutions.jts.io.ParseException; +import com.vividsolutions.jts.io.WKBReader; +import com.vividsolutions.jts.io.WKBWriter; +import com.vividsolutions.jts.io.WKTReader; + +/** + * The {@link ValueGeometryFactory} implementation for the JTS geometry + * framework. + * + * @author Steve Hruda + */ +public class JTSValueGeometryFactory implements ValueGeometryFactory { + + @Override + public Geometry getGeometry(byte[] bytes) throws DbException { + try { + return new WKBReader().read(bytes); + } catch (ParseException ex) { + throw DbException.convert(ex); + } + } + + @Override + public Geometry getGeometry(String s) throws DbException + { + try { + return new WKTReader().read(s); + } catch (ParseException ex) { + throw DbException.convert(ex); + } + } + + @Override + public Geometry getGeometry(String s, int srid) throws DbException { + try { + GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), srid); + return new WKTReader(geometryFactory).read(s); + }catch(ParseException ex) + { + throw DbException.convert(ex); + } + } + + @Override + public JTSValueGeometry get(Geometry g) { + byte[] bytes = convertToWKB(g); + return (JTSValueGeometry) Value.cache(new JTSValueGeometry(bytes, g)); + } + + + @Override + public JTSValueGeometry get(String s) { + try { + Geometry g = new WKTReader().read(s); + return get(g); + } catch (ParseException ex) { + throw DbException.convert(ex); + } + } + + @Override + public JTSValueGeometry get(String s, int srid) { + try { + GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), srid); + Geometry g = new WKTReader(geometryFactory).read(s); + return get(g); + } catch (ParseException ex) { + throw DbException.convert(ex); + } + } + + @Override + public JTSValueGeometry get(byte[] bytes) { + return (JTSValueGeometry) Value.cache(new JTSValueGeometry(bytes, null)); + } + + @Override + public JTSValueGeometry get(Object g) { + if(!isGeometryTypeSupported(g)) + throw new RuntimeException("The given object is not compatible with this ValueGeometryFactory instance!"); + + return get((Geometry)g); + } + + @Override + public JTSValueGeometry get(Value g) { + if(!(g instanceof JTSValueGeometry)) + throw new RuntimeException("The given value is not compatible with this ValueGeometryFactory instance!"); + + return (JTSValueGeometry) g; + } + + @Override + public boolean isGeometryTypeSupported(Object g) { + return g instanceof Geometry; + } + + @Override + public Class getGeometryType() { + return Geometry.class; + } + + private static byte[] convertToWKB(Geometry g) { + boolean includeSRID = g.getSRID() != 0; + int dimensionCount = getDimensionCount(g); + WKBWriter writer = new WKBWriter(dimensionCount, includeSRID); + return writer.write(g); + } + + private static int getDimensionCount(Geometry geometry) { + ZVisitor finder = new ZVisitor(); + geometry.apply(finder); + return finder.isFoundZ() ? 3 : 2; + } + + /** + * A visitor that checks if there is a Z coordinate. + */ + static class ZVisitor implements CoordinateSequenceFilter { + boolean foundZ; + + public boolean isFoundZ() { + return foundZ; + } + + @Override + public void filter(CoordinateSequence coordinateSequence, int i) { + if (!Double.isNaN(coordinateSequence.getOrdinate(i, 2))) { + foundZ = true; + } + } + + @Override + public boolean isDone() { + return foundZ; + } + + @Override + public boolean isGeometryChanged() { + return false; + } + } +} diff --git a/h2/src/main/org/h2/value/Transfer.java b/h2/src/main/org/h2/value/Transfer.java index ecc8016aa..de40aee21 100644 --- a/h2/src/main/org/h2/value/Transfer.java +++ b/h2/src/main/org/h2/value/Transfer.java @@ -693,9 +693,9 @@ public Value readValue() throws IOException { } case Value.GEOMETRY: if (version >= Constants.TCP_PROTOCOL_VERSION_14) { - return ValueGeometry.get(readBytes()); + return Value.getGeometryFactory().get(readBytes()); } - return ValueGeometry.get(readString()); + return Value.getGeometryFactory().get(readString()); default: throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "type=" + type); } diff --git a/h2/src/main/org/h2/value/Value.java b/h2/src/main/org/h2/value/Value.java index a78f9c8d3..9f084ee06 100644 --- a/h2/src/main/org/h2/value/Value.java +++ b/h2/src/main/org/h2/value/Value.java @@ -18,8 +18,12 @@ import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; +import java.util.Iterator; +import java.util.ServiceLoader; import org.h2.api.ErrorCode; +import org.h2.api.SpatialDriver; +import org.h2.api.ValueGeometryFactory; import org.h2.engine.Constants; import org.h2.engine.SysProperties; import org.h2.message.DbException; @@ -171,6 +175,18 @@ public abstract class Value { BigDecimal.valueOf(Long.MAX_VALUE); private static final BigDecimal MIN_LONG_DECIMAL = BigDecimal.valueOf(Long.MIN_VALUE); + + /** + * Factory which provides a couple of methods to create a {@link IGeometry} + * instance. + */ + private static final ValueGeometryFactory,?> GEOMETRY_FACTORY; + + static { + ServiceLoader geometryFactories = ServiceLoader.load(SpatialDriver.class); + Iterator geometryFactoryIterator = geometryFactories.iterator(); + GEOMETRY_FACTORY = (geometryFactoryIterator.hasNext() ? geometryFactories.iterator().next().createGeometryFactory() : null); + } /** * Get the SQL expression for this value. @@ -815,11 +831,11 @@ public Value convertTo(int targetType) { case GEOMETRY: switch(getType()) { case BYTES: - return ValueGeometry.get(getBytesNoCopy()); + return GEOMETRY_FACTORY.get(getBytesNoCopy()); case JAVA_OBJECT: Object object = JdbcUtils.deserialize(getBytesNoCopy(), getDataHandler()); - if (DataType.isGeometry(object)) { - return ValueGeometry.getFromGeometry(object); + if (GEOMETRY_FACTORY.isGeometryTypeSupported(object)) { + return GEOMETRY_FACTORY.get(object); } } } @@ -894,7 +910,7 @@ public Value convertTo(int targetType) { case UUID: return ValueUuid.get(s); case GEOMETRY: - return ValueGeometry.get(s); + return GEOMETRY_FACTORY.get(s); default: throw DbException.throwInternalError("type=" + targetType); } @@ -1167,5 +1183,18 @@ public interface ValueClob { public interface ValueBlob { // this is a marker interface } - + + /** + * Returns true if a IGeometryFactory is available and initialized. + * @return + */ + public static boolean isGeometryFactoryInitialized() + { + return GEOMETRY_FACTORY!=null; + } + + public static ValueGeometryFactory, ?> getGeometryFactory() + { + return GEOMETRY_FACTORY; + } } diff --git a/h2/src/main/org/h2/value/ValueGeometry.java b/h2/src/main/org/h2/value/ValueGeometry.java index 687b861dc..62f7da3c0 100644 --- a/h2/src/main/org/h2/value/ValueGeometry.java +++ b/h2/src/main/org/h2/value/ValueGeometry.java @@ -9,19 +9,8 @@ import java.sql.SQLException; import java.util.Arrays; -import com.vividsolutions.jts.geom.CoordinateSequence; -import com.vividsolutions.jts.geom.CoordinateSequenceFilter; -import com.vividsolutions.jts.geom.PrecisionModel; -import org.h2.message.DbException; +import org.h2.mvstore.rtree.SpatialKey; import org.h2.util.StringUtils; -import com.vividsolutions.jts.geom.Envelope; -import com.vividsolutions.jts.geom.Geometry; -import com.vividsolutions.jts.geom.GeometryFactory; -import com.vividsolutions.jts.io.ParseException; -import com.vividsolutions.jts.io.WKBReader; -import com.vividsolutions.jts.io.WKBWriter; -import com.vividsolutions.jts.io.WKTReader; -import com.vividsolutions.jts.io.WKTWriter; /** * Implementation of the GEOMETRY data type. @@ -29,26 +18,27 @@ * @author Thomas Mueller * @author Noel Grandin * @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888 + * @param the type of the used framework geometry */ -public class ValueGeometry extends Value { +public abstract class ValueGeometry extends Value{ /** - * As conversion from/to WKB cost a significant amount of CPU cycles, WKB - * are kept in ValueGeometry instance. + * As conversion from/to byte array cost a significant amount of CPU cycles, + * byte array are kept in ValueGeometry instance. * - * We always calculate the WKB, because not all WKT values can be - * represented in WKB, but since we persist it in WKB format, it has to be - * valid in WKB + * We always calculate the byte array, because not all geometry string + * representation values can be represented in byte array, but since we + * persist it in binary format, it has to be valid in byte array */ private final byte[] bytes; private final int hashCode; /** - * The value. Converted from WKB only on request as conversion from/to WKB - * cost a significant amount of CPU cycles. + * The value. Converted from byte array only on request as conversion + * from/to byte array cost a significant amount of CPU cycles. */ - private Geometry geometry; + private T geometry; /** * Create a new geometry objects. @@ -56,104 +46,32 @@ public class ValueGeometry extends Value { * @param bytes the bytes (always known) * @param geometry the geometry object (may be null) */ - private ValueGeometry(byte[] bytes, Geometry geometry) { + protected ValueGeometry(byte[] bytes, T geometry) { this.bytes = bytes; this.geometry = geometry; this.hashCode = Arrays.hashCode(bytes); } - /** - * Get or create a geometry value for the given geometry. - * - * @param o the geometry object (of type - * com.vividsolutions.jts.geom.Geometry) - * @return the value - */ - public static ValueGeometry getFromGeometry(Object o) { - return get((Geometry) o); - } - - private static ValueGeometry get(Geometry g) { - byte[] bytes = convertToWKB(g); - return (ValueGeometry) Value.cache(new ValueGeometry(bytes, g)); - } - - private static byte[] convertToWKB(Geometry g) { - boolean includeSRID = g.getSRID() != 0; - int dimensionCount = getDimensionCount(g); - WKBWriter writer = new WKBWriter(dimensionCount, includeSRID); - return writer.write(g); - } - - private static int getDimensionCount(Geometry geometry) { - ZVisitor finder = new ZVisitor(); - geometry.apply(finder); - return finder.isFoundZ() ? 3 : 2; - } - - /** - * Get or create a geometry value for the given geometry. - * - * @param s the WKT representation of the geometry - * @return the value - */ - public static ValueGeometry get(String s) { - try { - Geometry g = new WKTReader().read(s); - return get(g); - } catch (ParseException ex) { - throw DbException.convert(ex); - } - } - - /** - * Get or create a geometry value for the given geometry. - * - * @param s the WKT representation of the geometry - * @param srid the srid of the object - * @return the value - */ - public static ValueGeometry get(String s, int srid) { - try { - GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), srid); - Geometry g = new WKTReader(geometryFactory).read(s); - return get(g); - } catch (ParseException ex) { - throw DbException.convert(ex); - } - } - - /** - * Get or create a geometry value for the given geometry. - * - * @param bytes the WKB representation of the geometry - * @return the value - */ - public static ValueGeometry get(byte[] bytes) { - return (ValueGeometry) Value.cache(new ValueGeometry(bytes, null)); - } - /** * Get a copy of geometry object. Geometry object is mutable. The returned * object is therefore copied before returning. * * @return a copy of the geometry object */ - public Geometry getGeometry() { - return (Geometry) getGeometryNoCopy().clone(); - } - - public Geometry getGeometryNoCopy() { - if (geometry == null) { - try { - geometry = new WKBReader().read(bytes); - } catch (ParseException ex) { - throw DbException.convert(ex); - } - } - return geometry; - } - + public abstract T getGeometry(); + + /** + * Returns the internal geometry instance which should be immutable. + * @return the internal geometry instance which should be immutable + */ + @SuppressWarnings("unchecked") + public T getGeometryNoCopy() { + if (geometry == null) { + geometry = (T)getGeometryFactory().getGeometry(bytes); + } + return geometry; + } + /** * Test if this geometry envelope intersects with the other geometry * envelope. @@ -161,25 +79,50 @@ public Geometry getGeometryNoCopy() { * @param r the other geometry * @return true if the two overlap */ - public boolean intersectsBoundingBox(ValueGeometry r) { - // the Geometry object caches the envelope - return getGeometryNoCopy().getEnvelopeInternal().intersects( - r.getGeometryNoCopy().getEnvelopeInternal()); - } + protected abstract boolean _intersectsBoundingBox(ValueGeometry r); + /** + * Test if this geometry envelope intersects with the other geometry + * envelope. + * + * @param r the other geometry + * @return true if the two overlap + */ + @SuppressWarnings("unchecked") + public final boolean intersectsBoundingBox(ValueGeometry r) + { + if(!getClass().isInstance(r)){ + return false; // not supported and should never happen + } + + return _intersectsBoundingBox((ValueGeometry) r); + } + /** * Get the union. * * @param r the other geometry * @return the union of this geometry envelope and another geometry envelope */ - public Value getEnvelopeUnion(ValueGeometry r) { - GeometryFactory gf = new GeometryFactory(); - Envelope mergedEnvelope = new Envelope(getGeometryNoCopy().getEnvelopeInternal()); - mergedEnvelope.expandToInclude(r.getGeometryNoCopy().getEnvelopeInternal()); - return get(gf.toGeometry(mergedEnvelope)); - } - + protected abstract Value _getEnvelopeUnion(ValueGeometry r); + + /** + * Get the union. + * + * @param r the other geometry + * @return the union of this geometry envelope and another geometry envelope + */ + @SuppressWarnings("unchecked") + public final Value getEnvelopeUnion(ValueGeometry r) + { + if(!getClass().isInstance(r)){ + return ValueNull.INSTANCE; // not supported and should never happen + } + + return _getEnvelopeUnion((ValueGeometry) r); + } + + @Override public int getType() { return Value.GEOMETRY; @@ -193,17 +136,6 @@ public String getSQL() { return "X'" + StringUtils.convertBytesToHex(getBytesNoCopy()) + "'::Geometry"; } - @Override - protected int compareSecure(Value v, CompareMode mode) { - Geometry g = ((ValueGeometry) v).getGeometryNoCopy(); - return getGeometryNoCopy().compareTo(g); - } - - @Override - public String getString() { - return getWKT(); - } - @Override public long getPrecision() { return 0; @@ -221,12 +153,12 @@ public Object getObject() { @Override public byte[] getBytes() { - return getWKB(); + return bytes; } @Override public byte[] getBytesNoCopy() { - return getWKB(); + return bytes; } @Override @@ -237,39 +169,17 @@ public void set(PreparedStatement prep, int parameterIndex) @Override public int getDisplaySize() { - return getWKT().length(); + return getString().length(); } @Override public int getMemory() { - return getWKB().length * 20 + 24; + return getBytes().length * 20 + 24; } - + @Override - public boolean equals(Object other) { - // The JTS library only does half-way support for 3D coordinates, so - // their equals method only checks the first two coordinates. - return other instanceof ValueGeometry && - Arrays.equals(getWKB(), ((ValueGeometry) other).getWKB()); - } + public abstract boolean equals(Object other); - /** - * Get the value in Well-Known-Text format. - * - * @return the well-known-text - */ - public String getWKT() { - return new WKTWriter(3).write(getGeometryNoCopy()); - } - - /** - * Get the value in Well-Known-Binary format. - * - * @return the well-known-binary - */ - public byte[] getWKB() { - return bytes; - } @Override public Value convertTo(int targetType) { @@ -280,32 +190,9 @@ public Value convertTo(int targetType) { } /** - * A visitor that checks if there is a Z coordinate. + * Returns the {@link SpatialKey} for the given row key. + * @param id the row key + * @return the {@link SpatialKey} for the given row key */ - static class ZVisitor implements CoordinateSequenceFilter { - boolean foundZ; - - public boolean isFoundZ() { - return foundZ; - } - - @Override - public void filter(CoordinateSequence coordinateSequence, int i) { - if (!Double.isNaN(coordinateSequence.getOrdinate(i, 2))) { - foundZ = true; - } - } - - @Override - public boolean isDone() { - return foundZ; - } - - @Override - public boolean isGeometryChanged() { - return false; - } - - } - + public abstract SpatialKey getSpatialKey(long id); } diff --git a/h2/src/test/META-INF/services/org.h2.api.ISpatialDriver b/h2/src/test/META-INF/services/org.h2.api.ISpatialDriver new file mode 100644 index 000000000..ead47e333 --- /dev/null +++ b/h2/src/test/META-INF/services/org.h2.api.ISpatialDriver @@ -0,0 +1 @@ +org.h2.value.JTSSpatialDriver \ No newline at end of file diff --git a/h2/src/test/org/h2/test/db/TestSpatial.java b/h2/src/test/org/h2/test/db/TestSpatial.java index 5cc1b042f..e0397be4c 100644 --- a/h2/src/test/org/h2/test/db/TestSpatial.java +++ b/h2/src/test/org/h2/test/db/TestSpatial.java @@ -13,22 +13,24 @@ import java.sql.Types; import java.util.Random; -import com.vividsolutions.jts.geom.Envelope; -import com.vividsolutions.jts.geom.Point; -import com.vividsolutions.jts.geom.util.AffineTransformation; import org.h2.api.Aggregate; import org.h2.test.TestBase; import org.h2.tools.SimpleResultSet; import org.h2.tools.SimpleRowSource; import org.h2.value.DataType; +import org.h2.value.JTSValueGeometryFactory; import org.h2.value.Value; +import org.h2.value.ValueGeometry; + import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; +import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; +import com.vividsolutions.jts.geom.util.AffineTransformation; import com.vividsolutions.jts.io.ParseException; import com.vividsolutions.jts.io.WKTReader; -import org.h2.value.ValueGeometry; /** * Spatial datatype and index tests. @@ -58,7 +60,7 @@ public void test() throws SQLException { if (config.memory && config.mvcc) { return; } - if (DataType.GEOMETRY_CLASS != null) { + if (Value.isGeometryFactoryInitialized()) { deleteDb("spatial"); url = "spatial"; testSpatial(); @@ -93,11 +95,12 @@ private void testSpatial() throws SQLException { } private void testHashCode() { - ValueGeometry geomA = ValueGeometry + + ValueGeometry geomA = Value.getGeometryFactory() .get("POLYGON ((67 13 6, 67 18 5, 59 18 4, 59 13 6, 67 13 6))"); - ValueGeometry geomB = ValueGeometry + ValueGeometry geomB = Value.getGeometryFactory() .get("POLYGON ((67 13 6, 67 18 5, 59 18 4, 59 13 6, 67 13 6))"); - ValueGeometry geomC = ValueGeometry + ValueGeometry geomC = Value.getGeometryFactory() .get("POLYGON ((67 13 6, 67 18 5, 59 18 4, 59 13 5, 67 13 6))"); assertEquals(geomA.hashCode(), geomB.hashCode()); assertFalse(geomA.hashCode() == geomC.hashCode()); @@ -589,15 +592,16 @@ private void testGeometryDataType() { * Test serialization of Z and SRID values. */ private void testWKB() { - ValueGeometry geom3d = ValueGeometry.get( - "POLYGON ((67 13 6, 67 18 5, 59 18 4, 59 13 6, 67 13 6))", 27572); - ValueGeometry copy = ValueGeometry.get(geom3d.getBytes()); - assertEquals(6, copy.getGeometry().getCoordinates()[0].z); - assertEquals(5, copy.getGeometry().getCoordinates()[1].z); - assertEquals(4, copy.getGeometry().getCoordinates()[2].z); - // Test SRID - copy = ValueGeometry.get(geom3d.getBytes()); - assertEquals(27572, copy.getGeometry().getSRID()); + JTSValueGeometryFactory geometryFactory = new JTSValueGeometryFactory(); + ValueGeometry geom3d = geometryFactory.get( + "POLYGON ((67 13 6, 67 18 5, 59 18 4, 59 13 6, 67 13 6))", 27572); + ValueGeometry copy = geometryFactory.get(geom3d.getBytes()); + assertEquals(6, copy.getGeometry().getCoordinates()[0].z); + assertEquals(5, copy.getGeometry().getCoordinates()[1].z); + assertEquals(4, copy.getGeometry().getCoordinates()[2].z); + // Test SRID + copy = geometryFactory.get(geom3d.getBytes()); + assertEquals(27572, copy.getGeometry().getSRID()); } /** @@ -633,25 +637,25 @@ public static String getObjectString(Object object) { */ private void testEquals() { // 3d equality test - ValueGeometry geom3d = ValueGeometry.get( + ValueGeometry geom3d = Value.getGeometryFactory().get( "POLYGON ((67 13 6, 67 18 5, 59 18 4, 59 13 6, 67 13 6))"); - ValueGeometry geom2d = ValueGeometry.get( + ValueGeometry geom2d = Value.getGeometryFactory().get( "POLYGON ((67 13, 67 18, 59 18, 59 13, 67 13))"); assertFalse(geom3d.equals(geom2d)); // SRID equality test GeometryFactory geometryFactory = new GeometryFactory(); Geometry geometry = geometryFactory.createPoint(new Coordinate(0, 0)); geometry.setSRID(27572); - ValueGeometry valueGeometry = - ValueGeometry.getFromGeometry(geometry); + ValueGeometry valueGeometry = + Value.getGeometryFactory().get(geometry); Geometry geometry2 = geometryFactory.createPoint(new Coordinate(0, 0)); geometry2.setSRID(5326); - ValueGeometry valueGeometry2 = - ValueGeometry.getFromGeometry(geometry2); + ValueGeometry valueGeometry2 = + Value.getGeometryFactory().get(geometry2); assertFalse(valueGeometry.equals(valueGeometry2)); // Check illegal geometry (no WKB representation) try { - ValueGeometry.get("POINT EMPTY"); + Value.getGeometryFactory().get("POINT EMPTY"); fail("expected this to throw IllegalArgumentException"); } catch (IllegalArgumentException ex) { // expected @@ -796,14 +800,14 @@ private void testTableViewSpatialPredicate() throws SQLException { * Check ValueGeometry conversion into SQL script */ private void testValueGeometryScript() throws SQLException { - ValueGeometry valueGeometry = ValueGeometry.get("POINT(1 1 5)"); + ValueGeometry valueGeometry = Value.getGeometryFactory().get("POINT(1 1 5)"); Connection conn = getConnection(url); try { ResultSet rs = conn.createStatement().executeQuery( "SELECT " + valueGeometry.getSQL()); assertTrue(rs.next()); Object obj = rs.getObject(1); - ValueGeometry g = ValueGeometry.getFromGeometry(obj); + ValueGeometry g = Value.getGeometryFactory().get(obj); assertTrue("got: " + g + " exp: " + valueGeometry, valueGeometry.equals(g)); } finally { conn.close(); diff --git a/h2/src/test/org/h2/test/unit/TestValueMemory.java b/h2/src/test/org/h2/test/unit/TestValueMemory.java index 46bcb7c65..55bd1a1ee 100644 --- a/h2/src/test/org/h2/test/unit/TestValueMemory.java +++ b/h2/src/test/org/h2/test/unit/TestValueMemory.java @@ -195,10 +195,10 @@ private Value create(int type) throws SQLException { case Value.STRING_FIXED: return ValueStringFixed.get(randomString(random.nextInt(100))); case Value.GEOMETRY: - if (DataType.GEOMETRY_CLASS == null) { + if(!Value.isGeometryFactoryInitialized()){ return ValueNull.INSTANCE; } - return ValueGeometry.get("POINT (" + random.nextInt(100) + " " + + return Value.getGeometryFactory().get("POINT (" + random.nextInt(100) + " " + random.nextInt(100) + ")"); default: throw new AssertionError("type=" + type);