1 /*
2  * Copyright (c) 2016 Vivid Solutions.
3  *
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
7  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
8  * and the Eclipse Distribution License is available at
9  *
10  * http://www.eclipse.org/org/documents/edl-v10.php.
11  */
12 package org.locationtech.jtstest.util.io;
13 
14 import java.io.IOException;
15 import java.io.StringWriter;
16 import java.io.Writer;
17 
18 import org.locationtech.jts.geom.Geometry;
19 import org.locationtech.jts.io.ByteArrayInStream;
20 import org.locationtech.jts.io.ByteOrderDataInStream;
21 import org.locationtech.jts.io.ByteOrderValues;
22 import org.locationtech.jts.io.InStream;
23 import org.locationtech.jts.io.ParseException;
24 import org.locationtech.jts.io.WKBConstants;
25 import org.locationtech.jts.io.WKBWriter;
26 
27 /**
28  * Dumps out WKB in a structured formatted text display.
29  */
30 public class WKBDumper
31 {
dump(byte[] bytes, Writer writer)32   public static void dump(byte[] bytes, Writer writer) {
33     WKBDumper dumper = new WKBDumper();
34     dumper.read(bytes, writer);
35   }
36 
dump(byte[] bytes)37   public static String dump(byte[] bytes) {
38     WKBDumper dumper = new WKBDumper();
39     return dumper.readString(bytes);
40   }
41 
42   private ByteOrderDataInStream dis = new ByteOrderDataInStream();
43   private Writer writer;
44   private int inputDimension;
45 
WKBDumper()46   public WKBDumper() {
47 
48   }
49 
readString(byte[] bytes)50   private String readString(byte[] bytes) {
51     writer = new StringWriter();
52     read(bytes);
53     return writer.toString();
54   }
55 
read(byte[] bytes, Writer writer)56   private void read(byte[] bytes, Writer writer) {
57     this.writer = writer;
58     read(bytes);
59   }
60 
61   /**
62    * Reads a single {@link Geometry} in WKB format from a byte array.
63    *
64    * @param bytes the byte array to read from
65    * @return the geometry read
66    * @throws IOException
67    * @throws ParseException if the WKB is ill-formed
68    */
read(byte[] bytes)69   private void read(byte[] bytes)
70   {
71     // possibly reuse the ByteArrayInStream?
72     // don't throw IOExceptions, since we are not doing any I/O
73     try {
74       read(new ByteArrayInStream(bytes));
75     }
76     catch (Exception ex) {
77       // TODO Auto-generated catch block
78       try {
79         writer.write("ParseException: " + ex.getMessage() + "\n");
80       } catch (IOException e) {
81         // Nothing we can do
82       }
83     }
84   }
85 
86   /**
87    * Reads a {@link Geometry} in binary WKB format from an {@link InStream}.
88    *
89    * @param is the stream to read from
90    * @return the Geometry read
91    * @throws IOException if the underlying stream creates an error
92    * @throws ParseException if the WKB is ill-formed
93    */
read(InStream is)94   private void read(InStream is)
95   throws IOException, ParseException
96   {
97     dis.setInStream(is);
98     readGeometry(0);
99   }
100 
readGeometry(int SRID)101   private void readGeometry(int SRID)
102   throws IOException, ParseException
103   {
104     // determine byte order
105     byte byteOrderWKB = readEndian();
106 
107     // always set byte order, since it may change from geometry to geometry
108     if ( byteOrderWKB == WKBConstants.wkbNDR ) {
109       dis.setOrder(ByteOrderValues.LITTLE_ENDIAN);
110     } else if ( byteOrderWKB == WKBConstants.wkbXDR ) {
111       dis.setOrder(ByteOrderValues.BIG_ENDIAN);
112     }
113 
114     int typeInt = readInt();
115 
116     // Adds %1000 to make it compatible with OGC 06-103r4
117     int geometryType = (typeInt & 0xffff)%1000;
118 
119     // handle 3D and 4D WKB geometries
120     // geometries with Z coordinates have the 0x80 flag (postgis EWKB)
121     // or are in the 1000 range (Z) or in the 3000 range (ZM) of geometry type (OGC 06-103r4)
122     boolean hasZ = ((typeInt & 0x80000000) != 0 || (typeInt & 0xffff)/1000 == 1 || (typeInt & 0xffff)/1000 == 3);
123     // geometries with M coordinates have the 0x40 flag (postgis EWKB)
124     // or are in the 1000 range (M) or in the 3000 range (ZM) of geometry type (OGC 06-103r4)
125     boolean hasM = ((typeInt & 0x40000000) != 0 || (typeInt & 0xffff)/1000 == 2 || (typeInt & 0xffff)/1000 == 3);
126     //System.out.println(typeInt + " - " + geometryType + " - hasZ:" + hasZ);
127     inputDimension = 2 + (hasZ?1:0) + (hasM?1:0);
128 
129     // determine if SRIDs are present
130     boolean hasSRID = (typeInt & 0x20000000) != 0;
131 
132     writer.write(geometryTypeName(geometryType) + " ( " + geometryType + " ) ");
133     if (hasZ) writer.write(" Z" );
134     if (hasM) writer.write(" M" );
135     if (hasSRID) writer.write(" SRID" );
136 
137     writer.write("\n");
138 
139 
140     if (hasSRID) {
141       SRID = readTaggedInt("SRID");
142     }
143 
144     Geometry geom = null;
145     switch (geometryType) {
146       case WKBConstants.wkbPoint :
147         readPoint();
148         break;
149       case WKBConstants.wkbLineString :
150         readLineString();
151         break;
152      case WKBConstants.wkbPolygon :
153        readPolygon();
154         break;
155       case WKBConstants.wkbMultiPoint :
156       case WKBConstants.wkbMultiLineString :
157       case WKBConstants.wkbMultiPolygon :
158       case WKBConstants.wkbGeometryCollection :
159         readGeometryCollection(SRID);
160         break;
161       default:
162         //throw new ParseException("Unknown WKB type " + geometryType);
163     }
164   }
165 
geometryTypeName(int geometryType)166   private static String geometryTypeName(int geometryType) {
167     switch (geometryType) {
168     case WKBConstants.wkbPoint : return "POINT";
169     case WKBConstants.wkbLineString : return "LINESTRING";
170     case WKBConstants.wkbPolygon : return "POLYGON";
171     case WKBConstants.wkbMultiPoint : return "MULTIPOINT";
172     case WKBConstants.wkbMultiLineString : return "MULTILINESTRING";
173     case WKBConstants.wkbMultiPolygon : return "MULTIPOLYGON";
174     case WKBConstants.wkbGeometryCollection : return "GEOMETRYCOLLECTION";
175     default:
176       return "Unknown";
177     }
178   }
179 
readPoint()180   private void readPoint() throws IOException, ParseException
181   {
182     readCoordinateSequence(1);
183     // If X and Y are NaN create a empty point
184     /*
185     if (Double.isNaN(pts.getX(0)) || Double.isNaN(pts.getY(0))) {
186       //return factory.createPoint();
187     }
188     */
189   }
190 
readLineString()191   private void readLineString() throws IOException, ParseException
192   {
193     int size = readTaggedInt("Num Points");
194     readCoordinateSequence(size);
195   }
196 
readLinearRing()197   private void readLinearRing() throws IOException, ParseException
198   {
199     int size = readTaggedInt("Num Points");
200     readCoordinateSequence(size);
201   }
202 
readPolygon()203   private void readPolygon() throws IOException, ParseException
204   {
205     int numRings = readTaggedInt("Num Rings");
206     readLinearRing();
207     for (int i = 0; i < numRings - 1; i++) {
208       readLinearRing();
209     }
210   }
211 
readGeometryCollection(int SRID)212   private void readGeometryCollection(int SRID) throws IOException, ParseException
213   {
214     int numGeom = readTaggedInt("Num Elements");
215     for (int i = 0; i < numGeom; i++) {
216       writer.write(" ------- [ " + i + " ] ---------------\n");
217       readGeometry(SRID);
218     }
219   }
220 
readCoordinateSequence(int size)221   private void readCoordinateSequence(int size) throws IOException, ParseException
222   {
223     for (int i = 0; i < size; i++) {
224       readCoordinate(i);
225     }
226   }
227 
228   /**
229    * Reads a coordinate value with the specified dimensionality.
230    * Makes the X and Y ordinates precise according to the precision model
231    * in use.
232    * @throws ParseException
233    */
readCoordinate(int index)234   private void readCoordinate(int index) throws IOException, ParseException
235   {
236     writer.write(dis.getCount() + ": ");
237     String hex = "";
238     String nums = "";
239     for (int i = 0; i < inputDimension; i++) {
240         double d = dis.readDouble();
241         hex += WKBWriter.toHex(dis.getData()) + " ";
242         nums += (i > 0 ? ", " : "") + d;
243     }
244     writer.write(hex + " [" + index + "] " + nums + "\n");
245   }
246 
readTaggedInt(String tag)247   private int readTaggedInt(String tag) throws IOException, ParseException {
248     int size = readInt();
249     writer.write(tag + " = " + size + "\n");
250     return size;
251   }
252 
readInt()253   private int readInt() throws IOException, ParseException {
254     writer.write(dis.getCount() + ": ");
255     int i = dis.readInt();
256 
257     // TODO: write in hex
258     writer.write(WKBWriter.toHex(dis.getData()) + " - ");
259     return i;
260   }
261 
readEndian()262   private byte readEndian() throws IOException, ParseException {
263     writer.write(dis.getCount() + ": ");
264     byte i = dis.readByte();
265     String endian = i == WKBConstants.wkbNDR ? "NDR (Little endian)" :"XDR (Big endian)";
266     // TODO: write in hex
267     writer.write(WKBWriter.toHex(dis.getData()) + " - " + endian + "\n");
268     return i;
269   }
270 
271 }
272