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