org.geolatte.geom.codec.db.sqlserver.SqlServerGeometry Maven / Gradle / Ivy
Show all versions of geolatte-geom Show documentation
/*
* This file is part of the GeoLatte project.
*
* GeoLatte 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.
*
* GeoLatte 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 GeoLatte. If not, see .
*
* Copyright (C) 2010 - 2013 and Ownership of code is shared by:
* Qmino bvba - Romeinsestraat 18 - 3001 Heverlee (http://www.qmino.com)
* Geovise bvba - Generaal Eisenhowerlei 9 - 2140 Antwerpen (http://www.geovise.com)
*/
package org.geolatte.geom.codec.db.sqlserver;
import org.geolatte.geom.Position;
import org.geolatte.geom.PositionSequence;
import org.geolatte.geom.PositionSequenceBuilder;
import org.geolatte.geom.PositionSequenceBuilders;
import org.geolatte.geom.crs.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static org.geolatte.geom.crs.CoordinateReferenceSystems.hasMeasureAxis;
import static org.geolatte.geom.crs.CoordinateReferenceSystems.hasVerticalAxis;
/**
* A SqlServerGeometry
represents the native SQL Server database object.
*
* Instances are created by deserializing the byte array returned in the JDBC result set.
* They present the structure of the SQL Server Geometry object as specified in Microsoft SQL Server CLR Types Serialization Formats .
*
* @author Karel Maesen, Geovise BVBA.
*/
public class SqlServerGeometry {
public static final byte SUPPORTED_VERSION = 1;
private static final CoordinateReferenceSystem> DEFAULT_CRS = CoordinateReferenceSystems.mkProjected(CrsId.valueOf(0), Unit.METER);
private static final byte hasZValuesMask = 1;
private static final byte hasMValuesMask = 2;
private static final byte isValidMask = 4;
private static final byte isSinglePointMask = 8;
private static final byte isSingleLineSegment = 16;
private ByteBuffer buffer;
private Integer srid = 0;
private CoordinateReferenceSystem> crs = DEFAULT_CRS;
private byte version;
private byte serializationPropertiesByte;
private int numberOfPoints;
private double[] points;
private double[] mValues;
private double[] zValues;
private int numberOfFigures;
private Figure[] figures = null;
private int numberOfShapes;
private Shape[] shapes = null;
private SqlServerGeometry(byte[] bytes) {
buffer = ByteBuffer.wrap(bytes);
buffer.order(ByteOrder.LITTLE_ENDIAN);
}
SqlServerGeometry() {
}
public static byte[] serialize(SqlServerGeometry sqlServerGeom) {
int capacity = sqlServerGeom.calculateCapacity();
ByteBuffer buffer = ByteBuffer.allocate(capacity);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(sqlServerGeom.srid);
buffer.put(SUPPORTED_VERSION);
buffer.put(sqlServerGeom.serializationPropertiesByte);
if (!sqlServerGeom.isSinglePoint() && !sqlServerGeom.isSingleLineSegment()) {
buffer.putInt(sqlServerGeom.numberOfPoints);
}
for (int i = 0; i < sqlServerGeom.getNumPoints(); i++) {
buffer.putDouble(sqlServerGeom.points[2 * i]);
buffer.putDouble(sqlServerGeom.points[2 * i + 1]);
}
if (sqlServerGeom.hasZValues()) {
for (int i = 0; i < sqlServerGeom.zValues.length; i++) {
buffer.putDouble(sqlServerGeom.zValues[i]);
}
}
if (sqlServerGeom.hasMValues()) {
for (int i = 0; i < sqlServerGeom.mValues.length; i++) {
buffer.putDouble(sqlServerGeom.mValues[i]);
}
}
if (sqlServerGeom.isSingleLineSegment() || sqlServerGeom.isSinglePoint()) {
return buffer.array();
}
//in all other cases, we continue to serialize shapes and figures
buffer.putInt(sqlServerGeom.getNumFigures());
for (int i = 0; i < sqlServerGeom.getNumFigures(); i++) {
sqlServerGeom.getFigure(i).store(buffer);
}
buffer.putInt(sqlServerGeom.getNumShapes());
for (int i = 0; i < sqlServerGeom.getNumShapes(); i++) {
sqlServerGeom.getShape(i).store(buffer);
}
return buffer.array();
}
public static SqlServerGeometry deserialize(byte[] bytes) {
SqlServerGeometry result = new SqlServerGeometry(bytes);
result.parse();
return result;
}
void copyCoordinate(int index, double[] coords, CoordinateReferenceSystem
crs) {
coords[0] = points[2 * index];
coords[1] = points[2 * index + 1];
int idx = 2;
if (hasZValues()) {
assert (hasVerticalAxis(crs));
coords[idx++] = zValues[index];
}
if (hasMValues()) {
assert (CoordinateReferenceSystems.hasMeasureAxis(crs));
coords[idx] = mValues[index];
}
}
void setCoordinateReferenceSystem(CoordinateReferenceSystem> crs) {
this.crs = crs;
this.srid = crs.getCrsId().getCode();
}
boolean isParentShapeOf(int parent, int child) {
return getShape(child).parentOffset == parent;
}
boolean isEmptyShape(int shapeIndex) {
return getShape(shapeIndex).figureOffset == -1;
}
IndexRange getFiguresForShape(int shapeIndex) {
int startIdx = getShape(shapeIndex).figureOffset;
if (startIdx == -1) {
return new IndexRange(-1, -1); //empty figures
}
int endIdx = -1;
int nextShapeIdx = shapeIndex + 1;
if (nextShapeIdx == getNumShapes()) {
endIdx = getNumFigures();
} else {
endIdx = getShape(nextShapeIdx).figureOffset;
}
return new IndexRange(startIdx, endIdx);
}
/**
* Returns the range of indices in the point array for the specified figure.
*
* @param figureIndex index to shape in shape array
* @return index range for
*/
IndexRange getPointsForFigure(int figureIndex) {
int start = getFigure(figureIndex).pointOffset;
int end = -1;
int nextFigure = figureIndex + 1;
if (nextFigure == getNumFigures()) {
end = getNumPoints();
} else {
end = getFigure(nextFigure).pointOffset;
}
return new IndexRange(start, end);
}
boolean isFigureInteriorRing(int figureIdx) {
return getFigure(figureIdx).isInteriorRing();
}
OpenGisType getOpenGisTypeOfShape(int shpIdx) {
return getShape(shpIdx).openGisType;
}
CoordinateReferenceSystem> getCRS(int srid, boolean hasZValues, boolean hasMValues) {
CoordinateReferenceSystem> crs = CrsRegistry.getCoordinateReferenceSystemForEPSG(srid, DEFAULT_CRS);
return CoordinateReferenceSystems.adjustTo(crs, hasZValues, hasMValues);
}
PositionSequence> coordinateRange(IndexRange range) {
crs = getCRS(getSrid(), hasZValues(), hasMValues());
PositionSequenceBuilder> psBuilder = PositionSequenceBuilders.fixedSized(range.end - range.start, crs.getPositionClass());
double[] coordinates = new double[crs.getCoordinateDimension()];
for (int idx = range.start, i = 0; idx < range.end; idx++, i++) {
copyCoordinate(idx, coordinates, crs);
psBuilder.add(coordinates);
}
return psBuilder.toPositionSequence();
}
private Figure getFigure(int index) {
return figures[index];
}
private Shape getShape(int index) {
return shapes[index];
}
void setCoordinate(int index, PositionSequence> positions) {
points[2 * index] = positions.getPositionN(index).getCoordinate(0);
points[2 * index + 1] = positions.getPositionN(index).getCoordinate(1);
if (hasZValues()) {
//TODO ensure this conditions is satisfied
if (!hasVerticalAxis(crs)) throw new IllegalStateException();
zValues[index] = positions.getPositionN(index).getCoordinate(2);
}
if (hasMValues()) {
if (!hasMeasureAxis(crs)) throw new IllegalStateException();
int idx = hasZValues() ? 3 : 2;
mValues[index] = positions.getPositionN(index).getCoordinate(idx);
}
}
boolean isEmpty() {
return this.numberOfPoints == 0;
}
OpenGisType openGisType() {
if (isValid() && isSinglePoint()) {
return OpenGisType.POINT;
}
if (isValid() && isSingleLineSegment()) {
return OpenGisType.LINESTRING;
}
return firstShapeOpenGisType();
}
void setHasZValues() {
serializationPropertiesByte |= hasZValuesMask;
}
void allocateZValueArray() {
if (this.hasZValues()) {
this.zValues = new double[this.numberOfPoints];
}
}
void allocateMValueArray() {
if (this.hasMValues()) {
this.mValues = new double[this.numberOfPoints];
}
}
void setHasMValues() {
serializationPropertiesByte |= hasMValuesMask;
}
void setIsValid() {
serializationPropertiesByte |= isValidMask;
}
void setIsSinglePoint() {
setNumberOfPoints(1);
serializationPropertiesByte |= isSinglePointMask;
}
void setIsSingleLineSegment() {
serializationPropertiesByte |= isSingleLineSegment;
}
int getNumPoints() {
return this.numberOfPoints;
}
void setNumberOfPoints(int num) {
this.numberOfPoints = num;
this.points = new double[2 * this.numberOfPoints];
}
private void parse() {
srid = buffer.getInt();
version = buffer.get();
if (!isCompatible()) {
throw new IllegalStateException("Version mismatch. Expected version " + SUPPORTED_VERSION + ", but received version " + version);
}
serializationPropertiesByte = buffer.get();
determineNumberOfPoints();
readPoints();
if (hasZValues()) {
readZValues();
}
if (hasMValues()) {
readMValues();
}
if (isSingleLineSegment() ||
isSinglePoint()) {
//generate figure and shape.
// These are assumed, not explicitly encoded in the
// serialized data. See specs.
setNumberOfFigures(1);
setFigure(0, new Figure(FigureAttribute.Stroke, 0));
setNumberOfShapes(1);
OpenGisType gisType = isSinglePoint() ? OpenGisType.POINT : OpenGisType.LINESTRING;
setShape(0, new Shape(-1, 0, gisType));
return;
}
//in all other cases, figures and shapes are
//explicitly encoded.
readFigures();
readShapes();
}
private void readShapes() {
setNumberOfShapes(buffer.getInt());
for (int sIdx = 0; sIdx < numberOfShapes; sIdx++) {
int parentOffset = buffer.getInt();
int figureOffset = buffer.getInt();
byte ogtByte = buffer.get();
OpenGisType type = OpenGisType.valueOf(ogtByte);
Shape shape = new Shape(parentOffset, figureOffset, type);
setShape(sIdx, shape);
}
}
private void readFigures() {
setNumberOfFigures(buffer.getInt());
for (int fIdx = 0; fIdx < numberOfFigures; fIdx++) {
byte faByte = buffer.get();
int pointOffset = buffer.getInt();
FigureAttribute fa = FigureAttribute.valueOf(faByte);
Figure figure = new Figure(fa, pointOffset);
setFigure(fIdx, figure);
}
}
private OpenGisType firstShapeOpenGisType() {
if (shapes == null || shapes.length == 0) {
return OpenGisType.INVALID_TYPE;
}
return shapes[0].openGisType;
}
private int calculateCapacity() {
int numPoints = getNumPoints();
int prefixSize = 6;
if (isSinglePoint() ||
isSingleLineSegment()) {
int capacity = prefixSize + 16 * numPoints;
if (hasZValues()) {
capacity += 8 * numPoints;
}
if (hasMValues()) {
capacity += 8 * numPoints;
}
return capacity;
}
int pointSize = getPointByteSize();
int size = prefixSize + 3 * 4; // prefix + 3 ints for points, shapes and figures
size += getNumPoints() * pointSize;
size += getNumFigures() * Figure.getByteSize();
size += getNumShapes() * Shape.getByteSize();
return size;
}
int getNumShapes() {
return this.numberOfShapes;
}
private int getPointByteSize() {
int size = 16; //for X/Y values
if (hasMValues()) {
size += 8;
}
if (hasZValues()) {
size += 8;
}
return size;
}
private void readPoints() {
points = new double[2 * numberOfPoints];
for (int i = 0; i < numberOfPoints; i++) {
points[2 * i] = buffer.getDouble();
points[2 * i + 1] = buffer.getDouble();
}
}
private void readZValues() {
zValues = new double[numberOfPoints];
for (int i = 0; i < numberOfPoints; i++) {
zValues[i] = buffer.getDouble();
}
}
private void readMValues() {
mValues = new double[numberOfPoints];
for (int i = 0; i < numberOfPoints; i++) {
mValues[i] = buffer.getDouble();
}
}
private void determineNumberOfPoints() {
if (isSinglePoint()) {
numberOfPoints = 1;
return;
}
if (isSingleLineSegment()) {
numberOfPoints = 2;
return;
}
numberOfPoints = buffer.getInt();
}
boolean isCompatible() {
return version == SUPPORTED_VERSION;
}
void setSrid(Integer srid) {
this.srid = (srid == null) ? 0 : srid;
}
Integer getSrid() {
return srid;
}
boolean hasZValues() {
return (serializationPropertiesByte & hasZValuesMask) != 0;
}
boolean hasMValues() {
return (serializationPropertiesByte & hasMValuesMask) != 0;
}
boolean isValid() {
return (serializationPropertiesByte & isValidMask) != 0;
}
boolean isSinglePoint() {
return (serializationPropertiesByte & isSinglePointMask) != 0;
}
boolean isSingleLineSegment() {
return (serializationPropertiesByte & isSingleLineSegment) != 0;
}
void setNumberOfFigures(int num) {
numberOfFigures = num;
figures = new Figure[numberOfFigures];
}
void setFigure(int i, Figure figure) {
figures[i] = figure;
}
void setNumberOfShapes(int num) {
numberOfShapes = num;
shapes = new Shape[numberOfShapes];
}
void setShape(int i, Shape shape) {
shapes[i] = shape;
}
int getNumFigures() {
return this.numberOfFigures;
}
CoordinateReferenceSystem> getCoordinateReferenceSystem() {
return this.crs;
}
}