/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2015, Open Source Geospatial Foundation (OSGeo)
*
* 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;
* 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.
*/
package org.geotools.data.geobuf;
import com.vividsolutions.jts.geom.*;
import org.geotools.geometry.jts.JTSFactoryFinder;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* The GeobufGeometry class encodes and decodes geobuf geometries
*
* @author Jared Erickson
*/
public class GeobufGeometry {
private int precision;
private int dimension;
private double maxNumberOfDecimalPlaces;
private GeometryFactory geometryFactory;
public GeobufGeometry() {
this(6, 2, JTSFactoryFinder.getGeometryFactory(null));
}
public GeobufGeometry(int precision, int dimension) {
this(precision, dimension, JTSFactoryFinder.getGeometryFactory(null));
}
public GeobufGeometry(int precision, int dimension, GeometryFactory geometryFactory) {
this.precision = precision;
this.dimension = dimension;
this.maxNumberOfDecimalPlaces = Math.pow(10, precision);
this.geometryFactory = geometryFactory;
}
public int getDimension() {
return this.dimension;
}
public int getPrecision() {
return this.precision;
}
public void encode(Geometry geometry, OutputStream out) throws IOException {
Geobuf.Data.Builder dataBuilder = Geobuf.Data.newBuilder();
dataBuilder.setDimensions(dimension);
dataBuilder.setPrecision(precision);
Geobuf.Data.Geometry g = encode(geometry);
dataBuilder.setGeometry(g);
dataBuilder.build().writeTo(out);
}
public Geometry decode(InputStream in) throws IOException {
Geobuf.Data data = Geobuf.Data.parseFrom(in);
if (data.getDataTypeCase() != Geobuf.Data.DataTypeCase.GEOMETRY) {
throw new IllegalArgumentException("Geobuf data type is not Geometry!");
}
return decode(data.getGeometry());
}
protected Geobuf.Data.Geometry encode(Geometry geometry) {
Geobuf.Data.Geometry.Builder builder = Geobuf.Data.Geometry.newBuilder();
if (geometry instanceof Point) {
Point point = (Point) geometry;
builder.setType(Geobuf.Data.Geometry.Type.POINT);
addCoords(builder, new Coordinate[]{point.getCoordinate()});
} else if (geometry instanceof LineString) {
LineString line = (LineString) geometry;
builder.setType(Geobuf.Data.Geometry.Type.LINESTRING);
Coordinate[] coords = line.getCoordinates();
addCoords(builder, coords);
} else if (geometry instanceof Polygon) {
Polygon polygon = (Polygon) geometry;
builder.setType(Geobuf.Data.Geometry.Type.POLYGON);
Coordinate[] coords = polygon.getCoordinates();
addCoords(builder, coords);
} else if (geometry instanceof MultiPoint) {
MultiPoint multiPoint = (MultiPoint) geometry;
builder.setType(Geobuf.Data.Geometry.Type.MULTIPOINT);
Coordinate[] coords = multiPoint.getCoordinates();
addCoords(builder, coords);
} else if (geometry instanceof MultiLineString) {
MultiLineString multiLineString = (MultiLineString) geometry;
builder.setType(Geobuf.Data.Geometry.Type.MULTILINESTRING);
for (int i = 0; i < multiLineString.getNumGeometries(); i++) {
LineString line = (LineString) multiLineString.getGeometryN(i);
builder.addLengths(line.getCoordinates().length);
addCoords(builder, line.getCoordinates());
}
} else if (geometry instanceof MultiPolygon) {
MultiPolygon multiPolygon = (MultiPolygon) geometry;
builder.setType(Geobuf.Data.Geometry.Type.MULTIPOLYGON);
builder.addLengths(multiPolygon.getNumGeometries());
for (int i = 0; i < multiPolygon.getNumGeometries(); i++) {
Polygon polygon = (Polygon) multiPolygon.getGeometryN(i);
builder.addLengths(1 + polygon.getNumInteriorRing());
builder.addLengths(polygon.getExteriorRing().getCoordinates().length);
addCoords(builder, polygon.getExteriorRing().getCoordinates());
for (int j = 0; j < polygon.getNumInteriorRing(); j++) {
LinearRing ring = (LinearRing) polygon.getInteriorRingN(j);
builder.addLengths(ring.getCoordinates().length);
addCoords(builder, ring.getCoordinates());
}
}
} else if (geometry instanceof GeometryCollection) {
GeometryCollection geometryCollection = (GeometryCollection) geometry;
builder.setType(Geobuf.Data.Geometry.Type.GEOMETRYCOLLECTION);
for (int i = 0; i < geometryCollection.getNumGeometries(); i++) {
Geometry geom = geometryCollection.getGeometryN(i);
builder.addGeometries(encode(geom));
}
}
Geobuf.Data.Geometry geom = builder.build();
return geom;
}
private void addCoords(Geobuf.Data.Geometry.Builder builder, Coordinate[] coords) {
for (int i = 0; i < coords.length; i++) {
Coordinate coord = coords[i];
long x = Math.round(coord.x * maxNumberOfDecimalPlaces);
long y = Math.round(coord.y * maxNumberOfDecimalPlaces);
if (i > 0) {
Coordinate prevCoord = coords[i - 1];
x = x - Math.round(prevCoord.x * maxNumberOfDecimalPlaces);
y = y - Math.round(prevCoord.y * maxNumberOfDecimalPlaces);
}
builder.addCoords(x).addCoords(y);
}
}
protected Geometry decode(Geobuf.Data.Geometry g) {
if (g.getType() == Geobuf.Data.Geometry.Type.POINT) {
return geometryFactory.createPoint(getAllCoordinates(g)[0]);
} else if (g.getType() == Geobuf.Data.Geometry.Type.LINESTRING) {
return geometryFactory.createLineString(getAllCoordinates(g));
} else if (g.getType() == Geobuf.Data.Geometry.Type.POLYGON) {
Coordinate[] coords = getAllCoordinates(g);
if (!CoordinateArrays.isRing(coords)) {
Coordinate[] closedCoords = new Coordinate[coords.length + 1];
CoordinateArrays.copyDeep(coords, 0, closedCoords, 0, coords.length);
closedCoords[closedCoords.length - 1] = coords[0];
coords = closedCoords;
}
LinearRing shell = geometryFactory.createLinearRing(coords);
LinearRing[] holes = new LinearRing[0];
return geometryFactory.createPolygon(shell, holes);
} else if (g.getType() == Geobuf.Data.Geometry.Type.MULTIPOINT) {
Coordinate[] coords = getAllCoordinates(g);
return geometryFactory.createMultiPoint(coords);
} else if (g.getType() == Geobuf.Data.Geometry.Type.MULTILINESTRING) {
List<Coordinate[]> listOfCoordinates = getCoordinates(g);
LineString[] lines = new LineString[listOfCoordinates.size()];
for (int i = 0; i < listOfCoordinates.size(); i++) {
lines[i] = geometryFactory.createLineString(listOfCoordinates.get(i));
}
return geometryFactory.createMultiLineString(lines);
} else if (g.getType() == Geobuf.Data.Geometry.Type.MULTIPOLYGON) {
int lengthPosition = 0;
int numberOfPolygons = g.getLengths(lengthPosition);
lengthPosition++;
Polygon[] polygons = new Polygon[numberOfPolygons];
int start = 0;
for (int p = 0; p < numberOfPolygons; p++) {
int numberOfRings = g.getLengths(lengthPosition);
lengthPosition++;
LinearRing[] rings = new LinearRing[numberOfRings];
for (int r = 0; r < numberOfRings; r++) {
int numberOfCoordinates = g.getLengths(lengthPosition);
lengthPosition++;
int end = start + numberOfCoordinates * dimension;
Coordinate[] coords = getCoordinates(g, start, end);
if (!CoordinateArrays.isRing(coords)) {
Coordinate[] closedCoords = new Coordinate[coords.length + 1];
CoordinateArrays.copyDeep(coords, 0, closedCoords, 0, coords.length);
closedCoords[closedCoords.length - 1] = coords[0];
coords = closedCoords;
}
rings[r] = geometryFactory.createLinearRing(coords);
start = end;
}
if (rings.length > 1) {
polygons[p] = geometryFactory.createPolygon(rings[0], Arrays.copyOfRange(rings, 1, rings.length));
} else {
polygons[p] = geometryFactory.createPolygon(rings[0]);
}
}
return geometryFactory.createMultiPolygon(polygons);
} else if (g.getType() == Geobuf.Data.Geometry.Type.GEOMETRYCOLLECTION) {
List<Geometry> geoms = getGeometries(g);
return geometryFactory.createGeometryCollection(geoms.toArray(new Geometry[]{}));
} else {
return null;
}
}
protected List<Geometry> getGeometries(Geobuf.Data.Geometry g) {
List<Geometry> geometries = new ArrayList<Geometry>();
getGeometries(geometries, g);
return geometries;
}
protected void getGeometries(List<Geometry> geometries, Geobuf.Data.Geometry g) {
int count = g.getGeometriesCount();
if (count < 2) {
geometries.add(decode(g));
} else {
for (int i = 0; i < count; i++) {
getGeometries(geometries, g.getGeometries(i));
}
}
}
protected Coordinate[] getCoordinates(Geobuf.Data.Geometry g, int start, int end) {
int numberOfCoords = (end - start) / dimension;
Coordinate[] coords = new Coordinate[numberOfCoords];
int coordinateCounter = 0;
int c = start;
for (int i = start; i < start + numberOfCoords; i++) {
Coordinate coord = new Coordinate();
for (int k = 0; k < dimension; k++) {
double value = g.getCoords(c);
for (int l = start + k; l < c; l += dimension) {
value += g.getCoords(l);
}
value = value / maxNumberOfDecimalPlaces;
if (k == 0) {
coord.x = value;
} else if (k == 1) {
coord.y = value;
} else if (k == 2) {
coord.z = value;
}
c++;
}
coords[coordinateCounter] = coord;
coordinateCounter++;
}
return coords;
}
protected List<Coordinate[]> getCoordinates(Geobuf.Data.Geometry g) {
List<Coordinate[]> listOfCoordinates = new ArrayList<Coordinate[]>();
int numberOfLengths = g.getLengthsCount();
if (numberOfLengths == 0) {
listOfCoordinates.add(getAllCoordinates(g));
} else {
int c = 0;
int start = 0;
for (int i = 0; i < numberOfLengths; i++) {
int len = g.getLengths(i);
int end = start + (len * dimension);
Coordinate[] coordinates = getCoordinates(g, start, end);
listOfCoordinates.add(coordinates);
start = end;
}
}
return listOfCoordinates;
}
protected Coordinate[] getAllCoordinates(Geobuf.Data.Geometry g) {
return getCoordinates(g, 0, g.getCoordsCount());
}
}