/* * Copyright 2013 Hannes Janetzek * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * * 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 <http://www.gnu.org/licenses/>. */ package org.oscim.utils.wkb; import org.oscim.core.GeometryBuffer; import org.oscim.core.Tile; public class WKBReader { public interface Callback { public void process(GeometryBuffer geom); } private final GeometryBuffer mGeom; private final boolean mFlipY; private WKBReader.Callback mCallback; public WKBReader(GeometryBuffer geom, boolean flipY) { mGeom = geom; mFlipY = flipY; } public void setCallback(WKBReader.Callback cb) { mCallback = cb; } /** * Parse a binary encoded geometry. */ public void parse(byte[] value) { parseGeometry(valueGetterForEndian(value), 0); } /** * Parse a hex encoded geometry. */ public void parse(String value) { byte[] b = hexStringToByteArray(value); if (b == null) return; parse(b); } private void 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; 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); break; case Geometry.MULTIPOINT: mGeom.startPoints(); parseMultiPoint(data); break; case Geometry.MULTILINESTRING: mGeom.startLine(); parseMultiLineString(data); break; case Geometry.MULTIPOLYGON: mGeom.startPolygon(); parseMultiPolygon(data); 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); // } } private void parsePoint(ValueGetter data, boolean haveZ, boolean haveM) { float x = (float) data.getDouble(); float y = (float) data.getDouble(); if (mFlipY) y = Tile.SIZE - y; 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, int type) { mGeom.clear(); for (int i = 0; i < count; i++) { if (i > 0) { if (type == Geometry.LINESTRING) mGeom.startLine(); else if (type == Geometry.POLYGON) mGeom.startPolygon(); else { mCallback.process(mGeom); mGeom.clear(); } } parseGeometry(data, count); // mGeom.index[++mGeom.indexPos] = -1; } mCallback.process(mGeom); mGeom.clear(); } private void parseMultiPoint(ValueGetter data) { parseGeometryArray(data, data.getInt(), Geometry.POINT); } 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(); float y = (float) data.getDouble(); if (mFlipY) y = Tile.SIZE - y; 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(); int points = data.getInt(); for (int j = 0; j < points; j++) { float x = (float) data.getDouble(); float y = (float) data.getDouble(); if (mFlipY) y = Tile.SIZE - y; // drop redundant closing point if (j < points - 1) mGeom.addPoint(x, y); // ignore if (haveZ) data.getDouble(); if (haveM) data.getDouble(); } } } private void parseMultiLineString(ValueGetter data) { int count = data.getInt(); if (count <= 0) return; parseGeometryArray(data, count, Geometry.LINESTRING); } private void parseMultiPolygon(ValueGetter data) { int count = data.getInt(); if (count <= 0) return; parseGeometryArray(data, count, Geometry.POLYGON); } private void parseCollection(ValueGetter data) { int count = data.getInt(); parseGeometryArray(data, count, Geometry.GEOMETRYCOLLECTION); 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]); } } /** * Converting a string of hex character to bytes * * from http://stackoverflow.com/questions/140131/convert-a-string- * representation-of-a-hex-dump-to-a-byte-array-using-java */ public static byte[] hexStringToByteArray(String s) { int len = s.length(); if (len < 2) return null; byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); } return data; } }