/* * The JTS Topology Suite is a collection of Java classes that * implement the fundamental operations required to validate a given * geo-spatial data set to a known topological specification. * * Copyright (C) 2001 Vivid Solutions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.revolsys.geometry.wkb; import java.io.ByteArrayOutputStream; import java.io.IOException; import com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.Lineal; import com.revolsys.geometry.model.LinearRing; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.Polygon; import com.revolsys.geometry.model.Polygonal; import com.revolsys.geometry.model.Punctual; import com.revolsys.geometry.util.Assert; /** * Writes a {@link Geometry} into Well-Known Binary format. * Supports use of an {@link OutStream}, which allows easy use * with arbitary byte stream sinks. * <p> * The WKB format is specified in the * OGC <A HREF="http://www.opengis.org/techno/specs.htm"><i>Simple Features for SQL</i></a> * specification. * This implementation also supports the <b>Extended WKB</b> * standard. Extended WKB allows writing 3-dimensional coordinates * and including the geometry SRID value. * The presence of 3D coordinates is signified * by setting the high bit of the <tt>wkbType</tt> word. * The presence of an SRID is signified * by setting the third bit of the <tt>wkbType</tt> word. * EWKB format is upward compatible with the original SFS WKB format. * <p> * Empty Point cannot be represented in WKB; an * {@link IllegalArgumentException} will be thrown if one is * written. * <p> * The WKB specification does not support representing {@link LinearRing}s; * they will be written as {@link LineString}s. * <p> * This class is designed to support reuse of a single instance to read multiple * geometries. This class is not thread-safe; each thread should create its own * instance. * * <h3>Syntax</h3> * The following syntax specification describes the version of Well-Known Binary * supported by JTS. * <p> * <i>The specification uses a syntax language similar to that used in * the C language. Bitfields are specified from hi-order to lo-order bits.</i> * <p> * <blockquote><pre> * * <b>byte</b> = 1 byte * <b>uint32</b> = 32 bit unsigned integer (4 bytes) * <b>double</b> = double precision number (8 bytes) * * abstract Point { } * * Point2D extends Point { * <b>double</b> x; * <b>double</b> y; * } * * Point3D extends Point { * <b>double</b> x; * <b>double</b> y; * <b>double</b> z; * } * * LinearRing { * <b>uint32</b> numPoints; * Point points[numPoints]; * } * * enum wkbGeometryType { * wkbPoint = 1, * wkbLineString = 2, * wkbPolygon = 3, * wkbMultiPoint = 4, * wkbMultiLineString = 5, * wkbMultiPolygon = 6, * wkbGeometryCollection = 7 * } * * enum byteOrder { * wkbXDR = 0, // Big Endian * wkbNDR = 1 // Little Endian * } * * WKBType { * <b>uint32</b> wkbGeometryType : 8; // values from enum wkbGeometryType * } * * EWKBType { * <b>uint32</b> is3D : 1; // 0 = 2D, 1 = 3D * <b>uint32</b> noData1 : 1; * <b>uint32</b> hasSRID : 1; // 0, no, 1 = yes * <b>uint32</b> noData2 : 21; * <b>uint32</b> wkbGeometryType : 8; // values from enum wkbGeometryType * } * * abstract WKBGeometry { * <b>byte</b> byteOrder; // values from enum byteOrder * EWKBType wkbType * [ <b>uint32</b> srid; ] // only if hasSRID = yes * } * * WKBPoint extends WKBGeometry { * Point point; * } * * WKBLineString extends WKBGeometry { * <b>uint32</b> numCoords; * Point points[numCoords]; * } * * WKBPolygon extends WKBGeometry { * <b>uint32</b> numRings; * LinearRing rings[numRings]; * } * * WKBMultiPoint extends WKBGeometry { * <b>uint32</b> numElems; * WKBPoint elems[numElems]; * } * * WKBMultiLineString extends WKBGeometry { * <b>uint32</b> numElems; * WKBLineString elems[numElems]; * } * * wkbMultiPolygon extends WKBGeometry { * <b>uint32</b> numElems; * WKBPolygon elems[numElems]; * } * * WKBGeometryCollection extends WKBGeometry { * <b>uint32</b> numElems; * WKBGeometry elems[numElems]; * } * * </pre></blockquote> * @see WKBReader */ public class WKBWriter { /** * Converts a byte array to a hexadecimal string. * * @param bytes * @return a string of hexadecimal digits * * @deprecated */ @Deprecated public static String bytesToHex(final byte[] bytes) { return toHex(bytes); } /** * Converts a byte array to a hexadecimal string. * * @param bytes a byte array * @return a string of hexadecimal digits */ public static String toHex(final byte[] bytes) { final StringBuilder buf = new StringBuilder(); for (final byte b : bytes) { buf.append(toHexDigit(b >> 4 & 0x0F)); buf.append(toHexDigit(b & 0x0F)); } return buf.toString(); } private static char toHexDigit(final int n) { if (n < 0 || n > 15) { throw new IllegalArgumentException("Nibble value out of range: " + n); } if (n <= 9) { return (char)('0' + n); } return (char)('A' + (n - 10)); } // holds output data values private final byte[] buf = new byte[8]; private final ByteArrayOutputStream byteArrayOS = new ByteArrayOutputStream(); private final OutStream byteArrayOutStream = new OutputStreamOutStream(this.byteArrayOS); private final int byteOrder; private boolean includeSRID = false; private int outputDimension = 2; /** * Creates a writer that writes {@link Geometry}s with * output dimension = 2 and BIG_ENDIAN byte order */ public WKBWriter() { this(2, ByteOrderValues.BIG_ENDIAN); } /** * Creates a writer that writes {@link Geometry}s with * the given dimension (2 or 3) for output coordinates * and {@link ByteOrderValues#BIG_ENDIAN} byte order. * * @param outputDimension the coordinate dimension to output (2 or 3) */ public WKBWriter(final int outputDimension) { this(outputDimension, ByteOrderValues.BIG_ENDIAN); } /** * Creates a writer that writes {@link Geometry}s with * the given dimension (2 or 3) for output coordinates * and {@link ByteOrderValues#BIG_ENDIAN} byte order. This constructor also * takes a flag to control whether srid information will be * written. * * @param outputDimension the coordinate dimension to output (2 or 3) * @param includeSRID indicates whether SRID should be written */ public WKBWriter(final int outputDimension, final boolean includeSRID) { this(outputDimension, ByteOrderValues.BIG_ENDIAN, includeSRID); } /** * Creates a writer that writes {@link Geometry}s with * the given dimension (2 or 3) for output coordinates * and byte order * * @param outputDimension the coordinate dimension to output (2 or 3) * @param byteOrder the byte ordering to use */ public WKBWriter(final int outputDimension, final int byteOrder) { this(outputDimension, byteOrder, false); } /** * Creates a writer that writes {@link Geometry}s with * the given dimension (2 or 3) for output coordinates * and byte order. This constructor also takes a flag to * control whether srid information will be written. * * @param outputDimension the coordinate dimension to output (2 or 3) * @param byteOrder the byte ordering to use * @param includeSRID indicates whether SRID should be written */ public WKBWriter(final int outputDimension, final int byteOrder, final boolean includeSRID) { this.outputDimension = outputDimension; this.byteOrder = byteOrder; this.includeSRID = includeSRID; if (outputDimension < 2 || outputDimension > 3) { throw new IllegalArgumentException("Output dimension must be 2 or 3"); } } /** * Writes a {@link Geometry} into a byte array. * * @param geom the geometry to write * @return the byte array containing the WKB */ public byte[] write(final Geometry geom) { try { this.byteArrayOS.reset(); write(geom, this.byteArrayOutStream); } catch (final IOException ex) { throw new RuntimeException("Unexpected IO exception: " + ex.getMessage()); } return this.byteArrayOS.toByteArray(); } /** * Writes a {@link Geometry} to an {@link OutStream}. * * @param geom the geometry to write * @param os the out stream to write to * @throws IOException if an I/O error occurs */ public void write(final Geometry geom, final OutStream os) throws IOException { if (geom instanceof Point) { writePoint((Point)geom, os); } else if (geom instanceof LineString) { writeLineString((LineString)geom, os); } else if (geom instanceof Polygon) { writePolygon((Polygon)geom, os); } else if (geom instanceof Punctual) { writeGeometryCollection(WKBConstants.wkbMultiPoint, geom, os); } else if (geom instanceof Lineal) { writeGeometryCollection(WKBConstants.wkbMultiLineString, geom, os); } else if (geom instanceof Polygonal) { writeGeometryCollection(WKBConstants.wkbMultiPolygon, geom, os); } else if (geom.isGeometryCollection()) { writeGeometryCollection(WKBConstants.wkbGeometryCollection, geom, os); } else { Assert.shouldNeverReachHere("Unknown Geometry type"); } } private void writeByteOrder(final OutStream os) throws IOException { if (this.byteOrder == ByteOrderValues.LITTLE_ENDIAN) { this.buf[0] = WKBConstants.wkbNDR; } else { this.buf[0] = WKBConstants.wkbXDR; } os.write(this.buf, 1); } private void writeCoordinate(final LineString seq, final int index, final OutStream os) throws IOException { ByteOrderValues.putDouble(seq.getX(index), this.buf, this.byteOrder); os.write(this.buf, 8); ByteOrderValues.putDouble(seq.getY(index), this.buf, this.byteOrder); os.write(this.buf, 8); // only write 3rd dim if caller has requested it for this writer if (this.outputDimension >= 3) { // if 3rd dim is requested, only write it if the LineString provides // it double coordinate = Double.NaN; if (seq.getAxisCount() >= 3) { coordinate = seq.getCoordinate(index, 2); } ByteOrderValues.putDouble(coordinate, this.buf, this.byteOrder); os.write(this.buf, 8); } } private void writeCoordinates(final Point seq, final boolean writeSize, final OutStream os) throws IOException { ByteOrderValues.putDouble(seq.getX(), this.buf, this.byteOrder); os.write(this.buf, 8); ByteOrderValues.putDouble(seq.getY(), this.buf, this.byteOrder); os.write(this.buf, 8); // only write 3rd dim if caller has requested it for this writer if (this.outputDimension >= 3) { // if 3rd dim is requested, only write it if the LineString provides // it double coordinate = Double.NaN; if (seq.getAxisCount() >= 3) { coordinate = seq.getCoordinate(2); } ByteOrderValues.putDouble(coordinate, this.buf, this.byteOrder); os.write(this.buf, 8); } } private void writeCoordinateSequence(final LineString seq, final boolean writeSize, final OutStream os) throws IOException { int vertexCount; if (seq == null) { vertexCount = 0; } else { vertexCount = seq.getVertexCount(); } if (writeSize) { writeInt(vertexCount, os); } for (int i = 0; i < vertexCount; i++) { writeCoordinate(seq, i, os); } } private void writeGeometryCollection(final int geometryType, final Geometry geometry, final OutStream os) throws IOException { writeByteOrder(os); writeGeometryType(geometryType, geometry, os); writeInt(geometry.getGeometryCount(), os); for (int i = 0; i < geometry.getGeometryCount(); i++) { write(geometry.getGeometry(i), os); } } private void writeGeometryType(final int geometryType, final Geometry g, final OutStream os) throws IOException { final int flag3D = this.outputDimension == 3 ? 0x80000000 : 0; int typeInt = geometryType | flag3D; typeInt |= this.includeSRID ? 0x20000000 : 0; writeInt(typeInt, os); if (this.includeSRID) { writeInt(g.getCoordinateSystemId(), os); } } private void writeInt(final int intValue, final OutStream os) throws IOException { ByteOrderValues.putInt(intValue, this.buf, this.byteOrder); os.write(this.buf, 4); } private void writeLineString(final LineString line, final OutStream os) throws IOException { writeByteOrder(os); writeGeometryType(WKBConstants.wkbLineString, line, os); writeCoordinateSequence(line, true, os); } private void writePoint(final Point point, final OutStream os) throws IOException { if (point.isEmpty()) { writeGeometryCollection(WKBConstants.wkbMultiPoint, point, os); } else { writeByteOrder(os); writeGeometryType(WKBConstants.wkbPoint, point, os); writeCoordinates(point, false, os); } } private void writePolygon(final Polygon poly, final OutStream os) throws IOException { writeByteOrder(os); writeGeometryType(WKBConstants.wkbPolygon, poly, os); if (poly.isEmpty()) { writeInt(0, os); } else { writeInt(poly.getRingCount(), os); writeCoordinateSequence(poly.getShell(), true, os); for (int i = 0; i < poly.getHoleCount(); i++) { writeCoordinateSequence(poly.getHole(i), true, os); } } } }