/*
|
* JtsBinaryWriter.java
|
*
|
* PostGIS extension for PostgreSQL JDBC driver - Binary Writer
|
*
|
* (C) 2005 Markus Schaber, markus.schaber@logix-tt.com
|
*
|
* 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.
|
*
|
* 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 or visit the web at
|
* http://www.gnu.org.
|
*
|
* $Id$
|
*/
|
package com.ximple.eofms.util.postjts;
|
|
import org.postgis.binary.ByteSetter;
|
import org.postgis.binary.ValueSetter;
|
|
import com.vividsolutions.jts.geom.CoordinateSequence;
|
import com.vividsolutions.jts.geom.Geometry;
|
import com.vividsolutions.jts.geom.GeometryCollection;
|
import com.vividsolutions.jts.geom.LineString;
|
import com.vividsolutions.jts.geom.MultiLineString;
|
import com.vividsolutions.jts.geom.MultiPoint;
|
import com.vividsolutions.jts.geom.MultiPolygon;
|
import com.vividsolutions.jts.geom.Point;
|
import com.vividsolutions.jts.geom.Polygon;
|
|
/**
|
* Create binary representation of geometries. Currently, only text rep (hexed)
|
* implementation is tested. Supports only 2 dimensional geometries.
|
* <p/>
|
* It should be easy to add char[] and CharSequence ByteGetter instances,
|
* although the latter one is not compatible with older jdks.
|
* <p/>
|
* I did not implement real unsigned 32-bit integers or emulate them with long,
|
* as both java Arrays and Strings currently can have only 2^31-1 elements
|
* (bytes), so we cannot even get or build Geometries with more than approx.
|
* 2^28 coordinates (8 bytes each).
|
*
|
* @author markus.schaber@logi-track.com
|
*/
|
public class JtsBinaryWriter
|
{
|
|
/**
|
* Get the appropriate ValueGetter for my endianness
|
*
|
* @param bytes The appropriate Byte Getter
|
* @return the ValueGetter
|
*/
|
public static ValueSetter valueSetterForEndian(ByteSetter bytes, byte endian)
|
{
|
if (endian == ValueSetter.XDR.NUMBER)
|
{ // XDR
|
return new ValueSetter.XDR(bytes);
|
} else if (endian == ValueSetter.NDR.NUMBER)
|
{
|
return new ValueSetter.NDR(bytes);
|
} else
|
{
|
throw new IllegalArgumentException("Unknown Endian type:" + endian);
|
}
|
}
|
|
/**
|
* Write a hex encoded geometry
|
* <p/>
|
* Currently, geometries with more than 2 dimensions and measures are not
|
* cleanly supported, but SRID is honored.
|
*/
|
public String writeHexed(Geometry geom, byte REP)
|
{
|
int length = estimateBytes(geom);
|
ByteSetter.StringByteSetter bytes = new ByteSetter.StringByteSetter(length);
|
writeGeometry(geom, valueSetterForEndian(bytes, REP));
|
return bytes.result();
|
}
|
|
public String writeHexed(Geometry geom)
|
{
|
return writeHexed(geom, ValueSetter.NDR.NUMBER);
|
}
|
|
/**
|
* Write a binary encoded geometry.
|
* <p/>
|
* Currently, geometries with more than 2 dimensions and measures are not
|
* cleanly supported, but SRID is honored.
|
*/
|
public byte[] writeBinary(Geometry geom, byte REP)
|
{
|
int length = estimateBytes(geom);
|
ByteSetter.BinaryByteSetter bytes = new ByteSetter.BinaryByteSetter(length);
|
writeGeometry(geom, valueSetterForEndian(bytes, REP));
|
return bytes.result();
|
}
|
|
public byte[] writeBinary(Geometry geom)
|
{
|
return writeBinary(geom, ValueSetter.NDR.NUMBER);
|
}
|
|
/**
|
* Parse a geometry starting at offset.
|
*/
|
protected void writeGeometry(Geometry geom, ValueSetter dest)
|
{
|
final int dimension;
|
if (geom == null)
|
{
|
throw new NullPointerException();
|
} else if (geom.isEmpty())
|
{
|
// don't set any flag bits
|
dimension = 0;
|
} else
|
{
|
dimension = getCoordDim(geom);
|
if (dimension < 2 || dimension > 4)
|
{
|
throw new IllegalArgumentException("Unsupported geometry dimensionality: " + dimension);
|
}
|
}
|
// write endian flag
|
dest.setByte(dest.endian);
|
|
// write typeword
|
final int plaintype = getWKBType(geom);
|
int typeword = plaintype;
|
if (dimension == 3 || dimension == 4)
|
{
|
typeword |= 0x80000000;
|
}
|
if (dimension == 4)
|
{
|
typeword |= 0x40000000;
|
}
|
|
final boolean haveSrid = checkSrid(geom);
|
if (haveSrid)
|
{
|
typeword |= 0x20000000;
|
}
|
|
dest.setInt(typeword);
|
|
if (haveSrid)
|
{
|
dest.setInt(geom.getSRID());
|
}
|
|
switch (plaintype)
|
{
|
case org.postgis.Geometry.POINT:
|
writePoint((Point) geom, dest);
|
break;
|
case org.postgis.Geometry.LINESTRING:
|
writeLineString((LineString) geom, dest);
|
break;
|
case org.postgis.Geometry.POLYGON:
|
writePolygon((Polygon) geom, dest);
|
break;
|
case org.postgis.Geometry.MULTIPOINT:
|
writeMultiPoint((MultiPoint) geom, dest);
|
break;
|
case org.postgis.Geometry.MULTILINESTRING:
|
writeMultiLineString((MultiLineString) geom, dest);
|
break;
|
case org.postgis.Geometry.MULTIPOLYGON:
|
writeMultiPolygon((MultiPolygon) geom, dest);
|
break;
|
case org.postgis.Geometry.GEOMETRYCOLLECTION:
|
writeCollection((GeometryCollection) geom, dest);
|
break;
|
default:
|
throw new IllegalArgumentException("Unknown Geometry Type: " + plaintype);
|
}
|
}
|
|
public static int getWKBType(Geometry geom)
|
{
|
// We always write emtpy geometries as emtpy collections - for OpenGIS
|
// conformance
|
if (geom.isEmpty())
|
{
|
return org.postgis.Geometry.GEOMETRYCOLLECTION;
|
} else if (geom instanceof Point)
|
{
|
return org.postgis.Geometry.POINT;
|
} else if (geom instanceof com.vividsolutions.jts.geom.LineString)
|
{
|
return org.postgis.Geometry.LINESTRING;
|
} else if (geom instanceof com.vividsolutions.jts.geom.Polygon)
|
{
|
return org.postgis.Geometry.POLYGON;
|
} else if (geom instanceof MultiPoint)
|
{
|
return org.postgis.Geometry.MULTIPOINT;
|
} else if (geom instanceof MultiLineString)
|
{
|
return org.postgis.Geometry.MULTILINESTRING;
|
} else if (geom instanceof com.vividsolutions.jts.geom.MultiPolygon)
|
{
|
return org.postgis.Geometry.MULTIPOLYGON;
|
}
|
if (geom instanceof com.vividsolutions.jts.geom.GeometryCollection)
|
{
|
return org.postgis.Geometry.GEOMETRYCOLLECTION;
|
} else
|
{
|
throw new IllegalArgumentException("Unknown Geometry Type: " + geom.getClass().getName());
|
}
|
}
|
|
/**
|
* Writes a "slim" Point (without endiannes, srid ant type, only the
|
* ordinates and measure. Used by writeGeometry.
|
*/
|
private void writePoint(Point geom, ValueSetter dest)
|
{
|
writeCoordinates(geom.getCoordinateSequence(), getCoordDim(geom), dest);
|
}
|
|
/**
|
* Write a Coordinatesequence, part of LinearRing and Linestring, but not
|
* MultiPoint!
|
*/
|
private void writeCoordinates(CoordinateSequence seq, int dims, ValueSetter dest)
|
{
|
for (int i = 0; i < seq.size(); i++)
|
{
|
for (int d = 0; d < dims; d++)
|
{
|
dest.setDouble(seq.getOrdinate(i, d));
|
}
|
}
|
}
|
|
private void writeMultiPoint(MultiPoint geom, ValueSetter dest)
|
{
|
dest.setInt(geom.getNumPoints());
|
for (int i = 0; i < geom.getNumPoints(); i++)
|
{
|
writeGeometry(geom.getGeometryN(i), dest);
|
}
|
}
|
|
private void writeLineString(LineString geom, ValueSetter dest)
|
{
|
dest.setInt(geom.getNumPoints());
|
writeCoordinates(geom.getCoordinateSequence(), getCoordDim(geom), dest);
|
}
|
|
private void writePolygon(Polygon geom, ValueSetter dest)
|
{
|
dest.setInt(geom.getNumInteriorRing() + 1);
|
writeLineString(geom.getExteriorRing(), dest);
|
for (int i = 0; i < geom.getNumInteriorRing(); i++)
|
{
|
writeLineString(geom.getInteriorRingN(i), dest);
|
}
|
}
|
|
private void writeMultiLineString(MultiLineString geom, ValueSetter dest)
|
{
|
writeGeometryArray(geom, dest);
|
}
|
|
private void writeMultiPolygon(MultiPolygon geom, ValueSetter dest)
|
{
|
writeGeometryArray(geom, dest);
|
}
|
|
private void writeCollection(GeometryCollection geom, ValueSetter dest)
|
{
|
writeGeometryArray(geom, dest);
|
}
|
|
private void writeGeometryArray(Geometry geom, ValueSetter dest)
|
{
|
dest.setInt(geom.getNumGeometries());
|
for (int i = 0; i < geom.getNumGeometries(); i++)
|
{
|
writeGeometry(geom.getGeometryN(i), dest);
|
}
|
}
|
|
/**
|
* Estimate how much bytes a geometry will need in WKB.
|
*/
|
protected int estimateBytes(Geometry geom)
|
{
|
int result = 0;
|
|
// write endian flag
|
result += 1;
|
|
// write typeword
|
result += 4;
|
|
if (checkSrid(geom))
|
{
|
result += 4;
|
}
|
|
switch (getWKBType(geom))
|
{
|
case org.postgis.Geometry.POINT:
|
result += estimatePoint((Point) geom);
|
break;
|
case org.postgis.Geometry.LINESTRING:
|
result += estimateLineString((LineString) geom);
|
break;
|
case org.postgis.Geometry.POLYGON:
|
result += estimatePolygon((Polygon) geom);
|
break;
|
case org.postgis.Geometry.MULTIPOINT:
|
result += estimateMultiPoint((MultiPoint) geom);
|
break;
|
case org.postgis.Geometry.MULTILINESTRING:
|
result += estimateMultiLineString((MultiLineString) geom);
|
break;
|
case org.postgis.Geometry.MULTIPOLYGON:
|
result += estimateMultiPolygon((MultiPolygon) geom);
|
break;
|
case org.postgis.Geometry.GEOMETRYCOLLECTION:
|
result += estimateCollection((GeometryCollection) geom);
|
break;
|
default:
|
throw new IllegalArgumentException("Unknown Geometry Type: " + getWKBType(geom));
|
}
|
return result;
|
}
|
|
private boolean checkSrid(Geometry geom)
|
{
|
final int srid = geom.getSRID();
|
// SRID is default 0 with jts geometries
|
return (srid != -1) && (srid != 0);
|
}
|
|
private int estimatePoint(Point geom)
|
{
|
return 8 * getCoordDim(geom);
|
}
|
|
/**
|
* Write an Array of "full" Geometries
|
*/
|
private int estimateGeometryArray(Geometry container)
|
{
|
int result = 0;
|
for (int i = 0; i < container.getNumGeometries(); i++)
|
{
|
result += estimateBytes(container.getGeometryN(i));
|
}
|
return result;
|
}
|
|
/**
|
* Estimate an array of "fat" Points
|
*/
|
private int estimateMultiPoint(MultiPoint geom)
|
{
|
// int size
|
int result = 4;
|
if (geom.getNumGeometries() > 0)
|
{
|
// We can shortcut here, compared to estimateGeometryArray, as all
|
// subgeoms have the same fixed size
|
result += geom.getNumGeometries() * estimateBytes(geom.getGeometryN(0));
|
}
|
return result;
|
}
|
|
private int estimateLineString(LineString geom)
|
{
|
if (geom == null || geom.getNumGeometries() == 0)
|
{
|
return 0;
|
} else
|
{
|
return 4 + 8 * getCoordSequenceDim(geom.getCoordinateSequence()) * geom.getCoordinateSequence().size();
|
}
|
}
|
|
private int estimatePolygon(Polygon geom)
|
{
|
// int length
|
int result = 4;
|
result += estimateLineString(geom.getExteriorRing());
|
for (int i = 0; i < geom.getNumInteriorRing(); i++)
|
{
|
result += estimateLineString(geom.getInteriorRingN(i));
|
}
|
return result;
|
}
|
|
private int estimateMultiLineString(MultiLineString geom)
|
{
|
// 4-byte count + subgeometries
|
return 4 + estimateGeometryArray(geom);
|
}
|
|
private int estimateMultiPolygon(MultiPolygon geom)
|
{
|
// 4-byte count + subgeometries
|
return 4 + estimateGeometryArray(geom);
|
}
|
|
private int estimateCollection(GeometryCollection geom)
|
{
|
// 4-byte count + subgeometries
|
return 4 + estimateGeometryArray(geom);
|
}
|
|
public static final int getCoordDim(Geometry geom)
|
{
|
if (geom.isEmpty())
|
{
|
return 0;
|
}
|
if (geom instanceof Point)
|
{
|
return getCoordSequenceDim(((Point) geom).getCoordinateSequence());
|
} else if (geom instanceof LineString)
|
{
|
return getCoordSequenceDim(((LineString) geom).getCoordinateSequence());
|
} else if (geom instanceof Polygon)
|
{
|
return getCoordSequenceDim(((Polygon) geom).getExteriorRing().getCoordinateSequence());
|
} else
|
{
|
return getCoordDim(geom.getGeometryN(0));
|
}
|
}
|
|
public static final int getCoordSequenceDim(CoordinateSequence coords)
|
{
|
if (coords == null || coords.size() == 0)
|
return 0;
|
// JTS has a really strange way to handle dimensions!
|
// Just have a look at PackedCoordinateSequence and
|
// CoordinateArraySequence
|
int dimensions = coords.getDimension();
|
if (dimensions == 3)
|
{
|
// CoordinateArraySequence will always return 3, so we have to
|
// check, if
|
// the third ordinate contains NaN, then the geom is actually
|
// 2-dimensional
|
return Double.isNaN(coords.getOrdinate(0, CoordinateSequence.Z)) ? 2 : 3;
|
} else
|
{
|
return dimensions;
|
}
|
}
|
}
|