package org.geotools.data.sqlserver.reader;
import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.io.ByteArrayInStream;
import com.vividsolutions.jts.io.ByteOrderDataInStream;
import com.vividsolutions.jts.io.ByteOrderValues;
import com.vividsolutions.jts.io.InStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* Decode Sql Server binary format to JTS
*
* @author Anders Bakkevold, Bouvet
*
* @source $URL$
*/
public class SqlServerBinaryReader {
private ByteOrderDataInStream dis = new ByteOrderDataInStream();
private GeometryFactory gf = new GeometryFactory();
private SqlServerBinary binary;
public SqlServerBinaryReader() {
this.gf = new GeometryFactory();
}
public SqlServerBinaryReader(GeometryFactory gf) {
this.gf = gf;
}
public Geometry read(byte[] bytes) throws IOException {
this.binary = new SqlServerBinary();
return read(new ByteArrayInStream(bytes));
}
public Geometry read(InStream is) throws IOException {
parse(is);
readCoordinateSequences();
Type type = getTypeFromBinary();
Geometry geometry = decode(0, type);
geometry.setSRID(binary.getSrid());
return geometry;
}
private Geometry decode(int shapeIndex, Type type) throws SqlServerBinaryParseException {
switch (type) {
case GEOMETRYCOLLECTION:
return decodeGeometryCollection(shapeIndex);
case POINT:
return decodePoint(shapeIndex);
case LINESTRING:
return decodeLinestring(shapeIndex);
case POLYGON:
return decodePolygon(shapeIndex);
case MULTILINESTRING:
return decodeMultiLinestring(shapeIndex);
case MULTIPOINT:
return decodeMultiPoint(shapeIndex);
case MULTIPOLYGON:
return decodeMultiPolygon(shapeIndex);
default:
throw new SqlServerBinaryParseException("Geometry type unsupported " + type);
}
}
private Geometry decodeMultiPolygon( int shapeIndex) {
Collection<Geometry> polygons = new ArrayList<Geometry>();
for (int i = shapeIndex; i < binary.getShapes().length; i++) {
if (binary.getShape(i).getParentOffset() == shapeIndex) {
polygons.add(decodePolygon(i));
}
}
return gf.createMultiPolygon(polygons.toArray(new Polygon[polygons.size()])); }
private Geometry decodeMultiPoint(int shapeIndex) {
Collection<Geometry> points = new ArrayList<Geometry>();
for (int i = shapeIndex; i < binary.getShapes().length; i++) {
if (binary.getShape(i).getParentOffset() == shapeIndex) {
points.add(gf.createPoint(binary.getSequence(binary.getShape(i).getFigureOffset())));
}
}
return gf.createMultiPoint(points.toArray(new Point[points.size()]));
}
private Geometry decodeMultiLinestring(int shapeIndex) {
Collection<Geometry> linestrings = new ArrayList<Geometry>();
for (int i = shapeIndex; i < binary.getShapes().length; i++) {
if (binary.getShape(i).getParentOffset() == shapeIndex) {
linestrings.add(gf.createLineString(binary.getSequence(binary.getShape(i).getFigureOffset())));
}
}
return gf.createMultiLineString(linestrings.toArray(new LineString[linestrings.size()]));
}
private Geometry decodePolygon(int shapeIndex) {
Shape shape = binary.getShape(shapeIndex);
int figureOffset = shape.getFigureOffset();
int figureStopIndex = binary.getFigures().length-1;
if (shapeIndex +1 < binary.getShapes().length) {
Shape nextShape = binary.getShape(shapeIndex + 1);
figureStopIndex = nextShape.getFigureOffset()-1;
}
List<LinearRing> linearRings = new ArrayList<LinearRing>();
if (figureOffset <= -1) {
return gf.createPolygon(new Coordinate[0]);
}
for (int i = figureOffset; i <= figureStopIndex; i++) {
CoordinateSequence sequence = binary.getSequence(i);
linearRings.add(gf.createLinearRing(sequence));
}
LinearRing outerShell = linearRings.remove(0);
LinearRing[] holes = linearRings.toArray(new LinearRing[linearRings.size()]);
return gf.createPolygon(outerShell, holes);
}
private Geometry decodeLinestring(int shapeIndex) {
Shape shape = binary.getShape(shapeIndex);
CoordinateSequence sequence = binary.getSequence(shape.getFigureOffset());
return gf.createLineString(sequence);
}
private Geometry decodePoint(int shapeIndex) {
Shape shape = binary.getShapes()[shapeIndex];
Coordinate coordinate;
if (binary.isSinglePoint()) {
coordinate = binary.getCoordinates()[0];
} else if (shape.getParentOffset() != -1) {
Figure figure = binary.getFigure(shape.getFigureOffset());
coordinate = binary.getCoordinates()[figure.getPointOffset()];
} else {
coordinate = null;
}
return gf.createPoint(coordinate);
}
private Geometry decodeGeometryCollection(int shapeIndex) throws SqlServerBinaryParseException {
Collection<Geometry> geometries = new ArrayList<Geometry>();
for (int i = shapeIndex +1; i < binary.getShapes().length; i++) {
Shape subShape = binary.getShapes()[i];
if (subShape.getParentOffset() == shapeIndex) {
geometries.add(decode(i, subShape.getType()));
}
}
return gf.buildGeometry(geometries);
}
private Type getTypeFromBinary() {
if (binary.isSinglePoint()) {
return Type.POINT;
}
if (binary.hasSingleLineSegment()) {
return Type.LINESTRING;
}
return binary.getShapes()[0].getType();
}
private void readCoordinateSequences() {
Figure[] figures = binary.getFigures();
CoordinateSequence[] sequences = new CoordinateSequence[figures.length];
for (int i = 0; i < figures.length; i++) {
int figurePointOffset = figures[i].getPointOffset();
int nextPointOffset = figures.length >= i+2 ? figures[i+1].getPointOffset() : binary.getCoordinates().length;
Coordinate[] coordinates = Arrays.copyOfRange(binary.getCoordinates(), figurePointOffset, nextPointOffset);
int attribute = figures[i].getAttribute();
if ((attribute == 0 || attribute == 2) && !coordinates[0].equals(coordinates[coordinates.length-1]) ) {
coordinates = Arrays.copyOf(coordinates, coordinates.length + 1);
coordinates[coordinates.length-1] = coordinates[0];
}
sequences[i] = gf.getCoordinateSequenceFactory().create(coordinates);
}
binary.setSequences(sequences);
}
private void parse(InStream is) throws IOException {
dis.setInStream(is);
dis.setOrder(ByteOrderValues.LITTLE_ENDIAN);
binary.setSrid(dis.readInt());
byte version = dis.readByte();
if (version != 1) {
throw new SqlServerBinaryParseException("Unsupported version (only supports version 1): " + version);
}
binary.setSerializationProperties(dis.readByte());
readNumberOfPoints();
readCoordinates();
readZValues();
readMValues();
if (binary.isSinglePoint()) {
binary.setFigures(new Figure[] { new Figure(1,0) });
binary.setShapes(new Shape[] { new Shape(-1,0,2)});
} else if (binary.hasSingleLineSegment()) {
binary.setFigures(new Figure[] { new Figure(1,0) });
binary.setShapes(new Shape[] { new Shape(-1,0,1)});
} else {
readFigures();
readShapes();
}
}
private void readNumberOfPoints() throws IOException {
if (binary.isSinglePoint()) {
binary.setNumberOfPoints(1);
} else if (binary.hasSingleLineSegment()) {
binary.setNumberOfPoints(2);
} else {
binary.setNumberOfPoints(dis.readInt());
}
}
private void readCoordinates() throws IOException {
Coordinate[] coordinates = new Coordinate[binary.getNumberOfPoints()];
for(int i = 0; i<binary.getNumberOfPoints(); i++) {
coordinates[i] = readCoordinate();
}
binary.setCoordinates(coordinates);
}
private void readShapes() throws IOException {
int numberOfShapes;Shape[] shapesMetadata;
numberOfShapes = dis.readInt();
shapesMetadata = new Shape[numberOfShapes];
for (int i = 0; i < numberOfShapes; i++) {
int parentOffset = dis.readInt();
int figureOffset = dis.readInt();
int shapeType = dis.readByte();
shapesMetadata[i] = new Shape(parentOffset, figureOffset, shapeType);
}
binary.setShapes(shapesMetadata);
}
private void readFigures() throws IOException {
int numberOfFigures;Figure[] figuresMetadata;
numberOfFigures = dis.readInt();
figuresMetadata = new Figure[numberOfFigures];
for (int i = 0; i < numberOfFigures; i++) {
byte figureAttribute = dis.readByte();
int figurePointOffset = dis.readInt();
figuresMetadata[i] = new Figure(figureAttribute,figurePointOffset);
}
binary.setFigures(figuresMetadata);
}
private void readMValues() throws IOException {
//measure values are currently discarded, as they cannot be represented in a JTS Geometry
if (binary.hasM()) {
for (int i = 0; i < binary.getNumberOfPoints(); i++) {
dis.readDouble();
}
}
}
private void readZValues() throws IOException {
if (binary.hasZ()) {
for (int i = 0; i < binary.getNumberOfPoints(); i++) {
binary.getCoordinates()[i].z = dis.readDouble();
}
}
}
private Coordinate readCoordinate() throws IOException {
return new Coordinate(dis.readDouble(), dis.readDouble());
}
}