1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.value; |
7 | |
8 | import java.sql.PreparedStatement; |
9 | import java.sql.SQLException; |
10 | import java.util.Arrays; |
11 | |
12 | import com.vividsolutions.jts.geom.CoordinateSequence; |
13 | import com.vividsolutions.jts.geom.CoordinateSequenceFilter; |
14 | import com.vividsolutions.jts.geom.PrecisionModel; |
15 | import org.h2.message.DbException; |
16 | import org.h2.util.StringUtils; |
17 | import com.vividsolutions.jts.geom.Envelope; |
18 | import com.vividsolutions.jts.geom.Geometry; |
19 | import com.vividsolutions.jts.geom.GeometryFactory; |
20 | import com.vividsolutions.jts.io.ParseException; |
21 | import com.vividsolutions.jts.io.WKBReader; |
22 | import com.vividsolutions.jts.io.WKBWriter; |
23 | import com.vividsolutions.jts.io.WKTReader; |
24 | import com.vividsolutions.jts.io.WKTWriter; |
25 | |
26 | /** |
27 | * Implementation of the GEOMETRY data type. |
28 | * |
29 | * @author Thomas Mueller |
30 | * @author Noel Grandin |
31 | * @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888 |
32 | */ |
33 | public class ValueGeometry extends Value { |
34 | |
35 | /** |
36 | * As conversion from/to WKB cost a significant amount of CPU cycles, WKB |
37 | * are kept in ValueGeometry instance. |
38 | * |
39 | * We always calculate the WKB, because not all WKT values can be |
40 | * represented in WKB, but since we persist it in WKB format, it has to be |
41 | * valid in WKB |
42 | */ |
43 | private final byte[] bytes; |
44 | |
45 | private final int hashCode; |
46 | |
47 | /** |
48 | * The value. Converted from WKB only on request as conversion from/to WKB |
49 | * cost a significant amount of CPU cycles. |
50 | */ |
51 | private Geometry geometry; |
52 | |
53 | /** |
54 | * Create a new geometry objects. |
55 | * |
56 | * @param bytes the bytes (always known) |
57 | * @param geometry the geometry object (may be null) |
58 | */ |
59 | private ValueGeometry(byte[] bytes, Geometry geometry) { |
60 | this.bytes = bytes; |
61 | this.geometry = geometry; |
62 | this.hashCode = Arrays.hashCode(bytes); |
63 | } |
64 | |
65 | /** |
66 | * Get or create a geometry value for the given geometry. |
67 | * |
68 | * @param o the geometry object (of type |
69 | * com.vividsolutions.jts.geom.Geometry) |
70 | * @return the value |
71 | */ |
72 | public static ValueGeometry getFromGeometry(Object o) { |
73 | return get((Geometry) o); |
74 | } |
75 | |
76 | private static ValueGeometry get(Geometry g) { |
77 | byte[] bytes = convertToWKB(g); |
78 | return (ValueGeometry) Value.cache(new ValueGeometry(bytes, g)); |
79 | } |
80 | |
81 | private static byte[] convertToWKB(Geometry g) { |
82 | boolean includeSRID = g.getSRID() != 0; |
83 | int dimensionCount = getDimensionCount(g); |
84 | WKBWriter writer = new WKBWriter(dimensionCount, includeSRID); |
85 | return writer.write(g); |
86 | } |
87 | |
88 | private static int getDimensionCount(Geometry geometry) { |
89 | ZVisitor finder = new ZVisitor(); |
90 | geometry.apply(finder); |
91 | return finder.isFoundZ() ? 3 : 2; |
92 | } |
93 | |
94 | /** |
95 | * Get or create a geometry value for the given geometry. |
96 | * |
97 | * @param s the WKT representation of the geometry |
98 | * @return the value |
99 | */ |
100 | public static ValueGeometry get(String s) { |
101 | try { |
102 | Geometry g = new WKTReader().read(s); |
103 | return get(g); |
104 | } catch (ParseException ex) { |
105 | throw DbException.convert(ex); |
106 | } |
107 | } |
108 | |
109 | /** |
110 | * Get or create a geometry value for the given geometry. |
111 | * |
112 | * @param s the WKT representation of the geometry |
113 | * @param srid the srid of the object |
114 | * @return the value |
115 | */ |
116 | public static ValueGeometry get(String s, int srid) { |
117 | try { |
118 | GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), srid); |
119 | Geometry g = new WKTReader(geometryFactory).read(s); |
120 | return get(g); |
121 | } catch (ParseException ex) { |
122 | throw DbException.convert(ex); |
123 | } |
124 | } |
125 | |
126 | /** |
127 | * Get or create a geometry value for the given geometry. |
128 | * |
129 | * @param bytes the WKB representation of the geometry |
130 | * @return the value |
131 | */ |
132 | public static ValueGeometry get(byte[] bytes) { |
133 | return (ValueGeometry) Value.cache(new ValueGeometry(bytes, null)); |
134 | } |
135 | |
136 | /** |
137 | * Get a copy of geometry object. Geometry object is mutable. The returned |
138 | * object is therefore copied before returning. |
139 | * |
140 | * @return a copy of the geometry object |
141 | */ |
142 | public Geometry getGeometry() { |
143 | return (Geometry) getGeometryNoCopy().clone(); |
144 | } |
145 | |
146 | public Geometry getGeometryNoCopy() { |
147 | if (geometry == null) { |
148 | try { |
149 | geometry = new WKBReader().read(bytes); |
150 | } catch (ParseException ex) { |
151 | throw DbException.convert(ex); |
152 | } |
153 | } |
154 | return geometry; |
155 | } |
156 | |
157 | /** |
158 | * Test if this geometry envelope intersects with the other geometry |
159 | * envelope. |
160 | * |
161 | * @param r the other geometry |
162 | * @return true if the two overlap |
163 | */ |
164 | public boolean intersectsBoundingBox(ValueGeometry r) { |
165 | // the Geometry object caches the envelope |
166 | return getGeometryNoCopy().getEnvelopeInternal().intersects( |
167 | r.getGeometryNoCopy().getEnvelopeInternal()); |
168 | } |
169 | |
170 | /** |
171 | * Get the union. |
172 | * |
173 | * @param r the other geometry |
174 | * @return the union of this geometry envelope and another geometry envelope |
175 | */ |
176 | public Value getEnvelopeUnion(ValueGeometry r) { |
177 | GeometryFactory gf = new GeometryFactory(); |
178 | Envelope mergedEnvelope = new Envelope(getGeometryNoCopy().getEnvelopeInternal()); |
179 | mergedEnvelope.expandToInclude(r.getGeometryNoCopy().getEnvelopeInternal()); |
180 | return get(gf.toGeometry(mergedEnvelope)); |
181 | } |
182 | |
183 | @Override |
184 | public int getType() { |
185 | return Value.GEOMETRY; |
186 | } |
187 | |
188 | @Override |
189 | public String getSQL() { |
190 | // WKT does not hold Z or SRID with JTS 1.13. As getSQL is used to |
191 | // export database, it should contains all object attributes. Moreover |
192 | // using bytes is faster than converting WKB to Geometry then to WKT. |
193 | return "X'" + StringUtils.convertBytesToHex(getBytesNoCopy()) + "'::Geometry"; |
194 | } |
195 | |
196 | @Override |
197 | protected int compareSecure(Value v, CompareMode mode) { |
198 | Geometry g = ((ValueGeometry) v).getGeometryNoCopy(); |
199 | return getGeometryNoCopy().compareTo(g); |
200 | } |
201 | |
202 | @Override |
203 | public String getString() { |
204 | return getWKT(); |
205 | } |
206 | |
207 | @Override |
208 | public long getPrecision() { |
209 | return 0; |
210 | } |
211 | |
212 | @Override |
213 | public int hashCode() { |
214 | return hashCode; |
215 | } |
216 | |
217 | @Override |
218 | public Object getObject() { |
219 | return getGeometry(); |
220 | } |
221 | |
222 | @Override |
223 | public byte[] getBytes() { |
224 | return getWKB(); |
225 | } |
226 | |
227 | @Override |
228 | public byte[] getBytesNoCopy() { |
229 | return getWKB(); |
230 | } |
231 | |
232 | @Override |
233 | public void set(PreparedStatement prep, int parameterIndex) |
234 | throws SQLException { |
235 | prep.setObject(parameterIndex, getGeometryNoCopy()); |
236 | } |
237 | |
238 | @Override |
239 | public int getDisplaySize() { |
240 | return getWKT().length(); |
241 | } |
242 | |
243 | @Override |
244 | public int getMemory() { |
245 | return getWKB().length * 20 + 24; |
246 | } |
247 | |
248 | @Override |
249 | public boolean equals(Object other) { |
250 | // The JTS library only does half-way support for 3D coordinates, so |
251 | // their equals method only checks the first two coordinates. |
252 | return other instanceof ValueGeometry && |
253 | Arrays.equals(getWKB(), ((ValueGeometry) other).getWKB()); |
254 | } |
255 | |
256 | /** |
257 | * Get the value in Well-Known-Text format. |
258 | * |
259 | * @return the well-known-text |
260 | */ |
261 | public String getWKT() { |
262 | return new WKTWriter(3).write(getGeometryNoCopy()); |
263 | } |
264 | |
265 | /** |
266 | * Get the value in Well-Known-Binary format. |
267 | * |
268 | * @return the well-known-binary |
269 | */ |
270 | public byte[] getWKB() { |
271 | return bytes; |
272 | } |
273 | |
274 | @Override |
275 | public Value convertTo(int targetType) { |
276 | if (targetType == Value.JAVA_OBJECT) { |
277 | return this; |
278 | } |
279 | return super.convertTo(targetType); |
280 | } |
281 | |
282 | /** |
283 | * A visitor that checks if there is a Z coordinate. |
284 | */ |
285 | static class ZVisitor implements CoordinateSequenceFilter { |
286 | boolean foundZ; |
287 | |
288 | public boolean isFoundZ() { |
289 | return foundZ; |
290 | } |
291 | |
292 | @Override |
293 | public void filter(CoordinateSequence coordinateSequence, int i) { |
294 | if (!Double.isNaN(coordinateSequence.getOrdinate(i, 2))) { |
295 | foundZ = true; |
296 | } |
297 | } |
298 | |
299 | @Override |
300 | public boolean isDone() { |
301 | return foundZ; |
302 | } |
303 | |
304 | @Override |
305 | public boolean isGeometryChanged() { |
306 | return false; |
307 | } |
308 | |
309 | } |
310 | |
311 | } |