001: /*
002: * GeoLBS - OpenSource LocationReference Based Servces toolkit
003: * Copyright (C) 2003-2004, Julian J. Ray, All Rights Reserved
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation;
008: * version 2.1 of the License.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: */
016:
017: package org.geotools.data.geomedia.attributeio;
018:
019: import java.io.IOException;
020: import java.nio.ByteBuffer;
021: import java.nio.ByteOrder;
022: import java.util.ArrayList;
023: import java.util.Hashtable;
024:
025: import com.vividsolutions.jts.geom.Coordinate;
026: import com.vividsolutions.jts.geom.CoordinateArrays;
027: import com.vividsolutions.jts.geom.CoordinateList;
028: import com.vividsolutions.jts.geom.Geometry;
029: import com.vividsolutions.jts.geom.GeometryCollection;
030: import com.vividsolutions.jts.geom.GeometryFactory;
031: import com.vividsolutions.jts.geom.LineString;
032: import com.vividsolutions.jts.geom.LinearRing;
033: import com.vividsolutions.jts.geom.MultiLineString;
034: import com.vividsolutions.jts.geom.MultiPolygon;
035: import com.vividsolutions.jts.geom.Point;
036: import com.vividsolutions.jts.geom.Polygon;
037:
038: /**
039: * <p>
040: * Title: GeoTools2 Development
041: * </p>
042: *
043: * <p>
044: * Description:
045: * </p>
046: *
047: * <p>
048: * Copyright: Copyright (c) 2003
049: * </p>
050: *
051: * <p>
052: * Company:
053: * </p>
054: * Converts geometry objects between JTS geometry types and GeoMedia GDO serlaized geometry blobs. The following tables
055: * describe the mappings:
056: *
057: * <P></p>
058: *
059: * <P></p>
060: *
061: * @author Julian J. Ray
062: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/geomedia/src/main/java/org/geotools/data/geomedia/attributeio/GeoMediaGeometryAdapter.java $
063: * @version 1.0
064: *
065: * @todo Add interior polygons to Polygon constructor
066: * @todo Add support multi-polygon types
067: * @todo Add support for GeoMedia Arcs
068: */
069: public class GeoMediaGeometryAdapter {
070: // These are the Windows-based GUIDs used to identify each GeoMedia geometry type. Not used but here for reference!
071:
072: /** DOCUMENT ME! */
073: private static String[] mGdoGuidStrings = {
074: "0FD2FFC0-8CBC-11CF-ABDE-08003601B769", // Point Geometry
075: "0FD2FFC8-8CBC-11CF-ABDE-08003601B769", // Oriented Point Geometry
076: "0FD2FFC9-8CBC-11CF-ABDE-08003601B769", // Text Point Geometry
077: "0FD2FFC1-8CBC-11CF-ABDE-08003601B769", // Line Geometry
078: "0FD2FFC2-8CBC-11CF-ABDE-08003601B769", // Polyline Geometry
079: "0FD2FFC3-8CBC-11CF-ABDE-08003601B769", // Polygon Geometry
080: "0FD2FFC7-8CBC-11CF-ABDE-08003601B769", // Rectangle Geometry
081: "0FD2FFC5-8CBC-11CF-ABDE-08003601B769", // Boundary Geometry
082: "0FD2FFC6-8CBC-11CF-ABDE-08003601B769", // Geometry Collection (Hetereogeneous)
083: "0FD2FFCB-8CBC-11CF-ABDE-08003601B769", // Composite Polyline Geometry
084: "0FD2FFCC-8CBC-11CF-ABDE-08003601B769", // Composite Polygon Geometry
085: "0FD2FFCA-8CBC-11CF-ABDE-08003601B769" // Arc Geometry
086: };
087:
088: // This is the format of non-significant component of the GDO GUID headers represented
089: // as signed ints. These are written as unsigned bytes to the output stream
090:
091: /** DOCUMENT ME! */
092: private static int[] mGdoGuidByteArray = { 210, 15, 188, 140, 207,
093: 17, 171, 222, 8, 0, 54, 1, 183, 105 };
094:
095: // Used to construct JTS geometry objects
096:
097: /** DOCUMENT ME! */
098: GeometryFactory mGeometryFactory;
099:
100: /** DOCUMENT ME! */
101: private Hashtable mTypeMapping;
102:
103: /**
104: * Creates a new GeoMediaGeometryAdapter object.
105: */
106: public GeoMediaGeometryAdapter() {
107: // We cache a JTS geometry factory for expediency
108: mGeometryFactory = new GeometryFactory();
109:
110: // Set up the geometry type mappings
111: mTypeMapping = new Hashtable();
112: mTypeMapping.put(new Integer(65472), Point.class); // One-to-One correspondence
113: mTypeMapping.put(new Integer(65480), Point.class); // removes orientation component
114: mTypeMapping.put(new Integer(65481), Point.class); // Will not show the actual text but will indicate anchor points
115: mTypeMapping.put(new Integer(65473), LineString.class); // Converts simple lines to line strings
116: mTypeMapping.put(new Integer(65474), LineString.class); // One-to-One correspondence
117: mTypeMapping.put(new Integer(65475), Polygon.class); // One-to-One correspondence
118: mTypeMapping.put(new Integer(65479), Polygon.class); // Interpret rectangles as simple polygons
119: mTypeMapping.put(new Integer(65477), Polygon.class); // Interpret boundary polygons as simple polygons with no islands
120: mTypeMapping.put(new Integer(65478), GeometryCollection.class); // Heterogeneous collection
121: mTypeMapping.put(new Integer(65484), MultiLineString.class); // One-to-One correspondence
122: mTypeMapping.put(new Integer(65483), MultiPolygon.class); // Todo: Fix this
123:
124: //mTypeMapping.put(new Integer(65482), null); // No equivalent for arcs: TODO look for a stroking algorithm to piecewise it
125: }
126:
127: /**
128: * Converts GeoMedia blobs to JTS geometry types. Performs endian-conversion on data contained in the blob.
129: *
130: * @param input GeoMedia geometry blob read from geomedia spatial database.
131: *
132: * @return JTS Geometry
133: *
134: * @throws IOException
135: * @throws GeoMediaGeometryTypeNotKnownException
136: * @throws GeoMediaUnsupportedGeometryTypeException
137: */
138: public Geometry deSerialize(byte[] input) throws IOException,
139: GeoMediaGeometryTypeNotKnownException,
140: GeoMediaUnsupportedGeometryTypeException {
141: Geometry geom = null;
142:
143: if (input == null) {
144: return geom;
145: }
146:
147: // 40 bytes is the minimum size for a point geometry
148: if (input.length < 40) {
149: return geom;
150: }
151:
152: ByteBuffer byteBuffer = ByteBuffer.allocate(input.length);
153: byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
154: byteBuffer.put(input);
155: byteBuffer.position(0);
156:
157: // Extract the 16 byte GUID on the front end
158: // First 2 bytes (short) is the index into the type map as an unsigned short
159: int uidNum = (int) (byteBuffer.getShort() & 0xffff);
160:
161: // Skip the next 14 bytes
162: int[] vals = new int[14];
163:
164: for (int i = 0; i < 14; i++) {
165: vals[i] = byteBuffer.get();
166: }
167:
168: Class geomType = (Class) mTypeMapping.get(new Integer(uidNum));
169:
170: if (geomType == null) {
171: throw new GeoMediaGeometryTypeNotKnownException();
172: }
173:
174: // Delegate to the appropriate de-serializer. Throw exceptions if we come across a geometry we have not yet supported
175: if (geomType == Point.class) {
176: geom = createPointGeometry(byteBuffer);
177: } else if (geomType == LineString.class) {
178: geom = createLineStringGeometry(uidNum, byteBuffer);
179: } else if (geomType == MultiLineString.class) {
180: geom = createMultiLineStringGeometry(uidNum, byteBuffer);
181: } else if (geomType == Polygon.class) {
182: geom = createPolygonGeometry(uidNum, byteBuffer);
183: } else if (geomType == GeometryCollection.class) {
184: geom = createGeometryCollectionGeometry(byteBuffer);
185: } else if (geomType == MultiPolygon.class) { // TODO: Support this type
186: throw new GeoMediaUnsupportedGeometryTypeException();
187: }
188:
189: return geom;
190: }
191:
192: /**
193: * DOCUMENT ME!
194: *
195: * @param input DOCUMENT ME!
196: * @param binaryWriter DOCUMENT ME!
197: *
198: * @throws IOException DOCUMENT ME!
199: */
200: private void writeGUID(Geometry input, ByteBuffer binaryWriter)
201: throws IOException {
202: int guidFlag = 0;
203:
204: if (input instanceof Point) {
205: guidFlag = 65472;
206: } else if (input instanceof LineString) {
207: guidFlag = 65474;
208: } else if (input instanceof Polygon) {
209: guidFlag = 65475;
210: }
211:
212: // Notice that the short has to be unsigned
213: binaryWriter.putShort((short) (guidFlag & 0xffff));
214:
215: for (int i = 0; i < mGdoGuidByteArray.length; i++) {
216: binaryWriter.put((byte) mGdoGuidByteArray[i]);
217: }
218: }
219:
220: /**
221: * Converts a JTS geometry to a GeoMedia geometry blob which can be stored in a geomedia spatial database.
222: *
223: * @param input JTS Geometry
224: *
225: * @return byte[] GeoMedia blob format
226: *
227: * @throws IOException
228: * @throws GeoMediaUnsupportedGeometryTypeException
229: *
230: * @todo Figure out how to write the GDO_BOUNDS_XHI etc. bounding box for SQL Server data stores
231: */
232: public byte[] serialize(Geometry input) throws IOException,
233: GeoMediaUnsupportedGeometryTypeException {
234: ByteBuffer byteBuffer = ByteBuffer.allocate(65535);
235: byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
236: byteBuffer.position(0);
237:
238: if (input instanceof Point) {
239: Point geom = (Point) input;
240: Coordinate c = geom.getCoordinate();
241:
242: writeGUID(input, byteBuffer);
243: byteBuffer.putDouble(c.x);
244: byteBuffer.putDouble(c.y);
245: byteBuffer.putDouble((double) 0.0);
246: } else if (input instanceof LineString) {
247: LineString geom = (LineString) input;
248: Coordinate[] coords = geom.getCoordinates();
249:
250: writeGUID(input, byteBuffer);
251: byteBuffer.putInt(coords.length);
252:
253: for (int i = 0; i < coords.length; i++) {
254: byteBuffer.putDouble(coords[i].x);
255: byteBuffer.putDouble(coords[i].y);
256: byteBuffer.putDouble((double) 0.0);
257: }
258: } else if (input instanceof MultiLineString) {
259: MultiLineString geom = (MultiLineString) input;
260: int numGeoms = geom.getNumGeometries();
261:
262: writeGUID(input, byteBuffer);
263: byteBuffer.putInt(numGeoms);
264:
265: for (int i = 0; i < numGeoms; i++) {
266: // Use recursion to serialize all the sub-geometries
267: byte[] b = serialize(geom.getGeometryN(i));
268: byteBuffer.putInt(b.length);
269:
270: for (int j = 0; j < b.length; j++) {
271: byteBuffer.put(b[j]);
272: }
273: }
274: } else if (input instanceof Polygon) {
275: Polygon geom = (Polygon) input;
276: Coordinate[] coords = geom.getExteriorRing()
277: .getCoordinates();
278:
279: writeGUID(input, byteBuffer);
280: byteBuffer.putInt(coords.length);
281:
282: for (int i = 0; i < coords.length; i++) {
283: byteBuffer.putDouble(coords[i].x);
284: byteBuffer.putDouble(coords[i].y);
285: byteBuffer.putDouble((double) 0.0);
286: }
287: } else {
288: // Choke if we can't handle the geometry type yet...
289: throw new GeoMediaUnsupportedGeometryTypeException();
290: }
291:
292: return byteBuffer.array();
293: }
294:
295: /**
296: * Constructs and returns a JTS Point geometry
297: *
298: * @param x double
299: * @param y double
300: *
301: * @return Point
302: */
303: private Point createPointGeometry(double x, double y) {
304: return mGeometryFactory.createPoint(new Coordinate(x, y));
305: }
306:
307: /**
308: * Constructs and returns a JTS Point geometry by reading from the byte stream.
309: *
310: * @param reader LEDataInputStream reading from byte array containing a GeoMedia blob.
311: *
312: * @return Point
313: *
314: * @throws IOException
315: */
316: private Point createPointGeometry(ByteBuffer reader)
317: throws IOException {
318: return createPointGeometry(reader.getDouble(), reader
319: .getDouble());
320: }
321:
322: /**
323: * Constructs and returns a JTS LineString geometry.
324: *
325: * @param elems double[]
326: *
327: * @return LineString
328: */
329: private LineString createLineStringGeometry(double[] elems) {
330: CoordinateList list = new CoordinateList();
331:
332: // Check to see if the elems list is long enough
333: if ((elems.length != 0) && (elems.length < 4)) {
334: return null;
335: }
336:
337: // Watch for incorrectly encoded string from the server
338: if ((elems.length % 2) != 0) {
339: return null;
340: }
341:
342: for (int i = 0; i < elems.length;) {
343: list.add(new Coordinate(elems[i], elems[i + 1]));
344: i += 2;
345: }
346:
347: return mGeometryFactory.createLineString(CoordinateArrays
348: .toCoordinateArray(list));
349: }
350:
351: /**
352: * Constructs and returns a JTS LineString geometry from a GDO blob.
353: *
354: * @param guid int GeoMedia GUID flag.
355: * @param reader LEDataInputStream
356: *
357: * @return LineString
358: *
359: * @throws IOException
360: */
361: private LineString createLineStringGeometry(int guid,
362: ByteBuffer reader) throws IOException {
363: double[] a = null;
364:
365: if (guid == 65473) { // GDO Line Geometry
366: a = new double[4];
367: a[0] = reader.getDouble(); // x1
368: a[1] = reader.getDouble(); // y1
369: reader.getDouble(); // z1;
370: a[2] = reader.getDouble(); // x2
371: a[3] = reader.getDouble(); // y2
372: } else { // GDO Polyline Geometry
373:
374: int numOrdinates = reader.getInt();
375: a = new double[numOrdinates * 2];
376:
377: for (int i = 0; i < numOrdinates; i++) {
378: a[2 * i] = reader.getDouble(); // xn
379: a[(2 * i) + 1] = reader.getDouble(); // yn
380: reader.getDouble(); // zn
381: }
382: }
383:
384: return createLineStringGeometry(a);
385: }
386:
387: /**
388: * DOCUMENT ME!
389: *
390: * @param lineStrings DOCUMENT ME!
391: *
392: * @return DOCUMENT ME!
393: */
394: private MultiLineString createMultiLineStringGeometry(
395: ArrayList lineStrings) {
396: LineString[] array = new LineString[lineStrings.size()];
397:
398: for (int i = 0; i < lineStrings.size(); i++) {
399: array[i] = (LineString) lineStrings.get(i);
400: }
401:
402: return mGeometryFactory.createMultiLineString(array);
403: }
404:
405: /**
406: * DOCUMENT ME!
407: *
408: * @param guid DOCUMENT ME!
409: * @param reader DOCUMENT ME!
410: *
411: * @return DOCUMENT ME!
412: *
413: * @throws IOException DOCUMENT ME!
414: * @throws GeoMediaUnsupportedGeometryTypeException DOCUMENT ME!
415: * @throws GeoMediaGeometryTypeNotKnownException DOCUMENT ME!
416: */
417: private MultiLineString createMultiLineStringGeometry(int guid,
418: ByteBuffer reader) throws IOException,
419: GeoMediaUnsupportedGeometryTypeException,
420: GeoMediaGeometryTypeNotKnownException {
421: // get the number of items in the collection
422: int numItems = reader.getInt();
423:
424: // This is to hold the geometries from the collection
425: ArrayList array = new ArrayList();
426:
427: for (int i = 0; i < numItems; i++) {
428: // Read the size of the next blob
429: int elemSize = reader.getInt();
430:
431: // Recursively create a geometry from this blob
432: byte[] elem = new byte[elemSize];
433:
434: for (int j = 0; j < elemSize; j++) {
435: elem[j] = reader.get();
436: }
437:
438: Geometry g = deSerialize(elem);
439: array.add(g);
440: }
441:
442: // Now we need to append these items together
443: return createMultiLineStringGeometry(array);
444: }
445:
446: /**
447: * DOCUMENT ME!
448: *
449: * @param elems DOCUMENT ME!
450: *
451: * @return DOCUMENT ME!
452: */
453: private Polygon createPolygonGeometry(double[] elems) {
454: CoordinateList list = new CoordinateList();
455:
456: // Check to see if the elems list is long enough
457: if ((elems.length != 0) && (elems.length <= 6)) {
458: return null;
459: }
460:
461: // Watch for incorrectly encoded string from the server
462: if ((elems.length % 2) != 0) {
463: return null;
464: }
465:
466: for (int i = 0; i < elems.length;) {
467: list.add(new Coordinate(elems[i], elems[i + 1]));
468: i += 2;
469: }
470:
471: LinearRing ring = mGeometryFactory
472: .createLinearRing(CoordinateArrays
473: .toCoordinateArray(list));
474:
475: return mGeometryFactory.createPolygon(ring, null);
476: }
477:
478: /**
479: * DOCUMENT ME!
480: *
481: * @param guid DOCUMENT ME!
482: * @param reader DOCUMENT ME!
483: *
484: * @return DOCUMENT ME!
485: *
486: * @throws IOException DOCUMENT ME!
487: */
488: private Polygon createPolygonGeometry(int guid, ByteBuffer reader)
489: throws IOException {
490: double[] a = null;
491:
492: if (guid == 65475) { // Polygon Geometry
493:
494: int numOrdinates = reader.getInt();
495: a = new double[numOrdinates * 2];
496:
497: for (int i = 0; i < numOrdinates; i++) {
498: a[2 * i] = reader.getDouble();
499: a[(2 * i) + 1] = reader.getDouble();
500: reader.getDouble();
501: }
502: } else if (guid == 65479) { // Rectangle geomety
503:
504: // x, y, z, width, height
505: double x = reader.getDouble();
506: double y = reader.getDouble();
507: double z = reader.getDouble();
508: double w = reader.getDouble();
509: double h = reader.getDouble();
510:
511: a = new double[8];
512: a[0] = x;
513: a[1] = y;
514: a[2] = x + w;
515: a[3] = y;
516: a[4] = x + w;
517: a[5] = y + h;
518: a[6] = x;
519: a[7] = y + h;
520: }
521:
522: return createPolygonGeometry(a);
523: }
524:
525: /**
526: * DOCUMENT ME!
527: *
528: * @param reader DOCUMENT ME!
529: *
530: * @return DOCUMENT ME!
531: *
532: * @throws IOException DOCUMENT ME!
533: * @throws GeoMediaUnsupportedGeometryTypeException DOCUMENT ME!
534: * @throws GeoMediaGeometryTypeNotKnownException DOCUMENT ME!
535: */
536: private GeometryCollection createGeometryCollectionGeometry(
537: ByteBuffer reader) throws IOException,
538: GeoMediaUnsupportedGeometryTypeException,
539: GeoMediaGeometryTypeNotKnownException {
540: // get the number of items in the collection
541: int numItems = reader.getInt();
542:
543: // This is to hold the geometries from the collection
544: ArrayList array = new ArrayList();
545:
546: for (int i = 0; i < numItems; i++) {
547: // Read the size of the next blob
548: int elemSize = reader.getInt();
549:
550: // Recursively create a geometry from this blob
551: byte[] elem = new byte[elemSize];
552:
553: for (int j = 0; j < elemSize; j++) {
554: elem[j] = reader.get();
555: }
556:
557: Geometry g = deSerialize(elem);
558:
559: if (g != null) {
560: array.add(g);
561: }
562: }
563:
564: // Now we need to append these items together
565: return createGeometryCollectionGeometry(array);
566: }
567:
568: /**
569: * DOCUMENT ME!
570: *
571: * @param geoms DOCUMENT ME!
572: *
573: * @return DOCUMENT ME!
574: */
575: private GeometryCollection createGeometryCollectionGeometry(
576: ArrayList geoms) {
577: Geometry[] array = new Geometry[geoms.size()];
578:
579: for (int i = 0; i < geoms.size(); i++) {
580: array[i] = (Geometry) geoms.get(i);
581: }
582:
583: return mGeometryFactory.createGeometryCollection(array);
584: }
585: }
|