/*
* $Id: WKBWriter.java,v 1.1 2007-02-27 12:45:29 eugen Exp $
*
* Copyright (C) 2002 by Brockmann Consult (info@brockmann-consult.de)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation. This program is distributed in the hope it will
* be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package com.bc.util.geom;
import java.awt.Shape;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
/**
* Writes a {@link Geometry} into Well-Known Binary format.
* <p/>
* The WKB format is specified in the OGC Simple Features for SQL specification.
* <p/>
* Empty Points cannot be represented in WKB; an
* {@link IllegalArgumentException} will be thrown if one is written.
* <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.
*
* @see WKBReader
*/
public class WKBWriter {
private ByteArrayOutputStream byteArrayOS;
// holds output data values
private ByteBuffer bBuffer;
/**
* Creates a writer that writes {@link Geometry}s in BIG_ENDIAN byte order
*/
public WKBWriter() {
this(ByteOrder.BIG_ENDIAN);
}
/**
* Creates a writer that writes {@link Geometry}s with the given byte order
*
* @param byteOrder the byte ordering to use
*/
public WKBWriter(ByteOrder byteOrder) {
bBuffer = ByteBuffer.allocate(2 * 8);
bBuffer.order(byteOrder);
byteArrayOS = new ByteArrayOutputStream();
}
/**
* Writes a {@link Geometry} into a byte array.
*
* @param geom the geometry to write
*
* @return the byte array containing the WKB
*/
public byte[] write(Geometry geom) {
try {
byteArrayOS.reset();
write(geom, byteArrayOS);
} catch (IOException ex) {
throw new RuntimeException("Unexpected IO exception: "
+ ex.getMessage());
}
return byteArrayOS.toByteArray();
}
/**
* Writes a {@link Geometry} to an {@link OutputStream}.
*
* @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(Geometry geom, OutputStream os) throws IOException {
if (geom instanceof PointGeometry) {
writePoint((PointGeometry) geom, os);
} else if (geom instanceof LineStringGeometry) {
writeLineString((LineStringGeometry) geom, os);
} else if (geom instanceof PolygonGeometry) {
writePolygon((PolygonGeometry) geom, os);
} else if (geom instanceof MultiPointGeometry) {
writeMultiPoint((MultiPointGeometry) geom, os);
} else if (geom instanceof MultiLineStringGeometry) {
writeMultiLineString((MultiLineStringGeometry) geom, os);
} else if (geom instanceof MultiPolygonGeometry) {
writeMultiPolygon((MultiPolygonGeometry) geom, os);
}
// else if (geom instanceof GeometryCollection)
// writeGeometryCollection(WKBConstants.GEOMETRYCOLLECTION,
// (GeometryCollection) geom, os);
else {
// Assert.shouldNeverReachHere("Unknown Geometry type");
}
}
private void writePoint(PointGeometry pt, OutputStream os) throws IOException {
writeByteOrder(os);
writeGeometryType(WKBConstants.POINT, os);
writePoint2D(pt.getPoint(), os);
}
private void writeLineString(LineStringGeometry line, OutputStream os)
throws IOException {
writeByteOrder(os);
writeGeometryType(WKBConstants.LINESTRING, os);
List<List<Point2D>> pointListList = extractPoints(line.getAsShape());
if (pointListList.size() > 1) {
throw new IllegalStateException(
"LineString must only contain one startingpoint");
}
List<Point2D> pointList = pointListList.get(0);
writeInt(pointList.size(), os);
writePointList(pointList, os);
}
private void writePolygon(PolygonGeometry poly, OutputStream os)
throws IOException {
writeByteOrder(os);
writeGeometryType(WKBConstants.POLYGON, os);
List<List<Point2D>> pointListList = extractPoints(poly.getAsShape());
writeInt(pointListList.size(), os);
for (List<Point2D> pointList : pointListList) {
writeInt(pointList.size(), os);
writePointList(pointList, os);
}
}
private void writeMultiPoint(MultiPointGeometry multiPointGeometry,
OutputStream os) throws IOException {
writeByteOrder(os);
writeGeometryType(WKBConstants.MULTIPOINT, os);
writeInt(multiPointGeometry.getPointCount(), os);
for (int i = 0; i < multiPointGeometry.getPointCount(); i++) {
writePoint(multiPointGeometry.getPoint(i), os);
}
}
private void writeMultiLineString(
MultiLineStringGeometry multiLineStringGeometry, OutputStream os)
throws IOException {
writeByteOrder(os);
writeGeometryType(WKBConstants.MULTILINESTRING, os);
writeInt(multiLineStringGeometry.getLineStringCount(), os);
for (int i = 0; i < multiLineStringGeometry.getLineStringCount(); i++) {
writeLineString(multiLineStringGeometry.getLineString(i), os);
}
}
private void writeMultiPolygon(MultiPolygonGeometry multiPolygonGeometry,
OutputStream os) throws IOException {
writeByteOrder(os);
writeGeometryType(WKBConstants.MULTIPOLYGON, os);
writeInt(multiPolygonGeometry.getPolygonCount(), os);
for (int i = 0; i < multiPolygonGeometry.getPolygonCount(); i++) {
writePolygon(multiPolygonGeometry.getPolygon(i), os);
}
}
// private void writeGeometryCollection(int geometryType,
// GeometryCollection gc, OutStream os) throws IOException {
// writeByteOrder(os);
// writeGeometryType(geometryType, os);
// writeInt(gc.getGeometryCount(), os);
// for (int i = 0; i < gc.getGeometryCount(); i++) {
// write(gc.getGeometry(i), os);
// }
// }
private void writeByteOrder(OutputStream os) throws IOException {
int byteOderValue;
if (bBuffer.order() == ByteOrder.LITTLE_ENDIAN) {
byteOderValue = WKBConstants.NDR;
} else {
byteOderValue = WKBConstants.XDR;
}
os.write(byteOderValue);
}
private void writeGeometryType(int geometryType, OutputStream os)
throws IOException {
writeInt(geometryType, os);
}
private void writeInt(int intValue, OutputStream os) throws IOException {
bBuffer.rewind();
bBuffer.putInt(intValue);
os.write(bBuffer.array(), 0, 4);
}
private List<List<Point2D>> extractPoints(Shape shape) {
float[] coords1 = new float[6];
float x0 = 0, y0 = 0;
float xi = 0, yi = 0;
List<Point2D> listOfPoints = new ArrayList<Point2D>();
List<List<Point2D>> listOfListOfPoints = new ArrayList<List<Point2D>>();
final PathIterator pathIterator = shape.getPathIterator(null);
int openCount = 0;
while (!pathIterator.isDone()) {
final int type = pathIterator.currentSegment(coords1);
if (type == PathIterator.SEG_MOVETO) {
x0 = coords1[0];
y0 = coords1[1];
if (openCount > 0) {
listOfListOfPoints.add(listOfPoints);
listOfPoints = new ArrayList<Point2D>();
openCount--;
}
listOfPoints.add(new Point2D.Float(x0, y0));
openCount++;
} else if (type == PathIterator.SEG_LINETO) {
xi = coords1[0];
yi = coords1[1];
listOfPoints.add(new Point2D.Float(xi, yi));
} else if (type == PathIterator.SEG_CLOSE) {
openCount--;
if (x0 != xi || y0 != yi) {
listOfPoints.add(new Point2D.Float(x0, y0));
}
listOfListOfPoints.add(listOfPoints);
listOfPoints = new ArrayList<Point2D>();
} else {
throw new IllegalStateException("unexpected segment type: "
+ type);
}
pathIterator.next();
}
if (openCount > 0 && listOfPoints.size() != 0) {
listOfListOfPoints.add(listOfPoints);
openCount--;
}
if (openCount != 0) {
throw new IllegalStateException("strange path");
}
return listOfListOfPoints;
}
private void writePointList(List<Point2D> pointlist, OutputStream os)
throws IOException {
for (Point2D point2D : pointlist) {
writePoint2D(point2D, os);
}
}
private void writePoint2D(Point2D point, OutputStream os) throws IOException {
bBuffer.rewind();
bBuffer.putDouble(point.getX());
bBuffer.putDouble(point.getY());
os.write(bBuffer.array(), 0, 16);
}
}