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.*;
import org.geolatte.geom.crs.CoordinateReferenceSystem;
import org.geolatte.geom.crs.CoordinateReferenceSystems;
import org.geolatte.geom.crs.CrsRegistry;
import org.geolatte.geom.crs.Unit;
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 CoordinateReferenceSystem> DEFAULT_CRS = CoordinateReferenceSystems.mkProjected(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);
if (hasZValues) {
crs = CoordinateReferenceSystems.addVerticalSystem(crs, Unit.METER);
}
if (hasMValues) {
crs = CoordinateReferenceSystems.addLinearSystem(crs, Unit.METER);
}
return (CoordinateReferenceSystem>) crs;
}
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;
}
}