diff --git a/src/org/oscim/utils/wkb/Geometry.java b/src/org/oscim/utils/wkb/Geometry.java new file mode 100644 index 00000000..2833c197 --- /dev/null +++ b/src/org/oscim/utils/wkb/Geometry.java @@ -0,0 +1,384 @@ +/* + * Geometry.java + * + * PostGIS extension for PostgreSQL JDBC driver - geometry model + * + * (C) 2004 Paul Ramsey, pramsey@refractions.net + * + * (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 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 License for more + * details. + * + * You should have received a copy of the GNU Lesser General 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: Geometry.java 9324 2012-02-27 22:08:12Z pramsey $ + */ + +package org.oscim.utils.wkb; + +import java.io.Serializable; + +/** The base class of all geometries */ +abstract class Geometry implements Serializable { + /* JDK 1.5 Serialization */ + private static final long serialVersionUID = 0x100; + + // OpenGIS Geometry types as defined in the OGC WKB Spec + // (May we replace this with an ENUM as soon as JDK 1.5 + // has gained widespread usage?) + + /** Fake type for linear ring */ + static final int LINEARRING = 0; + /** + * The OGIS geometry type number for points. + */ + static final int POINT = 1; + + /** + * The OGIS geometry type number for lines. + */ + static final int LINESTRING = 2; + + /** + * The OGIS geometry type number for polygons. + */ + static final int POLYGON = 3; + + /** + * The OGIS geometry type number for aggregate points. + */ + static final int MULTIPOINT = 4; + + /** + * The OGIS geometry type number for aggregate lines. + */ + static final int MULTILINESTRING = 5; + + /** + * The OGIS geometry type number for aggregate polygons. + */ + static final int MULTIPOLYGON = 6; + + /** + * The OGIS geometry type number for feature collections. + */ + static final int GEOMETRYCOLLECTION = 7; + + static final String[] ALLTYPES = new String[] { + "", // internally used LinearRing does not have any text in front of + // it + "POINT", "LINESTRING", "POLYGON", "MULTIPOINT", "MULTILINESTRING", + "MULTIPOLYGON", "GEOMETRYCOLLECTION" }; + + /** + * The Text representations of the geometry types + * + * @param type + * ... + * @return ... + */ + static String getTypeString(int type) { + if (type >= 0 && type <= 7) + return ALLTYPES[type]; + + throw new IllegalArgumentException("Unknown Geometry type" + type); + + } + + // Properties common to all geometries + /** + * The dimensionality of this feature (2,3) + */ + int dimension; + + /** + * Do we have a measure (4th dimension) + */ + boolean haveMeasure = false; + + /** + * The OGIS geometry type of this feature. this is final as it never + * changes, it is bound to the subclass of the + * instance. + */ + final int type; + + /** + * Official UNKNOWN srid value + */ + final static int UNKNOWN_SRID = 0; + + /** + * The spacial reference system id of this geometry, default is no srid + */ + int srid = UNKNOWN_SRID; + + /** + * Parse a SRID value, anything <= 0 is unknown + * + * @param srid + * ... + * @return ... + */ + static int parseSRID(int srid) { + if (srid < 0) { + /* TODO: raise a warning ? */ + return 0; + } + return srid; + } + + /** + * Constructor for subclasses + * + * @param type + * has to be given by all subclasses. + */ + protected Geometry(int type) { + this.type = type; + } + + /** + * java.lang.Object hashCode implementation + */ + @Override + public int hashCode() { + return dimension | (type * 4) | (srid * 32); + } + + /** + * java.lang.Object equals implementation + */ + @Override + public boolean equals(Object other) { + return (other != null) && (other instanceof Geometry) + && equals((Geometry) other); + } + + /** + * geometry specific equals implementation - only defined for non-null + * values + * + * @param other + * ... + * @return ... + */ + public boolean equals(Geometry other) { + return (other != null) && (this.dimension == other.dimension) + && (this.type == other.type) && (this.srid == other.srid) + && (this.haveMeasure == other.haveMeasure) + && other.getClass().equals(this.getClass()) + && this.equalsintern(other); + } + + /** + * Whether test coordinates for geometry - subclass specific code + * Implementors can assume that dimensin, type, srid + * and haveMeasure are equal, other != null and other is the same subclass. + * + * @param other + * ... + * @return ... + */ + protected abstract boolean equalsintern(Geometry other); + + /** + * Return the number of Points of the geometry + * + * @return ... + */ + abstract int numPoints(); + + /** + * Get the nth Point of the geometry + * + * @param n + * the index of the point, from 0 to numPoints()-1; + * @throws ArrayIndexOutOfBoundsException + * in case of an emtpy geometry or bad index. + */ + // abstract Point getPoint(int n); + + // + // /** + // * Same as getPoint(0); + // */ + // abstract Point getFirstPoint(); + // + // /** + // * Same as getPoint(numPoints()-1); + // */ + // abstract Point getLastPoint(); + + /** + * The OGIS geometry type number of this geometry. + * + * @return ... + */ + int getType() { + return this.type; + } + + /** + * Return the Type as String + * + * @return ... + */ + String getTypeString() { + return getTypeString(this.type); + } + + /** + * Returns whether we have a measure + * + * @return .... + */ + boolean isMeasured() { + return haveMeasure; + } + + /** + * Queries the number of geometric dimensions of this geometry. This does + * not include measures, as opposed to the + * server. + * + * @return The dimensionality (eg, 2D or 3D) of this geometry. + */ + int getDimension() { + return this.dimension; + } + + /** + * The OGIS geometry type number of this geometry. + * + * @return ... + */ + int getSrid() { + return this.srid; + } + + /** + * Recursively sets the srid on this geometry and all contained + * subgeometries + * + * @param srid + * ... + */ + void setSrid(int srid) { + this.srid = srid; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + if (srid != UNKNOWN_SRID) { + sb.append("SRID="); + sb.append(srid); + sb.append(';'); + } + outerWKT(sb, true); + return sb.toString(); + } + + /** + * Render the WKT version of this Geometry (without SRID) into the given + * StringBuffer. + * + * @param sb + * ... + * @param putM + * ... + */ + void outerWKT(StringBuffer sb, boolean putM) { + sb.append(getTypeString()); + if (putM && haveMeasure && dimension == 2) { + sb.append('M'); + } + mediumWKT(sb); + } + + final void outerWKT(StringBuffer sb) { + outerWKT(sb, true); + } + + /** + * Render the WKT without the type name, but including the brackets into the + * StringBuffer + * + * @param sb + * ... + */ + protected void mediumWKT(StringBuffer sb) { + sb.append('('); + innerWKT(sb); + sb.append(')'); + } + + /** + * Render the "inner" part of the WKT (inside the brackets) into the + * StringBuffer. + * + * @param SB + * ... + */ + protected abstract void innerWKT(StringBuffer SB); + + /** + * backwards compatibility method + * + * @return ... + */ + String getValue() { + StringBuffer sb = new StringBuffer(); + mediumWKT(sb); + return sb.toString(); + } + + /** + * Do some internal consistency checks on the geometry. Currently, all + * Geometries must have a valid dimension (2 or + * 3) and a valid type. 2-dimensional Points must have Z=0.0, as well as + * non-measured Points must have m=0.0. + * Composed geometries must have all equal SRID, dimensionality and + * measures, as well as that they do not contain + * NULL or inconsistent subgeometries. BinaryParser and WKTParser should + * only generate consistent geometries. + * BinaryWriter may produce invalid results on inconsistent geometries. + * + * @return true if all checks are passed. + */ + boolean checkConsistency() { + return (dimension >= 2 && dimension <= 3) && (type >= 0 && type <= 7); + } + + /** + * Splits the SRID=4711; part of a EWKT rep if present and sets the srid. + * + * @param value + * ... + * @return value without the SRID=4711; part + */ + protected String initSRID(String value) { + String v = value.trim(); + if (v.startsWith("SRID=")) { + int index = v.indexOf(';', 5); // sridprefix length is 5 + if (index == -1) { + throw new IllegalArgumentException( + "Error parsing Geometry - SRID not delimited with ';' "); + } + this.srid = Integer.parseInt(v.substring(5, index)); + return v.substring(index + 1).trim(); + } + return v; + } +} diff --git a/src/org/oscim/utils/wkb/ValueGetter.java b/src/org/oscim/utils/wkb/ValueGetter.java new file mode 100644 index 00000000..bc45cd8c --- /dev/null +++ b/src/org/oscim/utils/wkb/ValueGetter.java @@ -0,0 +1,139 @@ +/* + * ValueGetter.java + * + * PostGIS extension for PostgreSQL JDBC driver - Binary Parser + * + * (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 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 License for more + * details. + * + * You should have received a copy of the GNU Lesser General 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: ValueGetter.java 9324 2012-02-27 22:08:12Z pramsey $ + */ + +package org.oscim.utils.wkb; + +abstract class ValueGetter { + byte[] data; + int position; + final byte endian; + + ValueGetter(byte[] data, byte endian) { + this.data = data; + + this.endian = endian; + } + + /** + * Get a byte, should be equal for all endians + * + * @return ... + */ + byte getByte() { + return data[position++]; + } + + int getInt() { + int res = getInt(position); + position += 4; + return res; + } + + long getLong() { + long res = getLong(position); + position += 8; + return res; + } + + /** + * Get a 32-Bit integer + * + * @param index + * ... + * @return ... + */ + protected abstract int getInt(int index); + + /** + * Get a long value. This is not needed directly, but as a nice side-effect + * from GetDouble. + * + * @param index + * ... + * @return ... + */ + protected abstract long getLong(int index); + + /** + * Get a double. + * + * @return ... + */ + double getDouble() { + long bitrep = getLong(); + return Double.longBitsToDouble(bitrep); + } + + static class XDR extends ValueGetter { + static final byte NUMBER = 0; + + XDR(byte[] data) { + super(data, NUMBER); + } + + @Override + protected int getInt(int index) { + return ((data[index] & 0xFF) << 24) + ((data[index + 1] & 0xFF) << 16) + + ((data[index + 2] & 0xFF) << 8) + (data[index + 3] & 0xFF); + } + + @Override + protected long getLong(int index) { + + return ((long) (data[index] & 0xFF) << 56) | ((long) (data[index + 1] & 0xFF) << 48) + | ((long) (data[index + 2] & 0xFF) << 40) + | ((long) (data[index + 3] & 0xFF) << 32) + | ((long) (data[index + 4] & 0xFF) << 24) + | ((long) (data[index + 5] & 0xFF) << 16) + | ((long) (data[index + 6] & 0xFF) << 8) + | ((long) (data[index + 7] & 0xFF) << 0); + } + } + + static class NDR extends ValueGetter { + static final byte NUMBER = 1; + + NDR(byte[] data) { + super(data, NUMBER); + } + + @Override + protected int getInt(int index) { + return ((data[index + 3] & 0xFF) << 24) + ((data[index + 2] & 0xFF) << 16) + + ((data[index + 1] & 0xFF) << 8) + (data[index] & 0xFF); + } + + @Override + protected long getLong(int index) { + return ((long) (data[index + 7] & 0xFF) << 56) + | ((long) (data[index + 6] & 0xFF) << 48) + | ((long) (data[index + 5] & 0xFF) << 40) + | ((long) (data[index + 4] & 0xFF) << 32) + | ((long) (data[index + 3] & 0xFF) << 24) + | ((long) (data[index + 2] & 0xFF) << 16) + | ((long) (data[index + 1] & 0xFF) << 8) | ((long) (data[index] & 0xFF) << 0); + + } + } +} diff --git a/src/org/oscim/utils/wkb/WKBReader.java b/src/org/oscim/utils/wkb/WKBReader.java new file mode 100644 index 00000000..5235fe9d --- /dev/null +++ b/src/org/oscim/utils/wkb/WKBReader.java @@ -0,0 +1,208 @@ +/* + * Copyright 2013 Hannes Janetzek + * + * This program 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 3 of the License, or (at your option) any later version. + * + * This program 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 program. If not, see . + */ +package org.oscim.utils.wkb; + +import org.oscim.core.GeometryBuffer; + +public class WKBReader { + interface Callback { + public void process(GeometryBuffer geom); + } + + // taken from postgis-java + + private GeometryBuffer mGeom; + private final double mScale = 1; + private final double mOffsetX = 0; + private final double mOffsetY = 0; + + private WKBReader.Callback mCallback; + + /** + * Parse a binary encoded geometry. + * + * @param value + * ... + * @return ... + */ + boolean parse(byte[] value) { + return parseGeometry(valueGetterForEndian(value), 0); + } + + private boolean parseGeometry(ValueGetter data, int count) { + byte endian = data.getByte(); // skip and test endian flag + if (endian != data.endian) { + throw new IllegalArgumentException("Endian inconsistency!"); + } + int typeword = data.getInt(); + + int realtype = typeword & 0x1FFFFFFF; // cut off high flag bits + + boolean haveZ = (typeword & 0x80000000) != 0; + boolean haveM = (typeword & 0x40000000) != 0; + boolean haveS = (typeword & 0x20000000) != 0; + + // int srid = Geometry.UNKNOWN_SRID; + boolean polygon = false; + if (haveS) { + // srid = Geometry.parseSRID(data.getInt()); + data.getInt(); + } + switch (realtype) { + case Geometry.POINT: + mGeom.startPoints(); + parsePoint(data, haveZ, haveM); + break; + case Geometry.LINESTRING: + mGeom.startLine(); + parseLineString(data, haveZ, haveM); + break; + case Geometry.POLYGON: + mGeom.startPolygon(); + parsePolygon(data, haveZ, haveM); + polygon = true; + break; + case Geometry.MULTIPOINT: + mGeom.startPoints(); + parseMultiPoint(data); + break; + case Geometry.MULTILINESTRING: + mGeom.startLine(); + parseMultiLineString(data); + break; + case Geometry.MULTIPOLYGON: + mGeom.startPolygon(); + parseMultiPolygon(data); + polygon = true; + break; + case Geometry.GEOMETRYCOLLECTION: + parseCollection(data); + break; + default: + throw new IllegalArgumentException("Unknown Geometry Type: " + realtype); + } + + if (count == 0) { + mCallback.process(mGeom); + mGeom.clear(); + } + // if (srid != Geometry.UNKNOWN_SRID) { + // result.setSrid(srid); + // } + return polygon; + } + + private void parsePoint(ValueGetter data, boolean haveZ, boolean haveM) { + + float x = (float) ((data.getDouble() + mOffsetX) * mScale); + float y = (float) ((data.getDouble() + mOffsetY) * mScale); + mGeom.addPoint(x, y); + + if (haveZ) + data.getDouble(); + + if (haveM) + data.getDouble(); + } + + /** + * Parse an Array of "full" Geometries + * + * @param data + * ... + * @param count + * ... + */ + private void parseGeometryArray(ValueGetter data, int count) { + mGeom.clear(); + + for (int i = 0; i < count; i++) { + parseGeometry(data, count); + mGeom.index[mGeom.indexPos++] = 0; + } + + mCallback.process(mGeom); + mGeom.clear(); + } + + private void parseMultiPoint(ValueGetter data) { + parseGeometryArray(data, data.getInt()); + } + + private void parseLineString(ValueGetter data, boolean haveZ, boolean haveM) { + + int count = data.getInt(); + + for (int i = 0; i < count; i++) { + float x = (float) ((data.getDouble() + mOffsetX) * mScale); + float y = (float) ((data.getDouble() + mOffsetY) * mScale); + mGeom.addPoint(x, y); + + // ignore + if (haveZ) + data.getDouble(); + if (haveM) + data.getDouble(); + } + } + + private void parsePolygon(ValueGetter data, boolean haveZ, boolean haveM) { + int count = data.getInt(); + + for (int i = 0; i < count; i++) { + + if (i > 0) + mGeom.startHole(); + + parseLineString(data, haveZ, haveM); + } + } + + private void parseMultiLineString(ValueGetter data) { + + int count = data.getInt(); + if (count <= 0) + return; + + parseGeometryArray(data, count); + } + + private void parseMultiPolygon(ValueGetter data) { + int count = data.getInt(); + if (count <= 0) + return; + + parseGeometryArray(data, count); + } + + private void parseCollection(ValueGetter data) { + int count = data.getInt(); + parseGeometryArray(data, count); + + mCallback.process(mGeom); + mGeom.clear(); + } + + private static ValueGetter valueGetterForEndian(byte[] bytes) { + if (bytes[0] == ValueGetter.XDR.NUMBER) { // XDR + return new ValueGetter.XDR(bytes); + } else if (bytes[0] == ValueGetter.NDR.NUMBER) { + return new ValueGetter.NDR(bytes); + } else { + throw new IllegalArgumentException("Unknown Endian type:" + bytes[0]); + } + } + +}