From 0e85b0acaed890a8d8291decb53d294de1281652 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Wed, 27 Sep 2017 14:23:10 -0700 Subject: [PATCH 01/15] creating files --- .../microsoft/sqlserver/jdbc/Geography.java | 5 + .../microsoft/sqlserver/jdbc/Geometry.java | 200 ++++++++++++++++++ .../jdbc/InternalSpatialDatatype.java | 41 ++++ 3 files changed, 246 insertions(+) create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/Geography.java create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/InternalSpatialDatatype.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java new file mode 100644 index 000000000..4afc49a50 --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java @@ -0,0 +1,5 @@ +package com.microsoft.sqlserver.jdbc; + +public class Geography { + +} diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java new file mode 100644 index 000000000..2c8753bf6 --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -0,0 +1,200 @@ +package com.microsoft.sqlserver.jdbc; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class Geometry { + + private ByteBuffer buffer; + private InternalSpatialDatatype internalType; + private String WKT; + private int srid; + private byte version; + private byte serializationProperties; + private int numberOfPoints; + private int numberOfFigures; + private int numberOfShapes; + private double points[]; + private double zValues[]; + private double mValues[]; + private Figure figures[]; + private Shape shapes[]; + + //serialization properties + private boolean hasZvalues; + private boolean hasMvalues; + private boolean isValid ; + private boolean isSinglePoint; + private boolean isSingleLineSegment; + + private final byte hasZvaluesMask = 0b00000001; // 1 + private final byte hasMvaluesMask = 0b00000010; // 2 + private final byte isValidMask = 0b00000100; // 4 + private final byte isSinglePointMask = 0b00001000; // 8 + private final byte isSingleLineSegmentMask = 0b00010000; // 16 + + + public Geometry(String WellKnownText, int srid) { + this.WKT = WellKnownText; + this.srid = srid; + } + + public Geometry(byte[] hexData) { + buffer = ByteBuffer.wrap(hexData); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + parseHexData(); + } + + public InternalSpatialDatatype getInternalType() { + return internalType; + } + + public int getSRID() { + return srid; + } + + public String toString() { + return WKT; + } + + private void parseHexData() { + srid = buffer.getInt(); + version = buffer.get(); + serializationProperties = buffer.get(); + + interpretSerializationPropBytes(); + + readNumberOfPoints(); + + readPoints(); + + readZvalues(); + + readMvalues(); + + readNumberOfFigures(); + + readFigures(); + + readNumberOfShapes(); + + readShapes(); + + if (version == 2) { + + } + } + + private void interpretSerializationPropBytes() { + hasZvalues = (serializationProperties & hasZvaluesMask) != 0; + hasMvalues = (serializationProperties & hasMvaluesMask) != 0; + isValid = (serializationProperties & isValidMask) != 0; + isSinglePoint = (serializationProperties & isSinglePointMask) != 0; + isSingleLineSegment = (serializationProperties & isSingleLineSegmentMask) != 0; + } + + private void readNumberOfPoints() { + if (isSinglePoint) { + numberOfPoints = 1; + } else if (isSingleLineSegment) { + numberOfPoints = 2; + } else { + numberOfPoints = buffer.getInt(); + } + } + + 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 readNumberOfFigures() { + numberOfFigures = buffer.getInt(); + } + + private void readFigures() { + byte fa; + int po; + for (int i = 0; i < numberOfFigures; i++) { + fa = buffer.get(); + po = buffer.getInt(); + figures[i] = new Figure(fa, po); + } + } + + private void readNumberOfShapes() { + numberOfShapes = buffer.getInt(); + } + + private void readShapes() { + int po; + int fo; + byte ogt; + for (int i = 0; i < numberOfShapes; i++) { + po = buffer.getInt(); + fo = buffer.getInt(); + ogt = buffer.get(); + shapes[i] = new Shape(po, fo, ogt); + } + } +} + +class Figure { + private byte figuresAttribute; + private int pointOffset; + + Figure(byte figuresAttribute, int pointOffset) { + this.figuresAttribute = figuresAttribute; + this.pointOffset = pointOffset; + } + + public byte getFiguresAttribute() { + return figuresAttribute; + } + + public int getPointOffset() { + return pointOffset; + } +} + +class Shape { + private int parentOffset; + private int figureOffset; + private byte openGISType; + + Shape(int parentOffset, int figureOffset, byte openGISType) { + this.parentOffset = parentOffset; + this.figureOffset = figureOffset; + this.openGISType = openGISType; + } + + public int getParentOffset() { + return parentOffset; + } + + public int getFigureOffset() { + return figureOffset; + } + + public byte getOpenGISType() { + return openGISType; + } +} \ No newline at end of file diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/InternalSpatialDatatype.java b/src/main/java/com/microsoft/sqlserver/jdbc/InternalSpatialDatatype.java new file mode 100644 index 000000000..a4fc6edfb --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/InternalSpatialDatatype.java @@ -0,0 +1,41 @@ +package com.microsoft.sqlserver.jdbc; + +public enum InternalSpatialDatatype { + POINT((byte)1, "POINT"), + LINESTRING((byte)2, "LINESTRING"), + POLYGON((byte)3, "POLYGON"), + MULTIPOINT((byte)4, "MULTIPOINT"), + MULTILINESTRING((byte)5, "MULTILINESTRING"), + MULTIPOLYGON((byte)6, "MULTIPOLYGON"), + GEOMETRYCOLLECTION((byte)7, "GEOMETRYCOLLECTION"), + CIRCULARSTRING((byte)8, "CIRCULARSTRING"), + COMPOUNDCURVE((byte)9, "COMPOUNDCURVE"), + CURVEPOLYGON((byte)10, "CURVEPOLYGON"), + FULLGLOBE((byte)11, "FULLGLOBE"), + INVALID_TYPE((byte)0, null); + + private byte typeCode; + private String typeName; + + private InternalSpatialDatatype(byte typeCode, String typeName) { + this.typeCode = typeCode; + this.typeName = typeName; + } + + public byte getTypeCode() { + return this.typeCode; + } + + public String getTypeName() { + return this.typeName; + } + + public static InternalSpatialDatatype valueOf(byte typeCode) { + for (InternalSpatialDatatype internalType : values()) { + if (internalType.typeCode == typeCode) { + return internalType; + } + } + return INVALID_TYPE; + } +} From 6f9a03e656d7b4ddbba658a4925b038aa246e393 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Thu, 28 Sep 2017 08:32:55 -0700 Subject: [PATCH 02/15] stash --- .../microsoft/sqlserver/jdbc/DataTypes.java | 4 ++- .../microsoft/sqlserver/jdbc/Geometry.java | 28 +++++++++++++++++++ .../jdbc/SQLServerPreparedStatement.java | 9 ++++++ src/main/java/microsoft/sql/Types.java | 2 ++ 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java index 3f6ebdbea..7eebec85f 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java @@ -856,7 +856,8 @@ enum JDBCType DATETIME (Category.TIMESTAMP, microsoft.sql.Types.DATETIME, "java.sql.Timestamp"), SMALLDATETIME (Category.TIMESTAMP, microsoft.sql.Types.SMALLDATETIME, "java.sql.Timestamp"), GUID (Category.CHARACTER, microsoft.sql.Types.GUID, "java.lang.String"), - SQL_VARIANT (Category.SQL_VARIANT, microsoft.sql.Types.SQL_VARIANT, "java.lang.Object"); + SQL_VARIANT (Category.SQL_VARIANT, microsoft.sql.Types.SQL_VARIANT, "java.lang.Object"), + GEOMETRY (Category.GEOMETRY, microsoft.sql.Types.GEOMETRY, "java.lang.Object"); final Category category; @@ -906,6 +907,7 @@ enum Category { TVP, GUID, SQL_VARIANT, + GEOMETRY, } // This SetterConversion enum is based on the Category enum diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index 2c8753bf6..fe1bfeff5 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -14,11 +14,13 @@ public class Geometry { private int numberOfPoints; private int numberOfFigures; private int numberOfShapes; + private int numberOfSegments; private double points[]; private double zValues[]; private double mValues[]; private Figure figures[]; private Shape shapes[]; + private Segment segments[]; //serialization properties private boolean hasZvalues; @@ -82,7 +84,9 @@ private void parseHexData() { readShapes(); if (version == 2) { + readNumberOfSegments(); + readSegments(); } } @@ -155,6 +159,18 @@ private void readShapes() { shapes[i] = new Shape(po, fo, ogt); } } + + private void readNumberOfSegments() { + numberOfSegments = buffer.getInt(); + } + + private void readSegments() { + byte st; + for (int i = 0; i < numberOfSegments; i++) { + st = buffer.get(); + segments[i] = new Segment(st); + } + } } class Figure { @@ -197,4 +213,16 @@ public int getFigureOffset() { public byte getOpenGISType() { return openGISType; } +} + +class Segment { + private byte segmentType; + + Segment(byte segmentType) { + this.segmentType = segmentType; + } + + public byte getSegmentType() { + return segmentType; + } } \ No newline at end of file diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index 1ba3c62e8..dc940f87b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -1566,6 +1566,15 @@ public final void setFloat(int n, setValue(n, JDBCType.REAL, x, JavaType.FLOAT, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setFloat"); } + + public final void setGeometry(int n, + Geometry x) throws SQLServerException { + if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) + loggerExternal.entering(getClassNameLogging(), "setGeometry", new Object[] {n, x}); + checkClosed(); + setValue(n, JDBCType.REAL, x, JavaType.FLOAT, false); + loggerExternal.exiting(getClassNameLogging(), "setGeometry"); + } public final void setInt(int n, int value) throws SQLServerException { diff --git a/src/main/java/microsoft/sql/Types.java b/src/main/java/microsoft/sql/Types.java index 9f510c2ec..ecd7eddca 100644 --- a/src/main/java/microsoft/sql/Types.java +++ b/src/main/java/microsoft/sql/Types.java @@ -57,4 +57,6 @@ private Types() { * The constant in the Java programming language, sometimes referred to as a type code, that identifies the Microsoft SQL type SQL_VARIANT. */ public static final int SQL_VARIANT = -156; + + public static final int GEOMETRY = -157; } From 7972b9dd923bdd2c9af70d13be743fbb718515da Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Thu, 5 Oct 2017 10:25:50 -0700 Subject: [PATCH 03/15] Spatial datatypes parse changes --- .../com/microsoft/sqlserver/jdbc/DDC.java | 2 + .../microsoft/sqlserver/jdbc/DataTypes.java | 6 +- .../microsoft/sqlserver/jdbc/Geometry.java | 420 +++++++++++++++++- .../microsoft/sqlserver/jdbc/Parameter.java | 4 + .../jdbc/SQLServerPreparedStatement.java | 2 +- .../sqlserver/jdbc/SQLServerResultSet.java | 16 + .../com/microsoft/sqlserver/jdbc/dtv.java | 3 + 7 files changed, 433 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java index 6bf3af9e6..da88d5a3a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java @@ -614,6 +614,8 @@ static final Object convertStreamToObject(BaseInputStream stream, byte[] byteValue = stream.getBytes(); if (JDBCType.GUID == jdbcType) { return Util.readGUID(byteValue); + } else if (JDBCType.GEOMETRY == jdbcType) { + return new Geometry(byteValue); } else { String hexString = Util.bytesToHexString(byteValue, byteValue.length); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java index 7eebec85f..6f4f54abb 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java @@ -148,7 +148,8 @@ enum SSType SQL_VARIANT (Category.SQL_VARIANT, "sql_variant", JDBCType.SQL_VARIANT), UDT (Category.UDT, "udt", JDBCType.VARBINARY), XML (Category.XML, "xml", JDBCType.LONGNVARCHAR), - TIMESTAMP (Category.TIMESTAMP, "timestamp", JDBCType.BINARY); + TIMESTAMP (Category.TIMESTAMP, "timestamp", JDBCType.BINARY), + GEOMETRY (Category.UDT, "geometry", JDBCType.GEOMETRY); final Category category; private final String name; @@ -352,7 +353,8 @@ enum GetterConversion EnumSet.of( JDBCType.Category.BINARY, JDBCType.Category.LONG_BINARY, - JDBCType.Category.CHARACTER)), + JDBCType.Category.CHARACTER, + JDBCType.Category.GEOMETRY)), GUID ( SSType.Category.GUID, diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index fe1bfeff5..f8950b727 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -21,19 +21,24 @@ public class Geometry { private Figure figures[]; private Shape shapes[]; private Segment segments[]; + private StringBuffer WKTsb; + private int pointNumStart = 0; + private int segmentNumStart = 0; + private int shapeNumStart = 0; //serialization properties private boolean hasZvalues; private boolean hasMvalues; - private boolean isValid ; + private boolean isValid; private boolean isSinglePoint; private boolean isSingleLineSegment; - private final byte hasZvaluesMask = 0b00000001; // 1 - private final byte hasMvaluesMask = 0b00000010; // 2 - private final byte isValidMask = 0b00000100; // 4 - private final byte isSinglePointMask = 0b00001000; // 8 - private final byte isSingleLineSegmentMask = 0b00010000; // 16 + private final byte hasZvaluesMask = 0b00000001; + private final byte hasMvaluesMask = 0b00000010; + private final byte isValidMask = 0b00000100; + private final byte isSinglePointMask = 0b00001000; + private final byte isSingleLineSegmentMask = 0b00010000; + private final byte isLargerThanHemisphere = 0b00100000; public Geometry(String WellKnownText, int srid) { @@ -46,6 +51,12 @@ public Geometry(byte[] hexData) { buffer.order(ByteOrder.LITTLE_ENDIAN); parseHexData(); + + WKTsb = new StringBuffer(); + + constructWKT(internalType, numberOfPoints); + + WKT = WKTsb.toString(); } public InternalSpatialDatatype getInternalType() { @@ -71,25 +82,35 @@ private void parseHexData() { readPoints(); - readZvalues(); - - readMvalues(); - - readNumberOfFigures(); - - readFigures(); + if (hasZvalues) { + readZvalues(); + } - readNumberOfShapes(); + if (hasMvalues) { + readMvalues(); + } - readShapes(); + if (isSinglePoint || isSingleLineSegment) { + + } else { + readNumberOfFigures(); + + readFigures(); + + readNumberOfShapes(); + + readShapes(); + } - if (version == 2) { + determineInternalType(); + + if (version == 2 && internalType.getTypeCode() != 8) { readNumberOfSegments(); readSegments(); } } - + private void interpretSerializationPropBytes() { hasZvalues = (serializationProperties & hasZvaluesMask) != 0; hasMvalues = (serializationProperties & hasMvaluesMask) != 0; @@ -137,6 +158,7 @@ private void readNumberOfFigures() { private void readFigures() { byte fa; int po; + figures = new Figure[numberOfFigures]; for (int i = 0; i < numberOfFigures; i++) { fa = buffer.get(); po = buffer.getInt(); @@ -152,6 +174,7 @@ private void readShapes() { int po; int fo; byte ogt; + shapes = new Shape[numberOfShapes]; for (int i = 0; i < numberOfShapes; i++) { po = buffer.getInt(); fo = buffer.getInt(); @@ -166,11 +189,374 @@ private void readNumberOfSegments() { private void readSegments() { byte st; + segments = new Segment[numberOfSegments]; for (int i = 0; i < numberOfSegments; i++) { st = buffer.get(); segments[i] = new Segment(st); } } + + private void determineInternalType() { + if (isSinglePoint) { + internalType = InternalSpatialDatatype.POINT; + } else if (isSingleLineSegment) { + internalType = InternalSpatialDatatype.LINESTRING; + } else { + internalType = InternalSpatialDatatype.valueOf(shapes[0].getOpenGISType()); + } + } + + private void constructWKT(InternalSpatialDatatype it, int pointNumEnd) { + //Might have to divide into Simple or Compound types, instead of type by type + //Refer to the PDF for simple vs compound + WKTsb.append(it.getTypeName()); + + if (null == points || numberOfPoints == 0) { + WKT = internalType + " EMPTY"; + return; + } + + WKTsb.append("("); + + switch (it) { + case POINT: + constructPointWKT(pointNumStart); + break; + case LINESTRING: + case CIRCULARSTRING: + constructLineWKT(pointNumStart, pointNumEnd); + break; + case POLYGON: + case MULTIPOINT: + case MULTILINESTRING: + constructSimpleWKT(0); + break; + case COMPOUNDCURVE: + constructCompoundcurveWKT(segmentNumStart); + break; + case MULTIPOLYGON: + constructMultipolygonWKT(); + break; + case GEOMETRYCOLLECTION: + constructGeometryCollectionWKT(); + break; + case CURVEPOLYGON: + constructCurvepolygonWKT(); + break; + case FULLGLOBE: + WKTsb.append("FULLGLOBE"); + break; + default: + break; + } + + WKTsb.append(")"); + } + + private void constructPointWKT(int pointNum) { + int firstPointIndex = pointNum * 2; + int secondPointIndex = firstPointIndex + 1; + int zValueIndex = pointNum; + int mValueIndex = pointNum; + + + WKTsb.append(points[firstPointIndex]); + WKTsb.append(" "); + + WKTsb.append(points[secondPointIndex]); + WKTsb.append(" "); + + if (hasZvalues && !Double.isNaN(zValues[zValueIndex])) { + WKTsb.append(zValues[zValueIndex]); + WKTsb.append(" "); + + if (hasMvalues && !Double.isNaN(mValues[mValueIndex])) { + WKTsb.append(mValues[mValueIndex]); + WKTsb.append(" "); + } + } + + pointNumStart++; + WKTsb.setLength(WKTsb.length() - 1); // truncate last space + } + + private void constructLineWKT(int startIndex, int endIndex) { + for (int i = startIndex; i < endIndex; i++) { + constructPointWKT(i); + + // add ', ' to separate points, except for the last point + if (i != endIndex - 1) { + WKTsb.append(", "); + } + } + } + + + private void constructSimpleWKT(int startIndex) { + // Method for constructing Simple (i.e. not constructed of other Geometry/Geography objects) + // Geometry/Geography objects. + for (int i = startIndex; i < figures.length; i++) { + WKTsb.append("("); + if (i != figures.length - 1) { //not the last figure + constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); + } else { + constructLineWKT(figures[i].getPointOffset(), numberOfPoints); + } + + if (i != figures.length - 1) { + WKTsb.append("), "); + } else { + WKTsb.append(")"); + } + } + } + + private void constructCompoundcurveWKT(int startIndex) { + for (int i = startIndex; i < segments.length; i++) { + byte segment = segments[i].getSegmentType(); + constructSegmentWKT(i, segment, 0); + + if (i == segments.length - 1) { + WKTsb.append(")"); + break; + } + + switch (segment) { + case 0: + case 2: + if (segments[i + 1].getSegmentType() != 0) { + WKTsb.append("), "); + } + break; + case 1: + case 3: + if (segments[i + 1].getSegmentType() != 1) { + WKTsb.append("), "); + } + break; + default: + return; + } + } + } + + private void constructSegmentWKT(int currentSegment, byte segment, int pointNumEnd) { + switch (segment) { + case 0: + WKTsb.append(", "); + constructLineWKT(pointNumStart, pointNumStart + 1); + + if (currentSegment == segments.length - 1) { // last segment + break; + } else if (segments[currentSegment + 1].getSegmentType() != 0) { // not being followed by another line, but not the last segment + pointNumStart = pointNumStart - 1; + incrementPointNumStartIfPointNotReused(pointNumEnd); + } + break; + case 1: + WKTsb.append(", "); + constructLineWKT(pointNumStart, pointNumStart + 2); + + if (currentSegment == segments.length - 1) { // last segment + break; + } else if (segments[currentSegment + 1].getSegmentType() != 1) { // not being followed by another arc, but not the last segment + pointNumStart = pointNumStart - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointNumEnd); + } + + break; + case 2: + WKTsb.append("("); + constructLineWKT(pointNumStart, pointNumStart + 2); + + if (currentSegment == segments.length - 1) { // last segment + break; + } else if (segments[currentSegment + 1].getSegmentType() != 0) { // not being followed by another line, but not the last segment + pointNumStart = pointNumStart - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointNumEnd); + } + + break; + case 3: + WKTsb.append("CIRCULARSTRING("); + constructLineWKT(pointNumStart, pointNumStart + 3); + + if (currentSegment == segments.length - 1) { // last segment + break; + } else if (segments[currentSegment + 1].getSegmentType() != 1) { // not being followed by another arc + pointNumStart = pointNumStart - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointNumEnd); + } + + break; + default: + return; + } + } + + private void incrementPointNumStartIfPointNotReused(int pointNumEnd) { + // We need to increment PointNumStart if this constructSegmentWKT was called from a CurvePolygon, and the last point was actually not re-used in the + // points array. + // 0 for pointNumEnd indicates that this check is not applicable. + if (0 != pointNumEnd && (pointNumStart + 1 >= pointNumEnd)) { + pointNumStart++; + } + } + + private void constructMultipolygonWKT() { + for (int i = 0; i < figures.length; i++) { + if (figures[i].getFiguresAttribute() == 2) { // exterior ring + WKTsb.append("(("); + } else { // interior ring + WKTsb.append("("); + } + + if (i == figures.length - 1) { // last polygon + constructLineWKT(figures[i].getPointOffset(), numberOfPoints); + } else { + constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); + } + + if (i == figures.length - 1) { // last polygon, close off the Multipolygon and return + WKTsb.append("))"); + return; + } else if (figures[i + 1].getFiguresAttribute() == 2) { // not the last polygon, followed by an exterior ring + WKTsb.append(")), "); + } else { // not the last polygon, followed by an interior ring + WKTsb.append("), "); + } + } + } + + private void constructCurvepolygonWKT() { + int currentSegment = 0; + + for (int i = 0; i < figures.length; i++) { + switch (figures[i].getFiguresAttribute()) { + case 1: // line + WKTsb.append("("); + + if (i == figures.length - 1) { + constructLineWKT(pointNumStart, numberOfPoints); + } else { + constructLineWKT(pointNumStart, figures[i + 1].getPointOffset()); + pointNumStart = figures[i + 1].getPointOffset(); + } + + WKTsb.append(")"); + break; + case 2: // arc + WKTsb.append("CIRCULARSTRING("); + + if (i == figures.length - 1) { + constructLineWKT(pointNumStart, numberOfPoints); + } else { + constructLineWKT(pointNumStart, figures[i + 1].getPointOffset()); + pointNumStart = figures[i + 1].getPointOffset(); + } + + WKTsb.append(")"); + + break; + case 3: // composite curve + WKTsb.append("COMPOUNDCURVE("); + + int pointNumEnd = 0; + + if (i == figures.length - 1) { + pointNumEnd = numberOfPoints; + } else { + pointNumEnd = figures[i + 1].getPointOffset(); + } + + while (pointNumStart < pointNumEnd) { + byte segment = segments[currentSegment].getSegmentType(); + constructSegmentWKT(currentSegment, segment, pointNumEnd); + + if (currentSegment == segments.length - 1) { + WKTsb.append(")"); + // about to exit while loop, but not the last segment = we are closing Compoundcurve. + } else if (!(pointNumStart < pointNumEnd)) { + WKTsb.append("))"); + } else { + switch (segment) { + case 0: + case 2: + if (segments[currentSegment + 1].getSegmentType() != 0) { + WKTsb.append("), "); + } + break; + case 1: + case 3: + if (segments[currentSegment + 1].getSegmentType() != 1) { + WKTsb.append("), "); + } + break; + default: + return; + } + } + + currentSegment++; + } + + break; + default: + return; + } + + if (i == figures.length - 1) { + WKTsb.append(")"); + } else { + WKTsb.append(", "); + } + + } + } + + private void constructGeometryCollectionWKT() { + while (shapeNumStart < shapes.length) { + byte openGISType = shapes[shapeNumStart].getOpenGISType(); + + if (openGISType == 7) { + // Another GeometryCollection inside another. + shapeNumStart++; + constructGeometryCollectionWKT(); + return; + } else { + if (shapeNumStart == shapes.length - 1) { + constructWKT(InternalSpatialDatatype.valueOf(openGISType), numberOfPoints); + WKTsb.append(")"); + } else { + int figureIndex = shapes[shapeNumStart].getFigureOffset(); + if (figureIndex == figures.length - 1) { + constructWKT(InternalSpatialDatatype.valueOf(openGISType), numberOfPoints); + } else { + constructWKT(InternalSpatialDatatype.valueOf(openGISType), figures[figureIndex + 1].getPointOffset()); + } + WKTsb.append(", "); + } + } + + shapeNumStart++; + } + + /* + for (int i = shapeNumStart; i < shapes.length; i++) { + if (shapes[i].getOpenGISType() == 7) { + + } else { + constructWKT(InternalSpatialDatatype.valueOf(shapes[i].getOpenGISType())); + } + + if (i == figures.length - 1) { + WKTsb.append(")"); + } else { + WKTsb.append(", "); + } + } + */ + } } class Figure { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java index 86edb5d8a..65ed72f09 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java @@ -885,6 +885,10 @@ else if ((null != jdbcTypeSetByUser) && ((jdbcTypeSetByUser == JDBCType.NVARCHAR case SQL_VARIANT: param.typeDefinition = SSType.SQL_VARIANT.toString(); break; + + case GEOMETRY: + param.typeDefinition = SSType.GEOMETRY.toString(); + break; default: assert false : "Unexpected JDBC type " + dtv.getJdbcType(); break; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index dc940f87b..b7451bd89 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -1572,7 +1572,7 @@ public final void setGeometry(int n, if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setGeometry", new Object[] {n, x}); checkClosed(); - setValue(n, JDBCType.REAL, x, JavaType.FLOAT, false); + setValue(n, JDBCType.GEOMETRY, x, JavaType.STRING, false); loggerExternal.exiting(getClassNameLogging(), "setGeometry"); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index e937b27c3..c5ce92075 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -2126,6 +2126,22 @@ public float getFloat(String columnName) throws SQLServerException { loggerExternal.exiting(getClassNameLogging(), "getFloat", value); return null != value ? value : 0; } + + public Geometry getGeometry(int columnIndex) throws SQLServerException { + loggerExternal.entering(getClassNameLogging(), "getFloat", columnIndex); + checkClosed(); + Geometry value = (Geometry) getValue(columnIndex, JDBCType.GEOMETRY); + loggerExternal.exiting(getClassNameLogging(), "getFloat", value); + return value; + } + + public Geometry getGeometry(String columnName) throws SQLServerException { + loggerExternal.entering(getClassNameLogging(), "getFloat", columnName); + checkClosed(); + Geometry value = (Geometry) getValue(findColumn(columnName), JDBCType.GEOMETRY); + loggerExternal.exiting(getClassNameLogging(), "getFloat", value); + return value; + } public int getInt(int columnIndex) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getInt", columnIndex); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java index e0ea30378..a4e881ea6 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java @@ -1631,6 +1631,9 @@ final void executeOp(DTVExecuteOp op) throws SQLServerException { else if (JDBCType.SQL_VARIANT == jdbcType) { op.execute(this, String.valueOf(value)); } + else if (JDBCType.GEOMETRY == jdbcType) { + op.execute(this, value.toString()); + } else { if (null != cryptoMeta) { // if streaming types check for allowed data length in AE From 513c04dba83b87f4b4390b19ab37ecef3c35e65f Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Fri, 13 Oct 2017 15:35:12 -0700 Subject: [PATCH 04/15] Parse logic to WKT completed - draft --- .../microsoft/sqlserver/jdbc/Geometry.java | 385 ++++++++++++------ 1 file changed, 270 insertions(+), 115 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index f8950b727..73a696170 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -22,9 +22,10 @@ public class Geometry { private Shape shapes[]; private Segment segments[]; private StringBuffer WKTsb; - private int pointNumStart = 0; - private int segmentNumStart = 0; - private int shapeNumStart = 0; + private int currentPointIndex = 0; + private int currentFigureIndex = 0; + private int currentSegmentIndex = 0; + private int currentShapeIndex = 0; //serialization properties private boolean hasZvalues; @@ -54,7 +55,7 @@ public Geometry(byte[] hexData) { WKTsb = new StringBuffer(); - constructWKT(internalType, numberOfPoints); + constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); WKT = WKTsb.toString(); } @@ -206,46 +207,43 @@ private void determineInternalType() { } } - private void constructWKT(InternalSpatialDatatype it, int pointNumEnd) { - //Might have to divide into Simple or Compound types, instead of type by type - //Refer to the PDF for simple vs compound - WKTsb.append(it.getTypeName()); - + private void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) { if (null == points || numberOfPoints == 0) { WKT = internalType + " EMPTY"; return; } + WKTsb.append(isd.getTypeName()); WKTsb.append("("); - - switch (it) { + + switch (isd) { case POINT: - constructPointWKT(pointNumStart); + constructPointWKT(currentPointIndex); break; case LINESTRING: case CIRCULARSTRING: - constructLineWKT(pointNumStart, pointNumEnd); + constructLineWKT(currentPointIndex, pointIndexEnd); break; case POLYGON: case MULTIPOINT: case MULTILINESTRING: - constructSimpleWKT(0); + constructSimpleWKT(currentFigureIndex, figureIndexEnd); break; case COMPOUNDCURVE: - constructCompoundcurveWKT(segmentNumStart); + constructCompoundcurveWKT(currentSegmentIndex, segmentIndexEnd, pointIndexEnd); break; case MULTIPOLYGON: - constructMultipolygonWKT(); + constructMultipolygonWKT(currentFigureIndex, figureIndexEnd); break; case GEOMETRYCOLLECTION: - constructGeometryCollectionWKT(); + constructGeometryCollectionWKT(shapeIndexEnd); break; case CURVEPOLYGON: - constructCurvepolygonWKT(); + constructCurvepolygonWKT(currentFigureIndex, figureIndexEnd, currentSegmentIndex, segmentIndexEnd); break; case FULLGLOBE: - WKTsb.append("FULLGLOBE"); - break; + //TODO: return error + return; default: break; } @@ -253,11 +251,11 @@ private void constructWKT(InternalSpatialDatatype it, int pointNumEnd) { WKTsb.append(")"); } - private void constructPointWKT(int pointNum) { - int firstPointIndex = pointNum * 2; + private void constructPointWKT(int pointIndex) { + int firstPointIndex = pointIndex * 2; int secondPointIndex = firstPointIndex + 1; - int zValueIndex = pointNum; - int mValueIndex = pointNum; + int zValueIndex = pointIndex; + int mValueIndex = pointIndex; WKTsb.append(points[firstPointIndex]); @@ -276,34 +274,33 @@ private void constructPointWKT(int pointNum) { } } - pointNumStart++; + currentPointIndex++; WKTsb.setLength(WKTsb.length() - 1); // truncate last space } - private void constructLineWKT(int startIndex, int endIndex) { - for (int i = startIndex; i < endIndex; i++) { + private void constructLineWKT(int pointStartIndex, int pointEndIndex) { + for (int i = pointStartIndex; i < pointEndIndex; i++) { constructPointWKT(i); // add ', ' to separate points, except for the last point - if (i != endIndex - 1) { + if (i != pointEndIndex - 1) { WKTsb.append(", "); } } } - - private void constructSimpleWKT(int startIndex) { + private void constructSimpleWKT(int figureStartIndex, int figureEndIndex) { // Method for constructing Simple (i.e. not constructed of other Geometry/Geography objects) // Geometry/Geography objects. - for (int i = startIndex; i < figures.length; i++) { + for (int i = figureStartIndex; i < figureEndIndex; i++) { WKTsb.append("("); - if (i != figures.length - 1) { //not the last figure + if (i != numberOfFigures - 1) { //not the last figure constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); } else { constructLineWKT(figures[i].getPointOffset(), numberOfPoints); } - if (i != figures.length - 1) { + if (i != figureEndIndex - 1) { WKTsb.append("), "); } else { WKTsb.append(")"); @@ -311,12 +308,12 @@ private void constructSimpleWKT(int startIndex) { } } - private void constructCompoundcurveWKT(int startIndex) { - for (int i = startIndex; i < segments.length; i++) { + private void constructCompoundcurveWKT(int segmentStartIndex, int segmentEndIndex, int pointEndIndex) { + for (int i = segmentStartIndex; i < segmentEndIndex; i++) { byte segment = segments[i].getSegmentType(); - constructSegmentWKT(i, segment, 0); + constructSegmentWKT(i, segment, pointEndIndex); - if (i == segments.length - 1) { + if (i == segmentEndIndex - 1) { WKTsb.append(")"); break; } @@ -340,52 +337,53 @@ private void constructCompoundcurveWKT(int startIndex) { } } - private void constructSegmentWKT(int currentSegment, byte segment, int pointNumEnd) { + private void constructSegmentWKT(int currentSegment, byte segment, int pointEndIndex) { switch (segment) { case 0: WKTsb.append(", "); - constructLineWKT(pointNumStart, pointNumStart + 1); + constructLineWKT(currentPointIndex, currentPointIndex + 1); if (currentSegment == segments.length - 1) { // last segment break; } else if (segments[currentSegment + 1].getSegmentType() != 0) { // not being followed by another line, but not the last segment - pointNumStart = pointNumStart - 1; - incrementPointNumStartIfPointNotReused(pointNumEnd); + currentPointIndex = currentPointIndex - 1; + incrementPointNumStartIfPointNotReused(pointEndIndex); } break; + case 1: WKTsb.append(", "); - constructLineWKT(pointNumStart, pointNumStart + 2); + constructLineWKT(currentPointIndex, currentPointIndex + 2); if (currentSegment == segments.length - 1) { // last segment break; } else if (segments[currentSegment + 1].getSegmentType() != 1) { // not being followed by another arc, but not the last segment - pointNumStart = pointNumStart - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused - incrementPointNumStartIfPointNotReused(pointNumEnd); + currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointEndIndex); } break; case 2: WKTsb.append("("); - constructLineWKT(pointNumStart, pointNumStart + 2); + constructLineWKT(currentPointIndex, currentPointIndex + 2); if (currentSegment == segments.length - 1) { // last segment break; } else if (segments[currentSegment + 1].getSegmentType() != 0) { // not being followed by another line, but not the last segment - pointNumStart = pointNumStart - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused - incrementPointNumStartIfPointNotReused(pointNumEnd); + currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointEndIndex); } break; case 3: WKTsb.append("CIRCULARSTRING("); - constructLineWKT(pointNumStart, pointNumStart + 3); + constructLineWKT(currentPointIndex, currentPointIndex + 3); if (currentSegment == segments.length - 1) { // last segment break; } else if (segments[currentSegment + 1].getSegmentType() != 1) { // not being followed by another arc - pointNumStart = pointNumStart - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused - incrementPointNumStartIfPointNotReused(pointNumEnd); + currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointEndIndex); } break; @@ -394,30 +392,29 @@ private void constructSegmentWKT(int currentSegment, byte segment, int pointNumE } } - private void incrementPointNumStartIfPointNotReused(int pointNumEnd) { - // We need to increment PointNumStart if this constructSegmentWKT was called from a CurvePolygon, and the last point was actually not re-used in the - // points array. + private void incrementPointNumStartIfPointNotReused(int pointEndIndex) { + // We need to increment PointNumStart if the last point was actually not re-used in the points array. // 0 for pointNumEnd indicates that this check is not applicable. - if (0 != pointNumEnd && (pointNumStart + 1 >= pointNumEnd)) { - pointNumStart++; + if (currentPointIndex + 1 >= pointEndIndex) { + currentPointIndex++; } } - private void constructMultipolygonWKT() { - for (int i = 0; i < figures.length; i++) { + private void constructMultipolygonWKT(int figureStartIndex, int figureEndIndex) { + for (int i = figureStartIndex; i < figureEndIndex; i++) { if (figures[i].getFiguresAttribute() == 2) { // exterior ring WKTsb.append("(("); } else { // interior ring WKTsb.append("("); } - if (i == figures.length - 1) { // last polygon + if (i == figures.length - 1) { // last figure constructLineWKT(figures[i].getPointOffset(), numberOfPoints); } else { constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); } - if (i == figures.length - 1) { // last polygon, close off the Multipolygon and return + if (i == figureEndIndex - 1) { // last polygon of this multipolygon, close off the Multipolygon and return WKTsb.append("))"); return; } else if (figures[i + 1].getFiguresAttribute() == 2) { // not the last polygon, followed by an exterior ring @@ -428,19 +425,17 @@ private void constructMultipolygonWKT() { } } - private void constructCurvepolygonWKT() { - int currentSegment = 0; - - for (int i = 0; i < figures.length; i++) { + private void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex, int segmentStartIndex, int segmentEndIndex) { + for (int i = figureStartIndex; i < figureEndIndex; i++) { switch (figures[i].getFiguresAttribute()) { case 1: // line WKTsb.append("("); if (i == figures.length - 1) { - constructLineWKT(pointNumStart, numberOfPoints); + constructLineWKT(currentPointIndex, numberOfPoints); } else { - constructLineWKT(pointNumStart, figures[i + 1].getPointOffset()); - pointNumStart = figures[i + 1].getPointOffset(); + constructLineWKT(currentPointIndex, figures[i + 1].getPointOffset()); + //currentPointIndex = figures[i + 1].getPointOffset(); } WKTsb.append(")"); @@ -449,10 +444,10 @@ private void constructCurvepolygonWKT() { WKTsb.append("CIRCULARSTRING("); if (i == figures.length - 1) { - constructLineWKT(pointNumStart, numberOfPoints); + constructLineWKT(currentPointIndex, numberOfPoints); } else { - constructLineWKT(pointNumStart, figures[i + 1].getPointOffset()); - pointNumStart = figures[i + 1].getPointOffset(); + constructLineWKT(currentPointIndex, figures[i + 1].getPointOffset()); + //currentPointIndex = figures[i + 1].getPointOffset(); } WKTsb.append(")"); @@ -461,34 +456,34 @@ private void constructCurvepolygonWKT() { case 3: // composite curve WKTsb.append("COMPOUNDCURVE("); - int pointNumEnd = 0; + int pointEndIndex = 0; if (i == figures.length - 1) { - pointNumEnd = numberOfPoints; + pointEndIndex = numberOfPoints; } else { - pointNumEnd = figures[i + 1].getPointOffset(); + pointEndIndex = figures[i + 1].getPointOffset(); } - while (pointNumStart < pointNumEnd) { - byte segment = segments[currentSegment].getSegmentType(); - constructSegmentWKT(currentSegment, segment, pointNumEnd); + while (currentPointIndex < pointEndIndex) { + byte segment = segments[segmentStartIndex].getSegmentType(); + constructSegmentWKT(segmentStartIndex, segment, pointEndIndex); - if (currentSegment == segments.length - 1) { + if (segmentStartIndex >= segmentEndIndex - 1) { WKTsb.append(")"); - // about to exit while loop, but not the last segment = we are closing Compoundcurve. - } else if (!(pointNumStart < pointNumEnd)) { + // about to exit while loop, but not the last segment = we are closing Compoundcurve. + } else if (!(currentPointIndex < pointEndIndex)) { WKTsb.append("))"); } else { switch (segment) { case 0: case 2: - if (segments[currentSegment + 1].getSegmentType() != 0) { + if (segments[segmentStartIndex + 1].getSegmentType() != 0) { WKTsb.append("), "); } break; case 1: case 3: - if (segments[currentSegment + 1].getSegmentType() != 1) { + if (segments[segmentStartIndex + 1].getSegmentType() != 1) { WKTsb.append("), "); } break; @@ -497,7 +492,7 @@ private void constructCurvepolygonWKT() { } } - currentSegment++; + segmentStartIndex++; } break; @@ -505,7 +500,7 @@ private void constructCurvepolygonWKT() { return; } - if (i == figures.length - 1) { + if (i == figureEndIndex - 1) { WKTsb.append(")"); } else { WKTsb.append(", "); @@ -514,48 +509,208 @@ private void constructCurvepolygonWKT() { } } - private void constructGeometryCollectionWKT() { - while (shapeNumStart < shapes.length) { - byte openGISType = shapes[shapeNumStart].getOpenGISType(); + private void constructGeometryCollectionWKT(int shapeEndIndex) { + currentShapeIndex++; + constructGeometryCollectionWKThelper(shapeEndIndex); + } + + private void constructGeometryCollectionWKThelper(int shapeEndIndex) { + //phase 1: assume that there is no multi - stuff and no geometrycollection + while (currentShapeIndex < shapeEndIndex) { + InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(shapes[currentShapeIndex].getOpenGISType()); - if (openGISType == 7) { - // Another GeometryCollection inside another. - shapeNumStart++; - constructGeometryCollectionWKT(); - return; - } else { - if (shapeNumStart == shapes.length - 1) { - constructWKT(InternalSpatialDatatype.valueOf(openGISType), numberOfPoints); - WKTsb.append(")"); - } else { - int figureIndex = shapes[shapeNumStart].getFigureOffset(); - if (figureIndex == figures.length - 1) { - constructWKT(InternalSpatialDatatype.valueOf(openGISType), numberOfPoints); + int figureIndex = shapes[currentShapeIndex].getFigureOffset(); + int pointIndexEnd = numberOfPoints; + int figureIndexEnd = numberOfFigures; + int segmentIndexEnd = numberOfSegments; + int shapeIndexEnd = numberOfShapes; + int figureIndexIncrement = 0; + int segmentIndexIncrement = 0; + int localCurrentSegmentIndex = 0; + int localCurrentShapeIndex = 0; + + switch (isd) { + case POINT: + figureIndexIncrement++; + currentShapeIndex++; + break; + case LINESTRING: + case CIRCULARSTRING: + figureIndexIncrement++; + currentShapeIndex++; + pointIndexEnd = figures[figureIndex + 1].getPointOffset(); + break; + case POLYGON:; + case CURVEPOLYGON: + if (currentShapeIndex < shapes.length - 1) { + figureIndexEnd = shapes[currentShapeIndex + 1].getFigureOffset(); + } + + figureIndexIncrement = figureIndexEnd - currentFigureIndex; + currentShapeIndex++; + + // Needed to keep track of which segment we are at, inside the for loop + localCurrentSegmentIndex = currentSegmentIndex; + + if (isd.equals(InternalSpatialDatatype.CURVEPOLYGON)) { + // assume Version 2 + + for (int i = currentFigureIndex; i < figureIndexEnd; i++) { + // Only Compoundcurves (with figure attribute 3) can have segments + if (figures[i].getFiguresAttribute() == 3) { + + int pointOffsetEnd; + if (i == figures.length - 1) { + pointOffsetEnd = numberOfPoints; + } else { + pointOffsetEnd = figures[i + 1].getPointOffset(); + } + + int increment = calculateSegmentIncrement(localCurrentSegmentIndex, pointOffsetEnd - figures[i].getPointOffset()); + + segmentIndexIncrement = segmentIndexIncrement + increment; + localCurrentSegmentIndex = localCurrentSegmentIndex + increment; + } + } + } + + segmentIndexEnd = localCurrentSegmentIndex; + + break; + case MULTIPOINT: + case MULTILINESTRING: + case MULTIPOLYGON: + //Multipoint and MultiLineString can go on for multiple Shapes, but eventually + //the parentOffset will signal the end of the object, or it's reached the end of the + //shapes array. + //There is also no possibility that a MultiPoint or MultiLineString would branch + //into another parent. + + int thisShapesParentOffset = shapes[currentShapeIndex].getParentOffset(); + + // Increment shapeStartIndex to account for the shape index that either Multipoint, MultiLineString + // or MultiPolygon takes up + currentShapeIndex++; + while (currentShapeIndex < shapes.length - 1 && + shapes[currentShapeIndex].getParentOffset() != thisShapesParentOffset) { + figureIndexEnd = shapes[currentShapeIndex + 1].getFigureOffset(); + currentShapeIndex++; + } + + figureIndexIncrement = figureIndexEnd - currentFigureIndex; + break; + case GEOMETRYCOLLECTION: + WKTsb.append(isd.getTypeName()); + WKTsb.append("("); + + int geometryCollectionParentIndex = shapes[currentShapeIndex].getParentOffset(); + + // Needed to keep track of which shape we are at, inside the for loop + localCurrentShapeIndex = currentShapeIndex; + + + while (localCurrentShapeIndex < shapes.length - 1 && + shapes[localCurrentShapeIndex + 1].getParentOffset() > geometryCollectionParentIndex) { + localCurrentShapeIndex++; + } + // increment localCurrentShapeIndex one more time since it will be used as a shapeEndIndex parameter + // for constructGeometryCollectionWKT, and the shapeEndIndex parameter is used non-inclusively + localCurrentShapeIndex++; + + currentShapeIndex++; + constructGeometryCollectionWKThelper(localCurrentShapeIndex); + + if (currentShapeIndex < shapeEndIndex) { + WKTsb.append("), "); } else { - constructWKT(InternalSpatialDatatype.valueOf(openGISType), figures[figureIndex + 1].getPointOffset()); + WKTsb.append(")"); } - WKTsb.append(", "); - } + + continue; + case COMPOUNDCURVE: + if (currentFigureIndex == figures.length - 1) { + pointIndexEnd = numberOfPoints; + } else { + pointIndexEnd = figures[currentFigureIndex + 1].getPointOffset(); + } + + int increment = calculateSegmentIncrement(currentSegmentIndex, pointIndexEnd - + figures[currentFigureIndex].getPointOffset()); + + segmentIndexIncrement = increment; + segmentIndexEnd = currentSegmentIndex + increment; + figureIndexIncrement++; + currentShapeIndex++; + break; + case FULLGLOBE: + WKTsb.append("FULLGLOBE"); + break; + default: + break; } - shapeNumStart++; + constructWKT(isd, pointIndexEnd, figureIndexEnd, segmentIndexEnd, shapeIndexEnd); + currentFigureIndex = currentFigureIndex + figureIndexIncrement; + currentSegmentIndex = currentSegmentIndex + segmentIndexIncrement; + + if (currentShapeIndex < shapeEndIndex) { + WKTsb.append(", "); + } } + } + + //Calculates how many segments will be used by this CompoundCurve + private int calculateSegmentIncrement(int segmentStart, + int pointDifference) { - /* - for (int i = shapeNumStart; i < shapes.length; i++) { - if (shapes[i].getOpenGISType() == 7) { - - } else { - constructWKT(InternalSpatialDatatype.valueOf(shapes[i].getOpenGISType())); - } - - if (i == figures.length - 1) { - WKTsb.append(")"); - } else { - WKTsb.append(", "); + int segmentIncrement = 0; + + while (pointDifference > 0) { + switch (segments[segmentStart].getSegmentType()) { + case 0: + pointDifference = pointDifference - 1; + + if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment + break; + } else if (segments[segmentStart + 1].getSegmentType() != 0) { // one point will be reused + pointDifference = pointDifference + 1; + } + break; + case 1: + pointDifference = pointDifference - 2; + + if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment + break; + } else if (segments[segmentStart + 1].getSegmentType() != 1) { // one point will be reused + pointDifference = pointDifference + 1; + } + break; + case 2: + pointDifference = pointDifference - 2; + + if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment + break; + } else if (segments[segmentStart + 1].getSegmentType() != 0) { // one point will be reused + pointDifference = pointDifference + 1; + } + break; + case 3: + pointDifference = pointDifference - 3; + + if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment + break; + } else if (segments[segmentStart + 1].getSegmentType() != 1) { // one point will be reused + pointDifference = pointDifference + 1; + } + break; + default: + return segmentIncrement; } + segmentStart++; + segmentIncrement++; } - */ + + return segmentIncrement; } } From 3abc1164115504a6f501f9ace8a603e431869792 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Fri, 20 Oct 2017 10:24:30 -0700 Subject: [PATCH 05/15] Serialization implementation --- .../microsoft/sqlserver/jdbc/Geometry.java | 465 +++++++++++++++++- .../com/microsoft/sqlserver/jdbc/dtv.java | 2 +- 2 files changed, 440 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index 73a696170..34f73e6f2 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -1,15 +1,20 @@ package com.microsoft.sqlserver.jdbc; +import java.math.BigDecimal; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; public class Geometry { private ByteBuffer buffer; private InternalSpatialDatatype internalType; - private String WKT; + private String wkt; + private byte[] wkb; private int srid; - private byte version; + private byte version = 1; private byte serializationProperties; private int numberOfPoints; private int numberOfFigures; @@ -28,36 +33,52 @@ public class Geometry { private int currentShapeIndex = 0; //serialization properties - private boolean hasZvalues; - private boolean hasMvalues; - private boolean isValid; - private boolean isSinglePoint; - private boolean isSingleLineSegment; + private boolean hasZvalues = false; + private boolean hasMvalues = false; + //TODO: when is a geometry/geography not valid? + //Also, from the driver's point of view, should this ever be false? + private boolean isValid = true; + private boolean isSinglePoint = false; + private boolean isSingleLineSegment = false; + //TODO: how do i use this? + private boolean isLargerThanHemisphere = false; - private final byte hasZvaluesMask = 0b00000001; - private final byte hasMvaluesMask = 0b00000010; - private final byte isValidMask = 0b00000100; - private final byte isSinglePointMask = 0b00001000; - private final byte isSingleLineSegmentMask = 0b00010000; - private final byte isLargerThanHemisphere = 0b00100000; + private final byte hasZvaluesMask = 0b00000001; + private final byte hasMvaluesMask = 0b00000010; + private final byte isValidMask = 0b00000100; + private final byte isSinglePointMask = 0b00001000; + private final byte isSingleLineSegmentMask = 0b00010000; + private final byte isLargerThanHemisphereMask = 0b00100000; + // WKT to WKB properties + private int currentWktPos = 0; + private List pointList = new ArrayList(); + private List
figureList = new ArrayList
(); + private List shapeList = new ArrayList(); + private List segmentList = new ArrayList(); + public Geometry(String WellKnownText, int srid) { - this.WKT = WellKnownText; + this.wkt = WellKnownText; this.srid = srid; + + //TODO: do lazy conversion later + parseWKTForSerialization(currentWktPos, 0); + serializeToWkb(); } - - public Geometry(byte[] hexData) { - buffer = ByteBuffer.wrap(hexData); + + public Geometry(byte[] wkb) { + this.wkb = wkb; + buffer = ByteBuffer.wrap(wkb); buffer.order(ByteOrder.LITTLE_ENDIAN); - parseHexData(); + parseWkb(); WKTsb = new StringBuffer(); constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); - WKT = WKTsb.toString(); + wkt = WKTsb.toString(); } public InternalSpatialDatatype getInternalType() { @@ -68,11 +89,145 @@ public int getSRID() { return srid; } + public byte[] getWkb() { + return wkb; + } + public String toString() { - return WKT; + return wkt; + } + + private void serializeToWkb() { + ByteBuffer buf = ByteBuffer.allocate(determineWkbCapacity()); + createSerializationProperties(); + + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(srid); + buf.put(version); + buf.put(serializationProperties); + + if (!isSinglePoint && !isSingleLineSegment) { + buf.putInt(numberOfPoints); + } + + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(points[2 * i]); + buf.putDouble(points[2 * i + 1]); + } + + if (hasZvalues) { + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(zValues[i]); + } + } + + if (hasMvalues) { + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(mValues[i]); + } + } + + if (isSinglePoint || isSingleLineSegment) { + wkb = buf.array(); + return; + } + + buf.putInt(numberOfFigures); + for (int i = 0; i < numberOfFigures; i++) { + buf.put(figures[i].getFiguresAttribute()); + buf.putInt(figures[i].getPointOffset()); + } + + buf.putInt(numberOfShapes); + for (int i = 0; i < numberOfShapes; i++) { + buf.putInt(shapes[i].getParentOffset()); + buf.putInt(shapes[i].getFigureOffset()); + buf.put(shapes[i].getOpenGISType()); + } + + if (version == 2) { + buf.putInt(numberOfSegments); + for (int i = 0; i < numberOfSegments; i++) { + buf.put(segments[i].getSegmentType()); + } + } + + wkb = buf.array(); + return; } - private void parseHexData() { + private void createSerializationProperties() { + serializationProperties = 0; + if (hasZvalues) { + serializationProperties += hasZvaluesMask; + } + + if (hasMvalues) { + serializationProperties += hasMvaluesMask; + } + + if (isValid) { + serializationProperties += isValidMask; + } + + if (isSinglePoint) { + serializationProperties += isSinglePointMask; + } + + if (isSingleLineSegment) { + serializationProperties += isSingleLineSegmentMask; + } + + //TODO look into how the isLargerThanHemisphere is created + if (version == 2) { + if (isLargerThanHemisphere) { + serializationProperties += isLargerThanHemisphereMask; + } + } + } + + private int determineWkbCapacity() { + int totalSize = 0; + + totalSize+=6; // SRID + version + SerializationPropertiesByte + + if (isSinglePoint || isSingleLineSegment) { + totalSize += 16 * numberOfPoints; + + if (hasZvalues) { + totalSize += 8 * numberOfPoints; + } + + if (hasMvalues) { + totalSize += 8 * numberOfPoints; + } + + return totalSize; + } + + int pointSize = 16; + if (hasZvalues) { + pointSize += 8; + } + + if (hasMvalues) { + pointSize += 8; + } + + totalSize += 12; // 4 bytes for 3 ints, each representing the number of points, shapes and figures + totalSize += numberOfPoints * pointSize; + totalSize += numberOfFigures * 5; + totalSize += numberOfShapes * 9; + + if (version == 2) { + totalSize += 4; // 4 bytes for 1 int, representing the number of segments + totalSize += numberOfSegments; + } + + return totalSize; + } + + private void parseWkb() { srid = buffer.getInt(); version = buffer.get(); serializationProperties = buffer.get(); @@ -91,9 +246,8 @@ private void parseHexData() { readMvalues(); } - if (isSinglePoint || isSingleLineSegment) { - - } else { + //TODO: do I need to do anything when it's isSinglePoint or isSingleLineSegment? + if (!(isSinglePoint || isSingleLineSegment)) { readNumberOfFigures(); readFigures(); @@ -209,7 +363,7 @@ private void determineInternalType() { private void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) { if (null == points || numberOfPoints == 0) { - WKT = internalType + " EMPTY"; + wkt = internalType + " EMPTY"; return; } @@ -540,7 +694,7 @@ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { currentShapeIndex++; pointIndexEnd = figures[figureIndex + 1].getPointOffset(); break; - case POLYGON:; + case POLYGON: case CURVEPOLYGON: if (currentShapeIndex < shapes.length - 1) { figureIndexEnd = shapes[currentShapeIndex + 1].getFigureOffset(); @@ -712,6 +866,235 @@ private int calculateSegmentIncrement(int segmentStart, return segmentIncrement; } + + private void parseWKTForSerialization(int startPos, int parentShapeIndex) { + //after every iteration of this while loop, the currentWktPosition will be set to the + //end of the geometry/geography shape, except for the very first iteration of it. + //This means that there has to be comma (that separates the previous shape with the next shape), + //or we expect a ')' that will close the entire shape and exit the method. + + System.out.println("delete me"); + + while (hasMoreToken()) { + if (startPos != 0) { + if (wkt.charAt(currentWktPos) == ')') { + return; + } else if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + } else { + //TODO: throw exception here + return; + } + } + + String nextToken = getNextStringToken().toUpperCase(Locale.US); + + readOpenBracket(); + + if (nextToken.equals("CIRCULARSTRING") || nextToken.equals("COMPOUNDCURVE") || + nextToken.equals("CURVEPOLYGON")) { + version = 2; + } else { + version = 1; + } + + switch (nextToken) { + case "POINT": + if (startPos == 0 && nextToken.toUpperCase().equals("POINT")) { + isSinglePoint = true; + } + readPointWkt(); + break; + case "LINESTRING": + case "CIRCULARSTRING": + readLineWkt(parentShapeIndex); + + if (startPos == 0 && nextToken.toUpperCase().equals("LINESTRING") && pointList.size() == 2) { + isSingleLineSegment = true; + } + break; + case "POLYGON": + case "CURVEPOLYGON": + + break; + case "MULTIPOINT": + case "MULTILINESTRING": + case "MULTIPOLYGON": + + break; + case "GEOMETRYCOLLECTION": + //Geometrycollection i believe has to use return, in order to know the parent shape index. + continue; + case "COMPOUNDCURVE": + + break; + case "FULLGLOBE": + + break; + default: + break; + } + //all geometry methods return when the depth reaches 0. ( gives + 1 depth and ) takes away 1 depth. + readCloseBracket(); + } + + populateStructures(); + } + + private void readPointWkt() { + int numOfCoordinates = 0; + double sign; + double coords[] = new double[4]; + + while (numOfCoordinates < 4) { + sign = 1; + if (wkt.charAt(currentWktPos) == '-') { + sign = -1; + currentWktPos++; + } + + int startPos = currentWktPos; + + if (wkt.charAt(currentWktPos) == ')') { + break; + } + + while (currentWktPos < wkt.length() && + !(wkt.charAt(currentWktPos) == ',' || wkt.charAt(currentWktPos) == ' ')) { + currentWktPos++; + } + + try { + coords[numOfCoordinates] = sign * + new BigDecimal(wkt.substring(startPos, currentWktPos)).doubleValue(); + } catch (Exception e) { //modify to conversion exception + throw new IllegalArgumentException(); + } + + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + skipWhiteSpaces(); + break; + } + skipWhiteSpaces(); + + numOfCoordinates++; + } + + if (numOfCoordinates == 4) { + hasZvalues = true; + hasMvalues = true; + } else if (numOfCoordinates == 3) { + hasMvalues = true; + } + + pointList.add(new Point(coords[0], coords[1], coords[2], coords[3])); + } + + private void readLineWkt(int parentShapeOffset) { + shapeList.add(new Shape(parentShapeOffset - 1, figureList.size(), InternalSpatialDatatype.LINESTRING.getTypeCode())); + figureList.add(new Figure((byte) 1, pointList.size())); + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + readPointWkt(); + } + } + + private void readOpenBracket() { + if (wkt.charAt(currentWktPos) == '(') { + currentWktPos++; + skipWhiteSpaces(); + } else { + throw new IllegalArgumentException(); + } + } + + private void readCloseBracket() { + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == ')') { + currentWktPos++; + } else { + throw new IllegalArgumentException(); + } + } + + private boolean hasMoreToken() { + skipWhiteSpaces(); + return currentWktPos < wkt.length(); + } + + private void skipWhiteSpaces() { + while (currentWktPos < wkt.length() && Character.isWhitespace(wkt.charAt(currentWktPos))) { + currentWktPos++; + } + } + + private String getNextStringToken() { + skipWhiteSpaces(); + int endIndex = currentWktPos; + while (endIndex < wkt.length() && Character.isLetter(wkt.charAt(endIndex))) { + endIndex++; + } + int temp = currentWktPos; + currentWktPos = endIndex; + skipWhiteSpaces(); + + return wkt.substring(temp, endIndex); + } + + private void populateStructures() { + if (pointList.size() > 0) { + points = new double[pointList.size() * 2]; + + for (int i = 0; i < pointList.size(); i++) { + points[i * 2] = pointList.get(i).getX(); + points[i * 2 + 1] = pointList.get(i).getY(); + } + + if (hasZvalues) { + zValues = new double[pointList.size()]; + for (int i = 0; i < pointList.size(); i++) { + zValues[i] = pointList.get(i).getZ(); + } + } + + if (hasMvalues) { + mValues = new double[pointList.size()]; + for (int i = 0; i < pointList.size(); i++) { + mValues[i] = pointList.get(i).getM(); + } + } + } + + if (figureList.size() > 0) { + figures = new Figure[figureList.size()]; + + for (int i = 0; i < figureList.size(); i++) { + figures[i] = figureList.get(i); + } + } + + if (shapeList.size() > 0) { + shapes = new Shape[shapeList.size()]; + + for (int i = 0; i < shapeList.size(); i++) { + shapes[i] = shapeList.get(i); + } + } + + if (segmentList.size() > 0) { + segments = new Segment[segmentList.size()]; + + for (int i = 0; i < segmentList.size(); i++) { + segments[i] = segmentList.get(i); + } + } + + numberOfPoints = pointList.size(); + numberOfFigures = figureList.size(); + numberOfShapes = shapeList.size(); + numberOfSegments = segmentList.size(); + } } class Figure { @@ -766,4 +1149,34 @@ class Segment { public byte getSegmentType() { return segmentType; } +} + +class Point { + private final double x; + private final double y; + private final double z; + private final double m; + + Point(double x, double y, double z, double m) { + this.x = x; + this.y = y; + this.z = z; + this.m = m; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + public double getZ() { + return z; + } + + public double getM() { + return m; + } } \ No newline at end of file diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java index a4e881ea6..447ec0549 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java @@ -1632,7 +1632,7 @@ else if (JDBCType.SQL_VARIANT == jdbcType) { op.execute(this, String.valueOf(value)); } else if (JDBCType.GEOMETRY == jdbcType) { - op.execute(this, value.toString()); + op.execute(this, ((Geometry) value).getWkb()); } else { if (null != cryptoMeta) { From 40fe6eecd81b23a4b8c13bc124b81101cb61716a Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Tue, 24 Oct 2017 14:58:17 -0700 Subject: [PATCH 06/15] more serialization logic --- .../microsoft/sqlserver/jdbc/Geometry.java | 303 ++++++++++++++++-- 1 file changed, 273 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index 34f73e6f2..59969ac41 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -37,12 +37,26 @@ public class Geometry { private boolean hasMvalues = false; //TODO: when is a geometry/geography not valid? //Also, from the driver's point of view, should this ever be false? - private boolean isValid = true; + private boolean isValid = false; private boolean isSinglePoint = false; private boolean isSingleLineSegment = false; //TODO: how do i use this? private boolean isLargerThanHemisphere = false; + private final byte FA_INTERIOR_RING = 0; + private final byte FA_STROKE = 1; + private final byte FA_EXTERIOR_RING = 2; + + private final byte FA_POINT = 0; + private final byte FA_LINE = 1; + private final byte FA_ARC = 2; + private final byte FA_COMPOSITE_CURVE = 3; + + private final byte SEGMENT_LINE = 0; + private final byte SEGMENT_ARC = 1; + private final byte SEGMENT_FIRST_LINE = 2; + private final byte SEGMENT_FIRST_ARC = 3; + private final byte hasZvaluesMask = 0b00000001; private final byte hasMvaluesMask = 0b00000010; private final byte isValidMask = 0b00000100; @@ -57,13 +71,15 @@ public class Geometry { private List
figureList = new ArrayList
(); private List shapeList = new ArrayList(); private List segmentList = new ArrayList(); + + private List version_one_shape_indexes = new ArrayList(); public Geometry(String WellKnownText, int srid) { this.wkt = WellKnownText; this.srid = srid; //TODO: do lazy conversion later - parseWKTForSerialization(currentWktPos, 0); + parseWKTForSerialization(currentWktPos, -1, false); serializeToWkb(); } @@ -145,7 +161,7 @@ private void serializeToWkb() { buf.put(shapes[i].getOpenGISType()); } - if (version == 2) { + if (version == 2 && null != segments) { buf.putInt(numberOfSegments); for (int i = 0; i < numberOfSegments; i++) { buf.put(segments[i].getSegmentType()); @@ -381,7 +397,7 @@ private void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int fi case POLYGON: case MULTIPOINT: case MULTILINESTRING: - constructSimpleWKT(currentFigureIndex, figureIndexEnd); + constructShapeWKT(currentFigureIndex, figureIndexEnd); break; case COMPOUNDCURVE: constructCompoundcurveWKT(currentSegmentIndex, segmentIndexEnd, pointIndexEnd); @@ -443,9 +459,8 @@ private void constructLineWKT(int pointStartIndex, int pointEndIndex) { } } - private void constructSimpleWKT(int figureStartIndex, int figureEndIndex) { - // Method for constructing Simple (i.e. not constructed of other Geometry/Geography objects) - // Geometry/Geography objects. + private void constructShapeWKT(int figureStartIndex, int figureEndIndex) { + // Method for constructing shapes (simple Geometry/Geography entities that are contained within a single bracket) for (int i = figureStartIndex; i < figureEndIndex; i++) { WKTsb.append("("); if (i != numberOfFigures - 1) { //not the last figure @@ -867,14 +882,12 @@ private int calculateSegmentIncrement(int segmentStart, return segmentIncrement; } - private void parseWKTForSerialization(int startPos, int parentShapeIndex) { + private void parseWKTForSerialization(int startPos, int parentShapeIndex, boolean isGeoCollection) { //after every iteration of this while loop, the currentWktPosition will be set to the //end of the geometry/geography shape, except for the very first iteration of it. //This means that there has to be comma (that separates the previous shape with the next shape), //or we expect a ')' that will close the entire shape and exit the method. - System.out.println("delete me"); - while (hasMoreToken()) { if (startPos != 0) { if (wkt.charAt(currentWktPos) == ')') { @@ -882,51 +895,125 @@ private void parseWKTForSerialization(int startPos, int parentShapeIndex) { } else if (wkt.charAt(currentWktPos) == ',') { currentWktPos++; } else { - //TODO: throw exception here - return; + //TODO: throw exception here? + //return; } } String nextToken = getNextStringToken().toUpperCase(Locale.US); + String nextPotentialToken; + int thisShapeIndex; + InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(nextToken); + byte fa = 0; readOpenBracket(); - if (nextToken.equals("CIRCULARSTRING") || nextToken.equals("COMPOUNDCURVE") || - nextToken.equals("CURVEPOLYGON")) { + if (version == 1 && (nextToken.equals("CIRCULARSTRING") || nextToken.equals("COMPOUNDCURVE") || + nextToken.equals("CURVEPOLYGON"))) { version = 2; - } else { - version = 1; } - + switch (nextToken) { case "POINT": if (startPos == 0 && nextToken.toUpperCase().equals("POINT")) { isSinglePoint = true; } + + if (isGeoCollection) { + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + figureList.add(new Figure(FA_LINE, pointList.size())); + } + readPointWkt(); break; case "LINESTRING": case "CIRCULARSTRING": - readLineWkt(parentShapeIndex); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + fa = isd.getTypeCode() == InternalSpatialDatatype.LINESTRING.getTypeCode() ? FA_STROKE : FA_EXTERIOR_RING; + figureList.add(new Figure(fa, pointList.size())); + + readLineWkt(); if (startPos == 0 && nextToken.toUpperCase().equals("LINESTRING") && pointList.size() == 2) { isSingleLineSegment = true; } break; case "POLYGON": - case "CURVEPOLYGON": - - break; case "MULTIPOINT": case "MULTILINESTRING": - case "MULTIPOLYGON": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + readShapeWkt(thisShapeIndex, nextToken); break; - case "GEOMETRYCOLLECTION": - //Geometrycollection i believe has to use return, in order to know the parent shape index. - continue; + case "MULTIPOLYGON": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + shapeList.add(new Shape(thisShapeIndex, figureList.size(), InternalSpatialDatatype.POLYGON.getTypeCode())); //exterior polygon + readOpenBracket(); + readShapeWkt(thisShapeIndex, nextToken); + readCloseBracket(); + + if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow + readComma(); + } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop + continue; + } else { // unexpected input + throw new IllegalArgumentException(); + } + } + + break; case "COMPOUNDCURVE": + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + figureList.add(new Figure(FA_COMPOSITE_CURVE, pointList.size())); + + readCompoundCurveWkt(true); + + break; + case "CURVEPOLYGON": + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + nextPotentialToken = getNextStringToken().toUpperCase(Locale.US); + if (nextPotentialToken.equals("CIRCULARSTRING")) { + figureList.add(new Figure(FA_ARC, pointList.size())); + readOpenBracket(); + readLineWkt(); + readCloseBracket(); + } else if (nextPotentialToken.equals("COMPOUNDCURVE")) { + figureList.add(new Figure(FA_COMPOSITE_CURVE, pointList.size())); + readOpenBracket(); + readCompoundCurveWkt(true); + readCloseBracket(); + } else if (wkt.charAt(currentWktPos) == '(') { //LineString + figureList.add(new Figure(FA_LINE, pointList.size())); + readOpenBracket(); + readLineWkt(); + readCloseBracket(); + } else { + throw new IllegalArgumentException(); + } + + if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow + readComma(); + } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop + continue; + } else { // unexpected input + throw new IllegalArgumentException(); + } + } + + break; + case "GEOMETRYCOLLECTION": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + parseWKTForSerialization(currentWktPos, thisShapeIndex, true); + break; case "FULLGLOBE": @@ -941,6 +1028,33 @@ private void parseWKTForSerialization(int startPos, int parentShapeIndex) { populateStructures(); } + private void readCompoundCurveWkt(boolean isFirstIteration) { + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + String nextPotentialToken = getNextStringToken().toUpperCase(Locale.US); + if (nextPotentialToken.equals("CIRCULARSTRING")) { + readOpenBracket(); + readSegmentWkt(SEGMENT_FIRST_ARC, isFirstIteration); + readCloseBracket(); + } else if (wkt.charAt(currentWktPos) == '(') {//LineString + readOpenBracket(); + readSegmentWkt(SEGMENT_FIRST_LINE, isFirstIteration); + readCloseBracket(); + } else { + throw new IllegalArgumentException(); + } + + isFirstIteration = false; + + if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow + readComma(); + } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop + continue; + } else { // unexpected input + throw new IllegalArgumentException(); + } + } + } + private void readPointWkt() { int numOfCoordinates = 0; double sign; @@ -960,7 +1074,10 @@ private void readPointWkt() { } while (currentWktPos < wkt.length() && - !(wkt.charAt(currentWktPos) == ',' || wkt.charAt(currentWktPos) == ' ')) { + (Character.isDigit(wkt.charAt(currentWktPos)) + || wkt.charAt(currentWktPos) == '.' + || wkt.charAt(currentWktPos) == 'E' + || wkt.charAt(currentWktPos) == 'e')) { currentWktPos++; } @@ -975,6 +1092,7 @@ private void readPointWkt() { if (wkt.charAt(currentWktPos) == ',') { currentWktPos++; skipWhiteSpaces(); + numOfCoordinates++; break; } skipWhiteSpaces(); @@ -986,21 +1104,122 @@ private void readPointWkt() { hasZvalues = true; hasMvalues = true; } else if (numOfCoordinates == 3) { - hasMvalues = true; + hasZvalues = true; } pointList.add(new Point(coords[0], coords[1], coords[2], coords[3])); } - private void readLineWkt(int parentShapeOffset) { - shapeList.add(new Shape(parentShapeOffset - 1, figureList.size(), InternalSpatialDatatype.LINESTRING.getTypeCode())); - figureList.add(new Figure((byte) 1, pointList.size())); + private void readLineWkt() { while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { readPointWkt(); } } + + private void readShapeWkt(int parentShapeIndex, String nextToken) { + byte fa = FA_POINT; + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + if (nextToken.equals("MULTIPOINT")) { + shapeList.add(new Shape(parentShapeIndex, figureList.size(), InternalSpatialDatatype.POINT.getTypeCode())); + } else if (nextToken.equals("MULTILINESTRING")) { + shapeList.add(new Shape(parentShapeIndex, figureList.size(), InternalSpatialDatatype.LINESTRING.getTypeCode())); + } + + if (version == 1) { + if (nextToken.equals("MULTIPOINT")) { + fa = FA_STROKE; + } else if (nextToken.equals("MULTILINESTRING") || nextToken.equals("POLYGON")) { + fa = FA_EXTERIOR_RING; + } + version_one_shape_indexes.add(figureList.size()); + } else if (version == 2) { + if (nextToken.equals("MULTIPOINT") || nextToken.equals("MULTILINESTRING") || + nextToken.equals("POLYGON") || nextToken.equals("MULTIPOLYGON")) { + fa = FA_LINE; + } + } + + figureList.add(new Figure(fa, pointList.size())); + readOpenBracket(); + readLineWkt(); + readCloseBracket(); + + skipWhiteSpaces(); + + if (wkt.charAt(currentWktPos) == ',') { // more rings to follow + readComma(); + } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop + continue; + } else { // unexpected input + throw new IllegalArgumentException(); + } + } + } + + private void readSegmentWkt(int segmentType, boolean isFirstIteration) { + segmentList.add(new Segment((byte) segmentType)); + + int segmentLength = segmentType; + + // under 2 means 0 or 1 (possible values). 0 (line) has 1 point, and 1 (arc) has 2 points, so increment by one + if (segmentLength < 2) { + segmentLength++; + } + + for (int i = 0; i < segmentLength; i++) { + //If a segment type of 2 (first line) or 3 (first arc) is not from the very first iteration of the while loop, + //then the first point has to be a duplicate point from the previous segment, so skip the first point. + if (i == 0 && !isFirstIteration && segmentType >= 2) { + skipFirstPointWkt(); + } else { + readPointWkt(); + } + } + + if (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + if (segmentType == SEGMENT_FIRST_ARC || segmentType == SEGMENT_ARC) { + readSegmentWkt(SEGMENT_ARC, false); + } else if (segmentType == SEGMENT_FIRST_LINE | segmentType == SEGMENT_LINE) { + readSegmentWkt(SEGMENT_LINE, false); + } + } + } + + private void skipFirstPointWkt() { + int numOfCoordinates = 0; + + while (numOfCoordinates < 4) { + if (wkt.charAt(currentWktPos) == '-') { + currentWktPos++; + } + + if (wkt.charAt(currentWktPos) == ')') { + break; + } + + while (currentWktPos < wkt.length() && + (Character.isDigit(wkt.charAt(currentWktPos)) + || wkt.charAt(currentWktPos) == '.' + || wkt.charAt(currentWktPos) == 'E' + || wkt.charAt(currentWktPos) == 'e')) { + currentWktPos++; + } + + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + skipWhiteSpaces(); + numOfCoordinates++; + break; + } + skipWhiteSpaces(); + + numOfCoordinates++; + } + } private void readOpenBracket() { + skipWhiteSpaces(); if (wkt.charAt(currentWktPos) == '(') { currentWktPos++; skipWhiteSpaces(); @@ -1013,6 +1232,17 @@ private void readCloseBracket() { skipWhiteSpaces(); if (wkt.charAt(currentWktPos) == ')') { currentWktPos++; + skipWhiteSpaces(); + } else { + throw new IllegalArgumentException(); + } + } + + private void readComma() { + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + skipWhiteSpaces(); } else { throw new IllegalArgumentException(); } @@ -1066,6 +1296,15 @@ private void populateStructures() { } } + // if version is 2, then we need to check for potential shapes (polygon & multi-shapes) that were + // given their figure attributes as if it was version 1, since we don't know what would be the + // version of the geometry/geography before we parse the entire WKT. + if (version == 2) { + for (int i = 0; i < version_one_shape_indexes.size(); i++) { + figureList.get(version_one_shape_indexes.get(i)).setFiguresAttribute((byte) 1); + } + } + if (figureList.size() > 0) { figures = new Figure[figureList.size()]; @@ -1113,6 +1352,10 @@ public byte getFiguresAttribute() { public int getPointOffset() { return pointOffset; } + + public void setFiguresAttribute(byte fa) { + figuresAttribute = fa; + } } class Shape { From 1583949eb520f2cd45a61e86862275fe82f6d226 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Fri, 27 Oct 2017 11:42:49 -0700 Subject: [PATCH 07/15] Mostly finished with implementation of spatial datatypes --- .../com/microsoft/sqlserver/jdbc/DDC.java | 4 +- .../microsoft/sqlserver/jdbc/DataTypes.java | 10 +- .../microsoft/sqlserver/jdbc/Geography.java | 299 +++- .../microsoft/sqlserver/jdbc/Geometry.java | 1324 ++--------------- .../microsoft/sqlserver/jdbc/Parameter.java | 4 + .../jdbc/SQLServerPreparedStatement.java | 9 + .../sqlserver/jdbc/SQLServerResultSet.java | 16 + .../jdbc/SQLServerSpatialDatatype.java | 1282 ++++++++++++++++ .../com/microsoft/sqlserver/jdbc/dtv.java | 5 +- src/main/java/microsoft/sql/Types.java | 8 + 10 files changed, 1724 insertions(+), 1237 deletions(-) create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java index da88d5a3a..9ec99c7ba 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java @@ -615,7 +615,9 @@ static final Object convertStreamToObject(BaseInputStream stream, if (JDBCType.GUID == jdbcType) { return Util.readGUID(byteValue); } else if (JDBCType.GEOMETRY == jdbcType) { - return new Geometry(byteValue); + return Geometry.STGeomFromWKB(byteValue); + } else if (JDBCType.GEOGRAPHY == jdbcType) { + return new Geography(byteValue); } else { String hexString = Util.bytesToHexString(byteValue, byteValue.length); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java index 6f4f54abb..19c6a64a2 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java @@ -149,7 +149,8 @@ enum SSType UDT (Category.UDT, "udt", JDBCType.VARBINARY), XML (Category.XML, "xml", JDBCType.LONGNVARCHAR), TIMESTAMP (Category.TIMESTAMP, "timestamp", JDBCType.BINARY), - GEOMETRY (Category.UDT, "geometry", JDBCType.GEOMETRY); + GEOMETRY (Category.UDT, "geometry", JDBCType.GEOMETRY), + GEOGRAPHY (Category.UDT, "geography", JDBCType.GEOGRAPHY); final Category category; private final String name; @@ -354,7 +355,8 @@ enum GetterConversion JDBCType.Category.BINARY, JDBCType.Category.LONG_BINARY, JDBCType.Category.CHARACTER, - JDBCType.Category.GEOMETRY)), + JDBCType.Category.GEOMETRY, + JDBCType.Category.GEOGRAPHY)), GUID ( SSType.Category.GUID, @@ -859,7 +861,8 @@ enum JDBCType SMALLDATETIME (Category.TIMESTAMP, microsoft.sql.Types.SMALLDATETIME, "java.sql.Timestamp"), GUID (Category.CHARACTER, microsoft.sql.Types.GUID, "java.lang.String"), SQL_VARIANT (Category.SQL_VARIANT, microsoft.sql.Types.SQL_VARIANT, "java.lang.Object"), - GEOMETRY (Category.GEOMETRY, microsoft.sql.Types.GEOMETRY, "java.lang.Object"); + GEOMETRY (Category.GEOMETRY, microsoft.sql.Types.GEOMETRY, "java.lang.Object"), + GEOGRAPHY (Category.GEOGRAPHY, microsoft.sql.Types.GEOGRAPHY, "java.lang.Object"); final Category category; @@ -910,6 +913,7 @@ enum Category { GUID, SQL_VARIANT, GEOMETRY, + GEOGRAPHY } // This SetterConversion enum is based on the Category enum diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java index 4afc49a50..25020f379 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java @@ -1,5 +1,300 @@ package com.microsoft.sqlserver.jdbc; -public class Geography { +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Locale; -} +public class Geography extends SQLServerSpatialDatatype { + public Geography(String WellKnownText, int srid) { + this.wkt = WellKnownText; + this.srid = srid; + + parseWKTForSerialization(currentWktPos, -1, false); + serializeToWkb(false); + isNull = false; + } + + public Geography(byte[] wkb) { + this.wkb = wkb; + buffer = ByteBuffer.wrap(wkb); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + parseWkb(); + + WKTsb = new StringBuffer(); + WKTsbNoZM = new StringBuffer(); + + constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); + + wkt = WKTsb.toString(); + wktNoZM = WKTsbNoZM.toString(); + isNull = false; + } + + public Geography() { + // TODO Auto-generated constructor stub + } + + public static Geography STGeomFromText(String wkt, int srid) { + return new Geography(wkt, srid); + } + + public static Geography STGeomFromWKB(byte[] wkb) { + return new Geography(wkb); + } + + public static Geography deserialize(byte[] wkb) { + return new Geography(wkb); + } + + public static Geography parse(String wkt) { + return new Geography(wkt, 0); + } + + public static Geography point(double x, double y, int srid) { + return new Geography("POINT (" + x + " " + y + ")", srid); + } + + public String STAsText() { + if (null == wktNoZM) { + buffer = ByteBuffer.wrap(wkb); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + parseWkb(); + + WKTsb = new StringBuffer(); + WKTsbNoZM = new StringBuffer(); + constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); + wktNoZM = WKTsbNoZM.toString(); + } + return wktNoZM; + } + + public byte[] STAsBinary() { + if (null == wkbNoZM) { + serializeToWkb(true); + } + return wkbNoZM; + } + + public byte[] serialize() { + return wkb; + } + + public boolean hasM() { + return hasMvalues; + } + + public boolean hasZ() { + return hasZvalues; + } + + public Double getX() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && points.length == 2) { + return points[0]; + } + return null; + } + + public Double getY() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && points.length == 2) { + return points[1]; + } + return null; + } + + public Double getM() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && hasM()) { + return mValues[0]; + } + return null; + } + + public Double getZ() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && hasZ()) { + return zValues[0]; + } + return null; + } + + public int getSrid() { + return srid; + } + + public boolean isNull() { + return isNull; + } + + public int STNumPoints() { + return numberOfPoints; + } + + public String STGeographyType() { + if (null != internalType) { + return internalType.getTypeName(); + } + return null; + } + + public String asTextZM() { + return wkt; + } + + public String toString() { + return wkt; + } + + protected void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) { + if (null == points || numberOfPoints == 0) { + if (isd.getTypeCode() == 11) { // FULLGLOBE + appendToWKTBuffers("FULLGLOBE"); + return; + } + appendToWKTBuffers(internalType + " EMPTY"); + return; + } + + appendToWKTBuffers(isd.getTypeName()); + appendToWKTBuffers("("); + + switch (isd) { + case POINT: + constructPointWKT(currentPointIndex); + break; + case LINESTRING: + case CIRCULARSTRING: + constructLineWKT(currentPointIndex, pointIndexEnd); + break; + case POLYGON: + case MULTIPOINT: + case MULTILINESTRING: + constructShapeWKT(currentFigureIndex, figureIndexEnd); + break; + case COMPOUNDCURVE: + constructCompoundcurveWKT(currentSegmentIndex, segmentIndexEnd, pointIndexEnd); + break; + case MULTIPOLYGON: + constructMultipolygonWKT(currentFigureIndex, figureIndexEnd); + break; + case GEOMETRYCOLLECTION: + constructGeometryCollectionWKT(shapeIndexEnd); + break; + case CURVEPOLYGON: + constructCurvepolygonWKT(currentFigureIndex, figureIndexEnd, currentSegmentIndex, segmentIndexEnd); + break; + default: + break; + } + + appendToWKTBuffers(")"); + } + + protected void parseWKTForSerialization(int startPos, int parentShapeIndex, boolean isGeoCollection) { + //after every iteration of this while loop, the currentWktPosition will be set to the + //end of the geometry/geography shape, except for the very first iteration of it. + //This means that there has to be comma (that separates the previous shape with the next shape), + //or we expect a ')' that will close the entire shape and exit the method. + + parse: while (hasMoreToken()) { + if (startPos != 0) { + if (wkt.charAt(currentWktPos) == ')') { + return; + } else if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + } + } + + String nextToken = getNextStringToken().toUpperCase(Locale.US); + int thisShapeIndex; + InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(nextToken); + byte fa = 0; + + // check for FULLGLOBE before reading the first open bracket, since FULLGLOBE doesn't have one. + if (nextToken.equals("FULLGLOBE")) { + if (startPos != 0) { + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + } + + shapeList.add(new Shape(parentShapeIndex, -1, isd.getTypeCode())); + isLargerThanHemisphere = true; + version = 2; + break parse; + } + + readOpenBracket(); + + if (version == 1 && (nextToken.equals("CIRCULARSTRING") || nextToken.equals("COMPOUNDCURVE") || + nextToken.equals("CURVEPOLYGON"))) { + version = 2; + } + + switch (nextToken) { + case "POINT": + if (startPos == 0 && nextToken.toUpperCase().equals("POINT")) { + isSinglePoint = true; + } + + if (isGeoCollection) { + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + figureList.add(new Figure(FA_LINE, pointList.size())); + } + + readPointWkt(); + break; + case "LINESTRING": + case "CIRCULARSTRING": + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + fa = isd.getTypeCode() == InternalSpatialDatatype.LINESTRING.getTypeCode() ? FA_STROKE : FA_EXTERIOR_RING; + figureList.add(new Figure(fa, pointList.size())); + + readLineWkt(); + + if (startPos == 0 && nextToken.toUpperCase().equals("LINESTRING") && pointList.size() == 2) { + isSingleLineSegment = true; + } + break; + case "POLYGON": + case "MULTIPOINT": + case "MULTILINESTRING": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + readShapeWkt(thisShapeIndex, nextToken); + + break; + case "MULTIPOLYGON": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + readMultiPolygonWkt(thisShapeIndex, nextToken); + + break; + case "COMPOUNDCURVE": + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + figureList.add(new Figure(FA_COMPOSITE_CURVE, pointList.size())); + + readCompoundCurveWkt(true); + + break; + case "CURVEPOLYGON": + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + readCurvePolygon(); + + break; + case "GEOMETRYCOLLECTION": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + parseWKTForSerialization(currentWktPos, thisShapeIndex, true); + + break; + default: + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + } + readCloseBracket(); + } + + populateStructures(); + } +} \ No newline at end of file diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index 59969ac41..da18702d0 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -1,89 +1,20 @@ package com.microsoft.sqlserver.jdbc; -import java.math.BigDecimal; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; -public class Geometry { - - private ByteBuffer buffer; - private InternalSpatialDatatype internalType; - private String wkt; - private byte[] wkb; - private int srid; - private byte version = 1; - private byte serializationProperties; - private int numberOfPoints; - private int numberOfFigures; - private int numberOfShapes; - private int numberOfSegments; - private double points[]; - private double zValues[]; - private double mValues[]; - private Figure figures[]; - private Shape shapes[]; - private Segment segments[]; - private StringBuffer WKTsb; - private int currentPointIndex = 0; - private int currentFigureIndex = 0; - private int currentSegmentIndex = 0; - private int currentShapeIndex = 0; - - //serialization properties - private boolean hasZvalues = false; - private boolean hasMvalues = false; - //TODO: when is a geometry/geography not valid? - //Also, from the driver's point of view, should this ever be false? - private boolean isValid = false; - private boolean isSinglePoint = false; - private boolean isSingleLineSegment = false; - //TODO: how do i use this? - private boolean isLargerThanHemisphere = false; - - private final byte FA_INTERIOR_RING = 0; - private final byte FA_STROKE = 1; - private final byte FA_EXTERIOR_RING = 2; - - private final byte FA_POINT = 0; - private final byte FA_LINE = 1; - private final byte FA_ARC = 2; - private final byte FA_COMPOSITE_CURVE = 3; - - private final byte SEGMENT_LINE = 0; - private final byte SEGMENT_ARC = 1; - private final byte SEGMENT_FIRST_LINE = 2; - private final byte SEGMENT_FIRST_ARC = 3; - - private final byte hasZvaluesMask = 0b00000001; - private final byte hasMvaluesMask = 0b00000010; - private final byte isValidMask = 0b00000100; - private final byte isSinglePointMask = 0b00001000; - private final byte isSingleLineSegmentMask = 0b00010000; - private final byte isLargerThanHemisphereMask = 0b00100000; - - // WKT to WKB properties - - private int currentWktPos = 0; - private List pointList = new ArrayList(); - private List
figureList = new ArrayList
(); - private List shapeList = new ArrayList(); - private List segmentList = new ArrayList(); - - private List version_one_shape_indexes = new ArrayList(); - - public Geometry(String WellKnownText, int srid) { +public class Geometry extends SQLServerSpatialDatatype { + private Geometry(String WellKnownText, int srid) { this.wkt = WellKnownText; this.srid = srid; - //TODO: do lazy conversion later parseWKTForSerialization(currentWktPos, -1, false); - serializeToWkb(); + serializeToWkb(false); + isNull = false; } - public Geometry(byte[] wkb) { + private Geometry(byte[] wkb) { this.wkb = wkb; buffer = ByteBuffer.wrap(wkb); buffer.order(ByteOrder.LITTLE_ENDIAN); @@ -91,300 +22,139 @@ public Geometry(byte[] wkb) { parseWkb(); WKTsb = new StringBuffer(); + WKTsbNoZM = new StringBuffer(); constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); wkt = WKTsb.toString(); + wktNoZM = WKTsbNoZM.toString(); + isNull = false; } - public InternalSpatialDatatype getInternalType() { - return internalType; + public Geometry() { + // TODO Auto-generated constructor stub } - public int getSRID() { - return srid; + public static Geometry STGeomFromText(String wkt, int srid) { + return new Geometry(wkt, srid); } - public byte[] getWkb() { - return wkb; + public static Geometry STGeomFromWKB(byte[] wkb) { + return new Geometry(wkb); } - public String toString() { - return wkt; + public static Geometry deserialize(byte[] wkb) { + return new Geometry(wkb); } - private void serializeToWkb() { - ByteBuffer buf = ByteBuffer.allocate(determineWkbCapacity()); - createSerializationProperties(); - - buf.order(ByteOrder.LITTLE_ENDIAN); - buf.putInt(srid); - buf.put(version); - buf.put(serializationProperties); - - if (!isSinglePoint && !isSingleLineSegment) { - buf.putInt(numberOfPoints); - } - - for (int i = 0; i < numberOfPoints; i++) { - buf.putDouble(points[2 * i]); - buf.putDouble(points[2 * i + 1]); - } - - if (hasZvalues) { - for (int i = 0; i < numberOfPoints; i++) { - buf.putDouble(zValues[i]); - } - } - - if (hasMvalues) { - for (int i = 0; i < numberOfPoints; i++) { - buf.putDouble(mValues[i]); - } - } - - if (isSinglePoint || isSingleLineSegment) { - wkb = buf.array(); - return; - } - - buf.putInt(numberOfFigures); - for (int i = 0; i < numberOfFigures; i++) { - buf.put(figures[i].getFiguresAttribute()); - buf.putInt(figures[i].getPointOffset()); - } - - buf.putInt(numberOfShapes); - for (int i = 0; i < numberOfShapes; i++) { - buf.putInt(shapes[i].getParentOffset()); - buf.putInt(shapes[i].getFigureOffset()); - buf.put(shapes[i].getOpenGISType()); - } - - if (version == 2 && null != segments) { - buf.putInt(numberOfSegments); - for (int i = 0; i < numberOfSegments; i++) { - buf.put(segments[i].getSegmentType()); - } - } - - wkb = buf.array(); - return; + public static Geometry parse(String wkt) { + return new Geometry(wkt, 0); } - private void createSerializationProperties() { - serializationProperties = 0; - if (hasZvalues) { - serializationProperties += hasZvaluesMask; - } - - if (hasMvalues) { - serializationProperties += hasMvaluesMask; - } - - if (isValid) { - serializationProperties += isValidMask; - } - - if (isSinglePoint) { - serializationProperties += isSinglePointMask; - } - - if (isSingleLineSegment) { - serializationProperties += isSingleLineSegmentMask; - } - - //TODO look into how the isLargerThanHemisphere is created - if (version == 2) { - if (isLargerThanHemisphere) { - serializationProperties += isLargerThanHemisphereMask; - } - } + public static Geometry point(double x, double y, int srid) { + return new Geometry("POINT (" + x + " " + y + ")", srid); } - - private int determineWkbCapacity() { - int totalSize = 0; - - totalSize+=6; // SRID + version + SerializationPropertiesByte - - if (isSinglePoint || isSingleLineSegment) { - totalSize += 16 * numberOfPoints; - - if (hasZvalues) { - totalSize += 8 * numberOfPoints; - } + + public String STAsText() { + if (null == wktNoZM) { + buffer = ByteBuffer.wrap(wkb); + buffer.order(ByteOrder.LITTLE_ENDIAN); - if (hasMvalues) { - totalSize += 8 * numberOfPoints; - } + parseWkb(); - return totalSize; - } - - int pointSize = 16; - if (hasZvalues) { - pointSize += 8; - } - - if (hasMvalues) { - pointSize += 8; + WKTsb = new StringBuffer(); + WKTsbNoZM = new StringBuffer(); + constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); + wktNoZM = WKTsbNoZM.toString(); } - - totalSize += 12; // 4 bytes for 3 ints, each representing the number of points, shapes and figures - totalSize += numberOfPoints * pointSize; - totalSize += numberOfFigures * 5; - totalSize += numberOfShapes * 9; - - if (version == 2) { - totalSize += 4; // 4 bytes for 1 int, representing the number of segments - totalSize += numberOfSegments; - } - - return totalSize; + return wktNoZM; } - - private void parseWkb() { - srid = buffer.getInt(); - version = buffer.get(); - serializationProperties = buffer.get(); - - interpretSerializationPropBytes(); - - readNumberOfPoints(); - - readPoints(); - - if (hasZvalues) { - readZvalues(); - } - - if (hasMvalues) { - readMvalues(); - } - - //TODO: do I need to do anything when it's isSinglePoint or isSingleLineSegment? - if (!(isSinglePoint || isSingleLineSegment)) { - readNumberOfFigures(); - - readFigures(); - - readNumberOfShapes(); - - readShapes(); - } - - determineInternalType(); - - if (version == 2 && internalType.getTypeCode() != 8) { - readNumberOfSegments(); - - readSegments(); + + public byte[] STAsBinary() { + if (null == wkbNoZM) { + serializeToWkb(true); } + return wkbNoZM; } - - private void interpretSerializationPropBytes() { - hasZvalues = (serializationProperties & hasZvaluesMask) != 0; - hasMvalues = (serializationProperties & hasMvaluesMask) != 0; - isValid = (serializationProperties & isValidMask) != 0; - isSinglePoint = (serializationProperties & isSinglePointMask) != 0; - isSingleLineSegment = (serializationProperties & isSingleLineSegmentMask) != 0; + + public byte[] serialize() { + return wkb; } - private void readNumberOfPoints() { - if (isSinglePoint) { - numberOfPoints = 1; - } else if (isSingleLineSegment) { - numberOfPoints = 2; - } else { - numberOfPoints = buffer.getInt(); - } + public boolean hasM() { + return hasMvalues; } - - 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(); - } + + public boolean hasZ() { + return hasZvalues; } - private void readZvalues() { - zValues = new double[numberOfPoints]; - for (int i = 0; i < numberOfPoints; i++) { - zValues[i] = buffer.getDouble(); + public Double getX() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && points.length == 2) { + return points[0]; } + return null; } - private void readMvalues() { - mValues = new double[numberOfPoints]; - for (int i = 0; i < numberOfPoints; i++) { - mValues[i] = buffer.getDouble(); + public Double getY() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && points.length == 2) { + return points[1]; } + return null; } - private void readNumberOfFigures() { - numberOfFigures = buffer.getInt(); + public Double getM() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && hasM()) { + return mValues[0]; + } + return null; } - private void readFigures() { - byte fa; - int po; - figures = new Figure[numberOfFigures]; - for (int i = 0; i < numberOfFigures; i++) { - fa = buffer.get(); - po = buffer.getInt(); - figures[i] = new Figure(fa, po); + public Double getZ() { + if (null != internalType && internalType == InternalSpatialDatatype.POINT && hasZ()) { + return zValues[0]; } + return null; + } + + public int getSrid() { + return srid; } - private void readNumberOfShapes() { - numberOfShapes = buffer.getInt(); + public boolean isNull() { + return isNull; } - private void readShapes() { - int po; - int fo; - byte ogt; - shapes = new Shape[numberOfShapes]; - for (int i = 0; i < numberOfShapes; i++) { - po = buffer.getInt(); - fo = buffer.getInt(); - ogt = buffer.get(); - shapes[i] = new Shape(po, fo, ogt); + public int STNumPoints() { + return numberOfPoints; + } + + public String STGeometryType() { + if (null != internalType) { + return internalType.getTypeName(); } + return null; } - private void readNumberOfSegments() { - numberOfSegments = buffer.getInt(); + public String asTextZM() { + return wkt; } - private void readSegments() { - byte st; - segments = new Segment[numberOfSegments]; - for (int i = 0; i < numberOfSegments; i++) { - st = buffer.get(); - segments[i] = new Segment(st); - } - } - - private void determineInternalType() { - if (isSinglePoint) { - internalType = InternalSpatialDatatype.POINT; - } else if (isSingleLineSegment) { - internalType = InternalSpatialDatatype.LINESTRING; - } else { - internalType = InternalSpatialDatatype.valueOf(shapes[0].getOpenGISType()); - } + public String toString() { + return wkt; } - private void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) { + protected void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) { if (null == points || numberOfPoints == 0) { - wkt = internalType + " EMPTY"; + if (isd.getTypeCode() == 11) { // FULLGLOBE + throw new IllegalArgumentException("Fullglobe is not supported for Geometry."); + } + appendToWKTBuffers(internalType + " EMPTY"); return; } - WKTsb.append(isd.getTypeName()); - WKTsb.append("("); + appendToWKTBuffers(isd.getTypeName()); + appendToWKTBuffers("("); switch (isd) { case POINT: @@ -411,478 +181,14 @@ private void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int fi case CURVEPOLYGON: constructCurvepolygonWKT(currentFigureIndex, figureIndexEnd, currentSegmentIndex, segmentIndexEnd); break; - case FULLGLOBE: - //TODO: return error - return; default: - break; - } - - WKTsb.append(")"); - } - - private void constructPointWKT(int pointIndex) { - int firstPointIndex = pointIndex * 2; - int secondPointIndex = firstPointIndex + 1; - int zValueIndex = pointIndex; - int mValueIndex = pointIndex; - - - WKTsb.append(points[firstPointIndex]); - WKTsb.append(" "); - - WKTsb.append(points[secondPointIndex]); - WKTsb.append(" "); - - if (hasZvalues && !Double.isNaN(zValues[zValueIndex])) { - WKTsb.append(zValues[zValueIndex]); - WKTsb.append(" "); - - if (hasMvalues && !Double.isNaN(mValues[mValueIndex])) { - WKTsb.append(mValues[mValueIndex]); - WKTsb.append(" "); - } + throw new IllegalArgumentException(); } - currentPointIndex++; - WKTsb.setLength(WKTsb.length() - 1); // truncate last space - } - - private void constructLineWKT(int pointStartIndex, int pointEndIndex) { - for (int i = pointStartIndex; i < pointEndIndex; i++) { - constructPointWKT(i); - - // add ', ' to separate points, except for the last point - if (i != pointEndIndex - 1) { - WKTsb.append(", "); - } - } - } - - private void constructShapeWKT(int figureStartIndex, int figureEndIndex) { - // Method for constructing shapes (simple Geometry/Geography entities that are contained within a single bracket) - for (int i = figureStartIndex; i < figureEndIndex; i++) { - WKTsb.append("("); - if (i != numberOfFigures - 1) { //not the last figure - constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); - } else { - constructLineWKT(figures[i].getPointOffset(), numberOfPoints); - } - - if (i != figureEndIndex - 1) { - WKTsb.append("), "); - } else { - WKTsb.append(")"); - } - } - } - - private void constructCompoundcurveWKT(int segmentStartIndex, int segmentEndIndex, int pointEndIndex) { - for (int i = segmentStartIndex; i < segmentEndIndex; i++) { - byte segment = segments[i].getSegmentType(); - constructSegmentWKT(i, segment, pointEndIndex); - - if (i == segmentEndIndex - 1) { - WKTsb.append(")"); - break; - } - - switch (segment) { - case 0: - case 2: - if (segments[i + 1].getSegmentType() != 0) { - WKTsb.append("), "); - } - break; - case 1: - case 3: - if (segments[i + 1].getSegmentType() != 1) { - WKTsb.append("), "); - } - break; - default: - return; - } - } - } - - private void constructSegmentWKT(int currentSegment, byte segment, int pointEndIndex) { - switch (segment) { - case 0: - WKTsb.append(", "); - constructLineWKT(currentPointIndex, currentPointIndex + 1); - - if (currentSegment == segments.length - 1) { // last segment - break; - } else if (segments[currentSegment + 1].getSegmentType() != 0) { // not being followed by another line, but not the last segment - currentPointIndex = currentPointIndex - 1; - incrementPointNumStartIfPointNotReused(pointEndIndex); - } - break; - - case 1: - WKTsb.append(", "); - constructLineWKT(currentPointIndex, currentPointIndex + 2); - - if (currentSegment == segments.length - 1) { // last segment - break; - } else if (segments[currentSegment + 1].getSegmentType() != 1) { // not being followed by another arc, but not the last segment - currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused - incrementPointNumStartIfPointNotReused(pointEndIndex); - } - - break; - case 2: - WKTsb.append("("); - constructLineWKT(currentPointIndex, currentPointIndex + 2); - - if (currentSegment == segments.length - 1) { // last segment - break; - } else if (segments[currentSegment + 1].getSegmentType() != 0) { // not being followed by another line, but not the last segment - currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused - incrementPointNumStartIfPointNotReused(pointEndIndex); - } - - break; - case 3: - WKTsb.append("CIRCULARSTRING("); - constructLineWKT(currentPointIndex, currentPointIndex + 3); - - if (currentSegment == segments.length - 1) { // last segment - break; - } else if (segments[currentSegment + 1].getSegmentType() != 1) { // not being followed by another arc - currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused - incrementPointNumStartIfPointNotReused(pointEndIndex); - } - - break; - default: - return; - } + appendToWKTBuffers(")"); } - private void incrementPointNumStartIfPointNotReused(int pointEndIndex) { - // We need to increment PointNumStart if the last point was actually not re-used in the points array. - // 0 for pointNumEnd indicates that this check is not applicable. - if (currentPointIndex + 1 >= pointEndIndex) { - currentPointIndex++; - } - } - - private void constructMultipolygonWKT(int figureStartIndex, int figureEndIndex) { - for (int i = figureStartIndex; i < figureEndIndex; i++) { - if (figures[i].getFiguresAttribute() == 2) { // exterior ring - WKTsb.append("(("); - } else { // interior ring - WKTsb.append("("); - } - - if (i == figures.length - 1) { // last figure - constructLineWKT(figures[i].getPointOffset(), numberOfPoints); - } else { - constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); - } - - if (i == figureEndIndex - 1) { // last polygon of this multipolygon, close off the Multipolygon and return - WKTsb.append("))"); - return; - } else if (figures[i + 1].getFiguresAttribute() == 2) { // not the last polygon, followed by an exterior ring - WKTsb.append(")), "); - } else { // not the last polygon, followed by an interior ring - WKTsb.append("), "); - } - } - } - - private void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex, int segmentStartIndex, int segmentEndIndex) { - for (int i = figureStartIndex; i < figureEndIndex; i++) { - switch (figures[i].getFiguresAttribute()) { - case 1: // line - WKTsb.append("("); - - if (i == figures.length - 1) { - constructLineWKT(currentPointIndex, numberOfPoints); - } else { - constructLineWKT(currentPointIndex, figures[i + 1].getPointOffset()); - //currentPointIndex = figures[i + 1].getPointOffset(); - } - - WKTsb.append(")"); - break; - case 2: // arc - WKTsb.append("CIRCULARSTRING("); - - if (i == figures.length - 1) { - constructLineWKT(currentPointIndex, numberOfPoints); - } else { - constructLineWKT(currentPointIndex, figures[i + 1].getPointOffset()); - //currentPointIndex = figures[i + 1].getPointOffset(); - } - - WKTsb.append(")"); - - break; - case 3: // composite curve - WKTsb.append("COMPOUNDCURVE("); - - int pointEndIndex = 0; - - if (i == figures.length - 1) { - pointEndIndex = numberOfPoints; - } else { - pointEndIndex = figures[i + 1].getPointOffset(); - } - - while (currentPointIndex < pointEndIndex) { - byte segment = segments[segmentStartIndex].getSegmentType(); - constructSegmentWKT(segmentStartIndex, segment, pointEndIndex); - - if (segmentStartIndex >= segmentEndIndex - 1) { - WKTsb.append(")"); - // about to exit while loop, but not the last segment = we are closing Compoundcurve. - } else if (!(currentPointIndex < pointEndIndex)) { - WKTsb.append("))"); - } else { - switch (segment) { - case 0: - case 2: - if (segments[segmentStartIndex + 1].getSegmentType() != 0) { - WKTsb.append("), "); - } - break; - case 1: - case 3: - if (segments[segmentStartIndex + 1].getSegmentType() != 1) { - WKTsb.append("), "); - } - break; - default: - return; - } - } - - segmentStartIndex++; - } - - break; - default: - return; - } - - if (i == figureEndIndex - 1) { - WKTsb.append(")"); - } else { - WKTsb.append(", "); - } - - } - } - - private void constructGeometryCollectionWKT(int shapeEndIndex) { - currentShapeIndex++; - constructGeometryCollectionWKThelper(shapeEndIndex); - } - - private void constructGeometryCollectionWKThelper(int shapeEndIndex) { - //phase 1: assume that there is no multi - stuff and no geometrycollection - while (currentShapeIndex < shapeEndIndex) { - InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(shapes[currentShapeIndex].getOpenGISType()); - - int figureIndex = shapes[currentShapeIndex].getFigureOffset(); - int pointIndexEnd = numberOfPoints; - int figureIndexEnd = numberOfFigures; - int segmentIndexEnd = numberOfSegments; - int shapeIndexEnd = numberOfShapes; - int figureIndexIncrement = 0; - int segmentIndexIncrement = 0; - int localCurrentSegmentIndex = 0; - int localCurrentShapeIndex = 0; - - switch (isd) { - case POINT: - figureIndexIncrement++; - currentShapeIndex++; - break; - case LINESTRING: - case CIRCULARSTRING: - figureIndexIncrement++; - currentShapeIndex++; - pointIndexEnd = figures[figureIndex + 1].getPointOffset(); - break; - case POLYGON: - case CURVEPOLYGON: - if (currentShapeIndex < shapes.length - 1) { - figureIndexEnd = shapes[currentShapeIndex + 1].getFigureOffset(); - } - - figureIndexIncrement = figureIndexEnd - currentFigureIndex; - currentShapeIndex++; - - // Needed to keep track of which segment we are at, inside the for loop - localCurrentSegmentIndex = currentSegmentIndex; - - if (isd.equals(InternalSpatialDatatype.CURVEPOLYGON)) { - // assume Version 2 - - for (int i = currentFigureIndex; i < figureIndexEnd; i++) { - // Only Compoundcurves (with figure attribute 3) can have segments - if (figures[i].getFiguresAttribute() == 3) { - - int pointOffsetEnd; - if (i == figures.length - 1) { - pointOffsetEnd = numberOfPoints; - } else { - pointOffsetEnd = figures[i + 1].getPointOffset(); - } - - int increment = calculateSegmentIncrement(localCurrentSegmentIndex, pointOffsetEnd - figures[i].getPointOffset()); - - segmentIndexIncrement = segmentIndexIncrement + increment; - localCurrentSegmentIndex = localCurrentSegmentIndex + increment; - } - } - } - - segmentIndexEnd = localCurrentSegmentIndex; - - break; - case MULTIPOINT: - case MULTILINESTRING: - case MULTIPOLYGON: - //Multipoint and MultiLineString can go on for multiple Shapes, but eventually - //the parentOffset will signal the end of the object, or it's reached the end of the - //shapes array. - //There is also no possibility that a MultiPoint or MultiLineString would branch - //into another parent. - - int thisShapesParentOffset = shapes[currentShapeIndex].getParentOffset(); - - // Increment shapeStartIndex to account for the shape index that either Multipoint, MultiLineString - // or MultiPolygon takes up - currentShapeIndex++; - while (currentShapeIndex < shapes.length - 1 && - shapes[currentShapeIndex].getParentOffset() != thisShapesParentOffset) { - figureIndexEnd = shapes[currentShapeIndex + 1].getFigureOffset(); - currentShapeIndex++; - } - - figureIndexIncrement = figureIndexEnd - currentFigureIndex; - break; - case GEOMETRYCOLLECTION: - WKTsb.append(isd.getTypeName()); - WKTsb.append("("); - - int geometryCollectionParentIndex = shapes[currentShapeIndex].getParentOffset(); - - // Needed to keep track of which shape we are at, inside the for loop - localCurrentShapeIndex = currentShapeIndex; - - - while (localCurrentShapeIndex < shapes.length - 1 && - shapes[localCurrentShapeIndex + 1].getParentOffset() > geometryCollectionParentIndex) { - localCurrentShapeIndex++; - } - // increment localCurrentShapeIndex one more time since it will be used as a shapeEndIndex parameter - // for constructGeometryCollectionWKT, and the shapeEndIndex parameter is used non-inclusively - localCurrentShapeIndex++; - - currentShapeIndex++; - constructGeometryCollectionWKThelper(localCurrentShapeIndex); - - if (currentShapeIndex < shapeEndIndex) { - WKTsb.append("), "); - } else { - WKTsb.append(")"); - } - - continue; - case COMPOUNDCURVE: - if (currentFigureIndex == figures.length - 1) { - pointIndexEnd = numberOfPoints; - } else { - pointIndexEnd = figures[currentFigureIndex + 1].getPointOffset(); - } - - int increment = calculateSegmentIncrement(currentSegmentIndex, pointIndexEnd - - figures[currentFigureIndex].getPointOffset()); - - segmentIndexIncrement = increment; - segmentIndexEnd = currentSegmentIndex + increment; - figureIndexIncrement++; - currentShapeIndex++; - break; - case FULLGLOBE: - WKTsb.append("FULLGLOBE"); - break; - default: - break; - } - - constructWKT(isd, pointIndexEnd, figureIndexEnd, segmentIndexEnd, shapeIndexEnd); - currentFigureIndex = currentFigureIndex + figureIndexIncrement; - currentSegmentIndex = currentSegmentIndex + segmentIndexIncrement; - - if (currentShapeIndex < shapeEndIndex) { - WKTsb.append(", "); - } - } - } - - //Calculates how many segments will be used by this CompoundCurve - private int calculateSegmentIncrement(int segmentStart, - int pointDifference) { - - int segmentIncrement = 0; - - while (pointDifference > 0) { - switch (segments[segmentStart].getSegmentType()) { - case 0: - pointDifference = pointDifference - 1; - - if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment - break; - } else if (segments[segmentStart + 1].getSegmentType() != 0) { // one point will be reused - pointDifference = pointDifference + 1; - } - break; - case 1: - pointDifference = pointDifference - 2; - - if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment - break; - } else if (segments[segmentStart + 1].getSegmentType() != 1) { // one point will be reused - pointDifference = pointDifference + 1; - } - break; - case 2: - pointDifference = pointDifference - 2; - - if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment - break; - } else if (segments[segmentStart + 1].getSegmentType() != 0) { // one point will be reused - pointDifference = pointDifference + 1; - } - break; - case 3: - pointDifference = pointDifference - 3; - - if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment - break; - } else if (segments[segmentStart + 1].getSegmentType() != 1) { // one point will be reused - pointDifference = pointDifference + 1; - } - break; - default: - return segmentIncrement; - } - segmentStart++; - segmentIncrement++; - } - - return segmentIncrement; - } - - private void parseWKTForSerialization(int startPos, int parentShapeIndex, boolean isGeoCollection) { + protected void parseWKTForSerialization(int startPos, int parentShapeIndex, boolean isGeoCollection) { //after every iteration of this while loop, the currentWktPosition will be set to the //end of the geometry/geography shape, except for the very first iteration of it. //This means that there has to be comma (that separates the previous shape with the next shape), @@ -894,14 +200,10 @@ private void parseWKTForSerialization(int startPos, int parentShapeIndex, boolea return; } else if (wkt.charAt(currentWktPos) == ',') { currentWktPos++; - } else { - //TODO: throw exception here? - //return; } } String nextToken = getNextStringToken().toUpperCase(Locale.US); - String nextPotentialToken; int thisShapeIndex; InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(nextToken); byte fa = 0; @@ -951,21 +253,8 @@ private void parseWKTForSerialization(int startPos, int parentShapeIndex, boolea thisShapeIndex = shapeList.size(); shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { - shapeList.add(new Shape(thisShapeIndex, figureList.size(), InternalSpatialDatatype.POLYGON.getTypeCode())); //exterior polygon - readOpenBracket(); - readShapeWkt(thisShapeIndex, nextToken); - readCloseBracket(); - - if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow - readComma(); - } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop - continue; - } else { // unexpected input - throw new IllegalArgumentException(); - } - } - + readMultiPolygonWkt(thisShapeIndex, nextToken); + break; case "COMPOUNDCURVE": shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); @@ -977,35 +266,7 @@ private void parseWKTForSerialization(int startPos, int parentShapeIndex, boolea case "CURVEPOLYGON": shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { - nextPotentialToken = getNextStringToken().toUpperCase(Locale.US); - if (nextPotentialToken.equals("CIRCULARSTRING")) { - figureList.add(new Figure(FA_ARC, pointList.size())); - readOpenBracket(); - readLineWkt(); - readCloseBracket(); - } else if (nextPotentialToken.equals("COMPOUNDCURVE")) { - figureList.add(new Figure(FA_COMPOSITE_CURVE, pointList.size())); - readOpenBracket(); - readCompoundCurveWkt(true); - readCloseBracket(); - } else if (wkt.charAt(currentWktPos) == '(') { //LineString - figureList.add(new Figure(FA_LINE, pointList.size())); - readOpenBracket(); - readLineWkt(); - readCloseBracket(); - } else { - throw new IllegalArgumentException(); - } - - if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow - readComma(); - } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop - continue; - } else { // unexpected input - throw new IllegalArgumentException(); - } - } + readCurvePolygon(); break; case "GEOMETRYCOLLECTION": @@ -1016,410 +277,13 @@ private void parseWKTForSerialization(int startPos, int parentShapeIndex, boolea break; case "FULLGLOBE": - - break; + throw new IllegalArgumentException("Fullglobe is not supported for Geometry."); default: - break; + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } - //all geometry methods return when the depth reaches 0. ( gives + 1 depth and ) takes away 1 depth. readCloseBracket(); } populateStructures(); } - - private void readCompoundCurveWkt(boolean isFirstIteration) { - while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { - String nextPotentialToken = getNextStringToken().toUpperCase(Locale.US); - if (nextPotentialToken.equals("CIRCULARSTRING")) { - readOpenBracket(); - readSegmentWkt(SEGMENT_FIRST_ARC, isFirstIteration); - readCloseBracket(); - } else if (wkt.charAt(currentWktPos) == '(') {//LineString - readOpenBracket(); - readSegmentWkt(SEGMENT_FIRST_LINE, isFirstIteration); - readCloseBracket(); - } else { - throw new IllegalArgumentException(); - } - - isFirstIteration = false; - - if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow - readComma(); - } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop - continue; - } else { // unexpected input - throw new IllegalArgumentException(); - } - } - } - - private void readPointWkt() { - int numOfCoordinates = 0; - double sign; - double coords[] = new double[4]; - - while (numOfCoordinates < 4) { - sign = 1; - if (wkt.charAt(currentWktPos) == '-') { - sign = -1; - currentWktPos++; - } - - int startPos = currentWktPos; - - if (wkt.charAt(currentWktPos) == ')') { - break; - } - - while (currentWktPos < wkt.length() && - (Character.isDigit(wkt.charAt(currentWktPos)) - || wkt.charAt(currentWktPos) == '.' - || wkt.charAt(currentWktPos) == 'E' - || wkt.charAt(currentWktPos) == 'e')) { - currentWktPos++; - } - - try { - coords[numOfCoordinates] = sign * - new BigDecimal(wkt.substring(startPos, currentWktPos)).doubleValue(); - } catch (Exception e) { //modify to conversion exception - throw new IllegalArgumentException(); - } - - skipWhiteSpaces(); - if (wkt.charAt(currentWktPos) == ',') { - currentWktPos++; - skipWhiteSpaces(); - numOfCoordinates++; - break; - } - skipWhiteSpaces(); - - numOfCoordinates++; - } - - if (numOfCoordinates == 4) { - hasZvalues = true; - hasMvalues = true; - } else if (numOfCoordinates == 3) { - hasZvalues = true; - } - - pointList.add(new Point(coords[0], coords[1], coords[2], coords[3])); - } - - private void readLineWkt() { - while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { - readPointWkt(); - } - } - - private void readShapeWkt(int parentShapeIndex, String nextToken) { - byte fa = FA_POINT; - while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { - if (nextToken.equals("MULTIPOINT")) { - shapeList.add(new Shape(parentShapeIndex, figureList.size(), InternalSpatialDatatype.POINT.getTypeCode())); - } else if (nextToken.equals("MULTILINESTRING")) { - shapeList.add(new Shape(parentShapeIndex, figureList.size(), InternalSpatialDatatype.LINESTRING.getTypeCode())); - } - - if (version == 1) { - if (nextToken.equals("MULTIPOINT")) { - fa = FA_STROKE; - } else if (nextToken.equals("MULTILINESTRING") || nextToken.equals("POLYGON")) { - fa = FA_EXTERIOR_RING; - } - version_one_shape_indexes.add(figureList.size()); - } else if (version == 2) { - if (nextToken.equals("MULTIPOINT") || nextToken.equals("MULTILINESTRING") || - nextToken.equals("POLYGON") || nextToken.equals("MULTIPOLYGON")) { - fa = FA_LINE; - } - } - - figureList.add(new Figure(fa, pointList.size())); - readOpenBracket(); - readLineWkt(); - readCloseBracket(); - - skipWhiteSpaces(); - - if (wkt.charAt(currentWktPos) == ',') { // more rings to follow - readComma(); - } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop - continue; - } else { // unexpected input - throw new IllegalArgumentException(); - } - } - } - - private void readSegmentWkt(int segmentType, boolean isFirstIteration) { - segmentList.add(new Segment((byte) segmentType)); - - int segmentLength = segmentType; - - // under 2 means 0 or 1 (possible values). 0 (line) has 1 point, and 1 (arc) has 2 points, so increment by one - if (segmentLength < 2) { - segmentLength++; - } - - for (int i = 0; i < segmentLength; i++) { - //If a segment type of 2 (first line) or 3 (first arc) is not from the very first iteration of the while loop, - //then the first point has to be a duplicate point from the previous segment, so skip the first point. - if (i == 0 && !isFirstIteration && segmentType >= 2) { - skipFirstPointWkt(); - } else { - readPointWkt(); - } - } - - if (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { - if (segmentType == SEGMENT_FIRST_ARC || segmentType == SEGMENT_ARC) { - readSegmentWkt(SEGMENT_ARC, false); - } else if (segmentType == SEGMENT_FIRST_LINE | segmentType == SEGMENT_LINE) { - readSegmentWkt(SEGMENT_LINE, false); - } - } - } - - private void skipFirstPointWkt() { - int numOfCoordinates = 0; - - while (numOfCoordinates < 4) { - if (wkt.charAt(currentWktPos) == '-') { - currentWktPos++; - } - - if (wkt.charAt(currentWktPos) == ')') { - break; - } - - while (currentWktPos < wkt.length() && - (Character.isDigit(wkt.charAt(currentWktPos)) - || wkt.charAt(currentWktPos) == '.' - || wkt.charAt(currentWktPos) == 'E' - || wkt.charAt(currentWktPos) == 'e')) { - currentWktPos++; - } - - skipWhiteSpaces(); - if (wkt.charAt(currentWktPos) == ',') { - currentWktPos++; - skipWhiteSpaces(); - numOfCoordinates++; - break; - } - skipWhiteSpaces(); - - numOfCoordinates++; - } - } - - private void readOpenBracket() { - skipWhiteSpaces(); - if (wkt.charAt(currentWktPos) == '(') { - currentWktPos++; - skipWhiteSpaces(); - } else { - throw new IllegalArgumentException(); - } - } - - private void readCloseBracket() { - skipWhiteSpaces(); - if (wkt.charAt(currentWktPos) == ')') { - currentWktPos++; - skipWhiteSpaces(); - } else { - throw new IllegalArgumentException(); - } - } - - private void readComma() { - skipWhiteSpaces(); - if (wkt.charAt(currentWktPos) == ',') { - currentWktPos++; - skipWhiteSpaces(); - } else { - throw new IllegalArgumentException(); - } - } - - private boolean hasMoreToken() { - skipWhiteSpaces(); - return currentWktPos < wkt.length(); - } - - private void skipWhiteSpaces() { - while (currentWktPos < wkt.length() && Character.isWhitespace(wkt.charAt(currentWktPos))) { - currentWktPos++; - } - } - - private String getNextStringToken() { - skipWhiteSpaces(); - int endIndex = currentWktPos; - while (endIndex < wkt.length() && Character.isLetter(wkt.charAt(endIndex))) { - endIndex++; - } - int temp = currentWktPos; - currentWktPos = endIndex; - skipWhiteSpaces(); - - return wkt.substring(temp, endIndex); - } - - private void populateStructures() { - if (pointList.size() > 0) { - points = new double[pointList.size() * 2]; - - for (int i = 0; i < pointList.size(); i++) { - points[i * 2] = pointList.get(i).getX(); - points[i * 2 + 1] = pointList.get(i).getY(); - } - - if (hasZvalues) { - zValues = new double[pointList.size()]; - for (int i = 0; i < pointList.size(); i++) { - zValues[i] = pointList.get(i).getZ(); - } - } - - if (hasMvalues) { - mValues = new double[pointList.size()]; - for (int i = 0; i < pointList.size(); i++) { - mValues[i] = pointList.get(i).getM(); - } - } - } - - // if version is 2, then we need to check for potential shapes (polygon & multi-shapes) that were - // given their figure attributes as if it was version 1, since we don't know what would be the - // version of the geometry/geography before we parse the entire WKT. - if (version == 2) { - for (int i = 0; i < version_one_shape_indexes.size(); i++) { - figureList.get(version_one_shape_indexes.get(i)).setFiguresAttribute((byte) 1); - } - } - - if (figureList.size() > 0) { - figures = new Figure[figureList.size()]; - - for (int i = 0; i < figureList.size(); i++) { - figures[i] = figureList.get(i); - } - } - - if (shapeList.size() > 0) { - shapes = new Shape[shapeList.size()]; - - for (int i = 0; i < shapeList.size(); i++) { - shapes[i] = shapeList.get(i); - } - } - - if (segmentList.size() > 0) { - segments = new Segment[segmentList.size()]; - - for (int i = 0; i < segmentList.size(); i++) { - segments[i] = segmentList.get(i); - } - } - - numberOfPoints = pointList.size(); - numberOfFigures = figureList.size(); - numberOfShapes = shapeList.size(); - numberOfSegments = segmentList.size(); - } } - -class Figure { - private byte figuresAttribute; - private int pointOffset; - - Figure(byte figuresAttribute, int pointOffset) { - this.figuresAttribute = figuresAttribute; - this.pointOffset = pointOffset; - } - - public byte getFiguresAttribute() { - return figuresAttribute; - } - - public int getPointOffset() { - return pointOffset; - } - - public void setFiguresAttribute(byte fa) { - figuresAttribute = fa; - } -} - -class Shape { - private int parentOffset; - private int figureOffset; - private byte openGISType; - - Shape(int parentOffset, int figureOffset, byte openGISType) { - this.parentOffset = parentOffset; - this.figureOffset = figureOffset; - this.openGISType = openGISType; - } - - public int getParentOffset() { - return parentOffset; - } - - public int getFigureOffset() { - return figureOffset; - } - - public byte getOpenGISType() { - return openGISType; - } -} - -class Segment { - private byte segmentType; - - Segment(byte segmentType) { - this.segmentType = segmentType; - } - - public byte getSegmentType() { - return segmentType; - } -} - -class Point { - private final double x; - private final double y; - private final double z; - private final double m; - - Point(double x, double y, double z, double m) { - this.x = x; - this.y = y; - this.z = z; - this.m = m; - } - - public double getX() { - return x; - } - - public double getY() { - return y; - } - - public double getZ() { - return z; - } - - public double getM() { - return m; - } -} \ No newline at end of file diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java index 65ed72f09..a2d04a591 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java @@ -889,6 +889,10 @@ else if ((null != jdbcTypeSetByUser) && ((jdbcTypeSetByUser == JDBCType.NVARCHAR case GEOMETRY: param.typeDefinition = SSType.GEOMETRY.toString(); break; + + case GEOGRAPHY: + param.typeDefinition = SSType.GEOGRAPHY.toString(); + break; default: assert false : "Unexpected JDBC type " + dtv.getJdbcType(); break; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index b7451bd89..2b94e8c30 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -1575,6 +1575,15 @@ public final void setGeometry(int n, setValue(n, JDBCType.GEOMETRY, x, JavaType.STRING, false); loggerExternal.exiting(getClassNameLogging(), "setGeometry"); } + + public final void setGeography(int n, + Geography x) throws SQLServerException { + if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) + loggerExternal.entering(getClassNameLogging(), "setGeography", new Object[] {n, x}); + checkClosed(); + setValue(n, JDBCType.GEOGRAPHY, x, JavaType.STRING, false); + loggerExternal.exiting(getClassNameLogging(), "setGeography"); + } public final void setInt(int n, int value) throws SQLServerException { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index c5ce92075..87316e96e 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -2142,6 +2142,22 @@ public Geometry getGeometry(String columnName) throws SQLServerException { loggerExternal.exiting(getClassNameLogging(), "getFloat", value); return value; } + + public Geography getGeography(int columnIndex) throws SQLServerException { + loggerExternal.entering(getClassNameLogging(), "getFloat", columnIndex); + checkClosed(); + Geography value = (Geography) getValue(columnIndex, JDBCType.GEOGRAPHY); + loggerExternal.exiting(getClassNameLogging(), "getFloat", value); + return value; + } + + public Geography getGeography(String columnName) throws SQLServerException { + loggerExternal.entering(getClassNameLogging(), "getFloat", columnName); + checkClosed(); + Geography value = (Geography) getValue(findColumn(columnName), JDBCType.GEOGRAPHY); + loggerExternal.exiting(getClassNameLogging(), "getFloat", value); + return value; + } public int getInt(int columnIndex) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getInt", columnIndex); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java new file mode 100644 index 000000000..6dbb7e4cf --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java @@ -0,0 +1,1282 @@ +package com.microsoft.sqlserver.jdbc; + +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +abstract class SQLServerSpatialDatatype { + protected ByteBuffer buffer; + protected InternalSpatialDatatype internalType; + protected String wkt; + protected String wktNoZM; + protected byte[] wkb; + protected byte[] wkbNoZM; + protected int srid; + protected byte version = 1; + protected int numberOfPoints; + protected int numberOfFigures; + protected int numberOfShapes; + protected int numberOfSegments; + protected StringBuffer WKTsb; + protected StringBuffer WKTsbNoZM; + protected int currentPointIndex = 0; + protected int currentFigureIndex = 0; + protected int currentSegmentIndex = 0; + protected double points[]; + protected double zValues[]; + protected double mValues[]; + protected Figure figures[]; + protected Shape shapes[]; + protected Segment segments[]; + + //serialization properties + protected boolean hasZvalues = false; + protected boolean hasMvalues = false; + protected boolean isValid = false; + protected boolean isSinglePoint = false; + protected boolean isSingleLineSegment = false; + protected boolean isLargerThanHemisphere = false; + protected boolean isNull = true; + + protected final byte FA_INTERIOR_RING = 0; + protected final byte FA_STROKE = 1; + protected final byte FA_EXTERIOR_RING = 2; + + protected final byte FA_POINT = 0; + protected final byte FA_LINE = 1; + protected final byte FA_ARC = 2; + protected final byte FA_COMPOSITE_CURVE = 3; + + // WKT to WKB properties + protected int currentWktPos = 0; + protected List pointList = new ArrayList(); + protected List
figureList = new ArrayList
(); + protected List shapeList = new ArrayList(); + protected List segmentList = new ArrayList(); + + private int currentShapeIndex = 0; + private byte serializationProperties = 0; + + private final byte SEGMENT_LINE = 0; + private final byte SEGMENT_ARC = 1; + private final byte SEGMENT_FIRST_LINE = 2; + private final byte SEGMENT_FIRST_ARC = 3; + + private final byte hasZvaluesMask = 0b00000001; + private final byte hasMvaluesMask = 0b00000010; + private final byte isValidMask = 0b00000100; + private final byte isSinglePointMask = 0b00001000; + private final byte isSingleLineSegmentMask = 0b00010000; + private final byte isLargerThanHemisphereMask = 0b00100000; + + private List version_one_shape_indexes = new ArrayList(); + + protected abstract void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, + int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd); + + protected abstract void parseWKTForSerialization(int startPos, int parentShapeIndex, boolean isGeoCollection); + + protected void serializeToWkb(boolean noZM) { + ByteBuffer buf = ByteBuffer.allocate(determineWkbCapacity()); + createSerializationProperties(); + + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(srid); + buf.put(version); + buf.put(serializationProperties); + + if (!isSinglePoint && !isSingleLineSegment) { + buf.putInt(numberOfPoints); + } + + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(points[2 * i]); + buf.putDouble(points[2 * i + 1]); + } + + if (!noZM ) { + if (hasZvalues) { + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(zValues[i]); + } + } + + if (hasMvalues) { + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(mValues[i]); + } + } + } + + if (isSinglePoint || isSingleLineSegment) { + wkb = buf.array(); + return; + } + + buf.putInt(numberOfFigures); + for (int i = 0; i < numberOfFigures; i++) { + buf.put(figures[i].getFiguresAttribute()); + buf.putInt(figures[i].getPointOffset()); + } + + buf.putInt(numberOfShapes); + for (int i = 0; i < numberOfShapes; i++) { + buf.putInt(shapes[i].getParentOffset()); + buf.putInt(shapes[i].getFigureOffset()); + buf.put(shapes[i].getOpenGISType()); + } + + if (version == 2 && null != segments) { + buf.putInt(numberOfSegments); + for (int i = 0; i < numberOfSegments; i++) { + buf.put(segments[i].getSegmentType()); + } + } + + if (noZM) { + wkbNoZM = buf.array(); + } else { + wkb = buf.array(); + + } + return; + } + + protected void parseWkb() { + srid = buffer.getInt(); + version = buffer.get(); + serializationProperties = buffer.get(); + + interpretSerializationPropBytes(); + + readNumberOfPoints(); + + readPoints(); + + if (hasZvalues) { + readZvalues(); + } + + if (hasMvalues) { + readMvalues(); + } + + //TODO: do I need to do anything when it's isSinglePoint or isSingleLineSegment? + if (!(isSinglePoint || isSingleLineSegment)) { + readNumberOfFigures(); + + readFigures(); + + readNumberOfShapes(); + + readShapes(); + } + + determineInternalType(); + + if (version == 2 && internalType.getTypeCode() != 8 && internalType.getTypeCode() != 11) { + readNumberOfSegments(); + + readSegments(); + } + } + + protected void constructPointWKT(int pointIndex) { + int firstPointIndex = pointIndex * 2; + int secondPointIndex = firstPointIndex + 1; + int zValueIndex = pointIndex; + int mValueIndex = pointIndex; + + if (points[firstPointIndex] % 1 == 0) { + appendToWKTBuffers((int) points[firstPointIndex]); + } else { + appendToWKTBuffers(points[firstPointIndex]); + } + appendToWKTBuffers(" "); + + if (points[secondPointIndex] % 1 == 0) { + appendToWKTBuffers((int) points[secondPointIndex]); + } else { + appendToWKTBuffers(points[secondPointIndex]); + } + appendToWKTBuffers(" "); + + if (hasZvalues && !Double.isNaN(zValues[zValueIndex])) { + if (zValues[zValueIndex] % 1 == 0) { + WKTsb.append((int) zValues[zValueIndex]); + } else { + WKTsb.append(zValues[zValueIndex]); + } + WKTsb.append(" "); + + if (hasMvalues && !Double.isNaN(mValues[mValueIndex])) { + if (mValues[mValueIndex] % 1 == 0) { + WKTsb.append((int) mValues[mValueIndex]); + } else { + WKTsb.append(mValues[mValueIndex]); + } + WKTsb.append(" "); + } + } + + currentPointIndex++; + // truncate last space + WKTsb.setLength(WKTsb.length() - 1); + WKTsbNoZM.setLength(WKTsbNoZM.length() - 1); + } + + protected void constructLineWKT(int pointStartIndex, int pointEndIndex) { + for (int i = pointStartIndex; i < pointEndIndex; i++) { + constructPointWKT(i); + + // add ', ' to separate points, except for the last point + if (i != pointEndIndex - 1) { + appendToWKTBuffers(", "); + } + } + } + + protected void constructShapeWKT(int figureStartIndex, int figureEndIndex) { + // Method for constructing shapes (simple Geometry/Geography entities that are contained within a single bracket) + for (int i = figureStartIndex; i < figureEndIndex; i++) { + appendToWKTBuffers("("); + if (i != numberOfFigures - 1) { //not the last figure + constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); + } else { + constructLineWKT(figures[i].getPointOffset(), numberOfPoints); + } + + if (i != figureEndIndex - 1) { + appendToWKTBuffers("), "); + } else { + appendToWKTBuffers(")"); + } + } + } + + protected void constructCompoundcurveWKT(int segmentStartIndex, int segmentEndIndex, int pointEndIndex) { + for (int i = segmentStartIndex; i < segmentEndIndex; i++) { + byte segment = segments[i].getSegmentType(); + constructSegmentWKT(i, segment, pointEndIndex); + + if (i == segmentEndIndex - 1) { + appendToWKTBuffers(")"); + break; + } + + switch (segment) { + case 0: + case 2: + if (segments[i + 1].getSegmentType() != 0) { + appendToWKTBuffers("), "); + } + break; + case 1: + case 3: + if (segments[i + 1].getSegmentType() != 1) { + appendToWKTBuffers("), "); + } + break; + default: + return; + } + } + } + + protected void constructMultipolygonWKT(int figureStartIndex, int figureEndIndex) { + for (int i = figureStartIndex; i < figureEndIndex; i++) { + if (figures[i].getFiguresAttribute() == 2) { // exterior ring + appendToWKTBuffers("(("); + } else { // interior ring + appendToWKTBuffers("("); + } + + if (i == figures.length - 1) { // last figure + constructLineWKT(figures[i].getPointOffset(), numberOfPoints); + } else { + constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); + } + + if (i == figureEndIndex - 1) { // last polygon of this multipolygon, close off the Multipolygon and return + appendToWKTBuffers("))"); + return; + } else if (figures[i + 1].getFiguresAttribute() == 2) { // not the last polygon, followed by an exterior ring + appendToWKTBuffers(")), "); + } else { // not the last polygon, followed by an interior ring + appendToWKTBuffers("), "); + } + } + } + + protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex, int segmentStartIndex, int segmentEndIndex) { + for (int i = figureStartIndex; i < figureEndIndex; i++) { + switch (figures[i].getFiguresAttribute()) { + case 1: // line + appendToWKTBuffers("("); + + if (i == figures.length - 1) { + constructLineWKT(currentPointIndex, numberOfPoints); + } else { + constructLineWKT(currentPointIndex, figures[i + 1].getPointOffset()); + //currentPointIndex = figures[i + 1].getPointOffset(); + } + + appendToWKTBuffers(")"); + break; + case 2: // arc + appendToWKTBuffers("CIRCULARSTRING("); + + if (i == figures.length - 1) { + constructLineWKT(currentPointIndex, numberOfPoints); + } else { + constructLineWKT(currentPointIndex, figures[i + 1].getPointOffset()); + //currentPointIndex = figures[i + 1].getPointOffset(); + } + + appendToWKTBuffers(")"); + + break; + case 3: // composite curve + appendToWKTBuffers("COMPOUNDCURVE("); + + int pointEndIndex = 0; + + if (i == figures.length - 1) { + pointEndIndex = numberOfPoints; + } else { + pointEndIndex = figures[i + 1].getPointOffset(); + } + + while (currentPointIndex < pointEndIndex) { + byte segment = segments[segmentStartIndex].getSegmentType(); + constructSegmentWKT(segmentStartIndex, segment, pointEndIndex); + + if (segmentStartIndex >= segmentEndIndex - 1) { + appendToWKTBuffers(")"); + // about to exit while loop, but not the last segment = we are closing Compoundcurve. + } else if (!(currentPointIndex < pointEndIndex)) { + appendToWKTBuffers("))"); + } else { + switch (segment) { + case 0: + case 2: + if (segments[segmentStartIndex + 1].getSegmentType() != 0) { + appendToWKTBuffers("), "); + } + break; + case 1: + case 3: + if (segments[segmentStartIndex + 1].getSegmentType() != 1) { + appendToWKTBuffers("), "); + } + break; + default: + return; + } + } + + segmentStartIndex++; + } + + break; + default: + return; + } + + if (i == figureEndIndex - 1) { + appendToWKTBuffers(")"); + } else { + appendToWKTBuffers(", "); + } + + } + } + + protected void constructSegmentWKT(int currentSegment, byte segment, int pointEndIndex) { + switch (segment) { + case 0: + appendToWKTBuffers(", "); + constructLineWKT(currentPointIndex, currentPointIndex + 1); + + if (currentSegment == segments.length - 1) { // last segment + break; + } else if (segments[currentSegment + 1].getSegmentType() != 0) { // not being followed by another line, but not the last segment + currentPointIndex = currentPointIndex - 1; + incrementPointNumStartIfPointNotReused(pointEndIndex); + } + break; + + case 1: + appendToWKTBuffers(", "); + constructLineWKT(currentPointIndex, currentPointIndex + 2); + + if (currentSegment == segments.length - 1) { // last segment + break; + } else if (segments[currentSegment + 1].getSegmentType() != 1) { // not being followed by another arc, but not the last segment + currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointEndIndex); + } + + break; + case 2: + appendToWKTBuffers("("); + constructLineWKT(currentPointIndex, currentPointIndex + 2); + + if (currentSegment == segments.length - 1) { // last segment + break; + } else if (segments[currentSegment + 1].getSegmentType() != 0) { // not being followed by another line, but not the last segment + currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointEndIndex); + } + + break; + case 3: + appendToWKTBuffers("CIRCULARSTRING("); + constructLineWKT(currentPointIndex, currentPointIndex + 3); + + if (currentSegment == segments.length - 1) { // last segment + break; + } else if (segments[currentSegment + 1].getSegmentType() != 1) { // not being followed by another arc + currentPointIndex = currentPointIndex - 1; // only increment pointNumStart by one less than what we should be, since the last point will be reused + incrementPointNumStartIfPointNotReused(pointEndIndex); + } + + break; + default: + return; + } + } + + protected void constructGeometryCollectionWKT(int shapeEndIndex) { + currentShapeIndex++; + constructGeometryCollectionWKThelper(shapeEndIndex); + } + + protected void readPointWkt() { + int numOfCoordinates = 0; + double sign; + double coords[] = new double[4]; + + while (numOfCoordinates < 4) { + sign = 1; + if (wkt.charAt(currentWktPos) == '-') { + sign = -1; + currentWktPos++; + } + + int startPos = currentWktPos; + + if (wkt.charAt(currentWktPos) == ')') { + break; + } + + while (currentWktPos < wkt.length() && + (Character.isDigit(wkt.charAt(currentWktPos)) + || wkt.charAt(currentWktPos) == '.' + || wkt.charAt(currentWktPos) == 'E' + || wkt.charAt(currentWktPos) == 'e')) { + currentWktPos++; + } + + try { + coords[numOfCoordinates] = sign * + new BigDecimal(wkt.substring(startPos, currentWktPos)).doubleValue(); + } catch (Exception e) { //modify to conversion exception + throw new IllegalArgumentException(); + } + + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + skipWhiteSpaces(); + numOfCoordinates++; + break; + } + skipWhiteSpaces(); + + numOfCoordinates++; + } + + if (numOfCoordinates == 4) { + hasZvalues = true; + hasMvalues = true; + } else if (numOfCoordinates == 3) { + hasZvalues = true; + } + + pointList.add(new Point(coords[0], coords[1], coords[2], coords[3])); + } + + protected void readLineWkt() { + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + readPointWkt(); + } + } + + protected void readShapeWkt(int parentShapeIndex, String nextToken) { + byte fa = FA_POINT; + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + if (nextToken.equals("MULTIPOINT")) { + shapeList.add(new Shape(parentShapeIndex, figureList.size(), InternalSpatialDatatype.POINT.getTypeCode())); + } else if (nextToken.equals("MULTILINESTRING")) { + shapeList.add(new Shape(parentShapeIndex, figureList.size(), InternalSpatialDatatype.LINESTRING.getTypeCode())); + } + + if (version == 1) { + if (nextToken.equals("MULTIPOINT")) { + fa = FA_STROKE; + } else if (nextToken.equals("MULTILINESTRING") || nextToken.equals("POLYGON")) { + fa = FA_EXTERIOR_RING; + } + version_one_shape_indexes.add(figureList.size()); + } else if (version == 2) { + if (nextToken.equals("MULTIPOINT") || nextToken.equals("MULTILINESTRING") || + nextToken.equals("POLYGON") || nextToken.equals("MULTIPOLYGON")) { + fa = FA_LINE; + } + } + + figureList.add(new Figure(fa, pointList.size())); + readOpenBracket(); + readLineWkt(); + readCloseBracket(); + + skipWhiteSpaces(); + + if (wkt.charAt(currentWktPos) == ',') { // more rings to follow + readComma(); + } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop + continue; + } else { // unexpected input + throw new IllegalArgumentException(); + } + } + } + + protected void readCurvePolygon() { + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + String nextPotentialToken = getNextStringToken().toUpperCase(Locale.US); + if (nextPotentialToken.equals("CIRCULARSTRING")) { + figureList.add(new Figure(FA_ARC, pointList.size())); + readOpenBracket(); + readLineWkt(); + readCloseBracket(); + } else if (nextPotentialToken.equals("COMPOUNDCURVE")) { + figureList.add(new Figure(FA_COMPOSITE_CURVE, pointList.size())); + readOpenBracket(); + readCompoundCurveWkt(true); + readCloseBracket(); + } else if (wkt.charAt(currentWktPos) == '(') { //LineString + figureList.add(new Figure(FA_LINE, pointList.size())); + readOpenBracket(); + readLineWkt(); + readCloseBracket(); + } else { + throw new IllegalArgumentException(); + } + + if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow + readComma(); + } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop + continue; + } else { // unexpected input + throw new IllegalArgumentException(); + } + } + } + + protected void readMultiPolygonWkt(int thisShapeIndex, String nextToken) { + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + shapeList.add(new Shape(thisShapeIndex, figureList.size(), InternalSpatialDatatype.POLYGON.getTypeCode())); //exterior polygon + readOpenBracket(); + readShapeWkt(thisShapeIndex, nextToken); + readCloseBracket(); + + if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow + readComma(); + } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop + continue; + } else { // unexpected input + throw new IllegalArgumentException(); + } + } + } + + + protected void readSegmentWkt(int segmentType, boolean isFirstIteration) { + segmentList.add(new Segment((byte) segmentType)); + + int segmentLength = segmentType; + + // under 2 means 0 or 1 (possible values). 0 (line) has 1 point, and 1 (arc) has 2 points, so increment by one + if (segmentLength < 2) { + segmentLength++; + } + + for (int i = 0; i < segmentLength; i++) { + //If a segment type of 2 (first line) or 3 (first arc) is not from the very first iteration of the while loop, + //then the first point has to be a duplicate point from the previous segment, so skip the first point. + if (i == 0 && !isFirstIteration && segmentType >= 2) { + skipFirstPointWkt(); + } else { + readPointWkt(); + } + } + + if (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + if (segmentType == SEGMENT_FIRST_ARC || segmentType == SEGMENT_ARC) { + readSegmentWkt(SEGMENT_ARC, false); + } else if (segmentType == SEGMENT_FIRST_LINE | segmentType == SEGMENT_LINE) { + readSegmentWkt(SEGMENT_LINE, false); + } + } + } + + protected void readCompoundCurveWkt(boolean isFirstIteration) { + while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + String nextPotentialToken = getNextStringToken().toUpperCase(Locale.US); + if (nextPotentialToken.equals("CIRCULARSTRING")) { + readOpenBracket(); + readSegmentWkt(SEGMENT_FIRST_ARC, isFirstIteration); + readCloseBracket(); + } else if (wkt.charAt(currentWktPos) == '(') {//LineString + readOpenBracket(); + readSegmentWkt(SEGMENT_FIRST_LINE, isFirstIteration); + readCloseBracket(); + } else { + throw new IllegalArgumentException(); + } + + isFirstIteration = false; + + if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow + readComma(); + } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop + continue; + } else { // unexpected input + throw new IllegalArgumentException(); + } + } + } + + protected String getNextStringToken() { + skipWhiteSpaces(); + int endIndex = currentWktPos; + while (endIndex < wkt.length() && Character.isLetter(wkt.charAt(endIndex))) { + endIndex++; + } + int temp = currentWktPos; + currentWktPos = endIndex; + skipWhiteSpaces(); + + return wkt.substring(temp, endIndex); + } + + protected void populateStructures() { + if (pointList.size() > 0) { + points = new double[pointList.size() * 2]; + + for (int i = 0; i < pointList.size(); i++) { + points[i * 2] = pointList.get(i).getX(); + points[i * 2 + 1] = pointList.get(i).getY(); + } + + if (hasZvalues) { + zValues = new double[pointList.size()]; + for (int i = 0; i < pointList.size(); i++) { + zValues[i] = pointList.get(i).getZ(); + } + } + + if (hasMvalues) { + mValues = new double[pointList.size()]; + for (int i = 0; i < pointList.size(); i++) { + mValues[i] = pointList.get(i).getM(); + } + } + } + + // if version is 2, then we need to check for potential shapes (polygon & multi-shapes) that were + // given their figure attributes as if it was version 1, since we don't know what would be the + // version of the geometry/geography before we parse the entire WKT. + if (version == 2) { + for (int i = 0; i < version_one_shape_indexes.size(); i++) { + figureList.get(version_one_shape_indexes.get(i)).setFiguresAttribute((byte) 1); + } + } + + if (figureList.size() > 0) { + figures = new Figure[figureList.size()]; + + for (int i = 0; i < figureList.size(); i++) { + figures[i] = figureList.get(i); + } + } + + if (shapeList.size() > 0) { + shapes = new Shape[shapeList.size()]; + + for (int i = 0; i < shapeList.size(); i++) { + shapes[i] = shapeList.get(i); + } + } + + if (segmentList.size() > 0) { + segments = new Segment[segmentList.size()]; + + for (int i = 0; i < segmentList.size(); i++) { + segments[i] = segmentList.get(i); + } + } + + numberOfPoints = pointList.size(); + numberOfFigures = figureList.size(); + numberOfShapes = shapeList.size(); + numberOfSegments = segmentList.size(); + } + + protected void readOpenBracket() { + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == '(') { + currentWktPos++; + skipWhiteSpaces(); + } else { + throw new IllegalArgumentException(); + } + } + + protected void readCloseBracket() { + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == ')') { + currentWktPos++; + skipWhiteSpaces(); + } else { + throw new IllegalArgumentException(); + } + } + + protected boolean hasMoreToken() { + skipWhiteSpaces(); + return currentWktPos < wkt.length(); + } + + private void createSerializationProperties() { + serializationProperties = 0; + if (hasZvalues) { + serializationProperties += hasZvaluesMask; + } + + if (hasMvalues) { + serializationProperties += hasMvaluesMask; + } + + if (isValid) { + serializationProperties += isValidMask; + } + + if (isSinglePoint) { + serializationProperties += isSinglePointMask; + } + + if (isSingleLineSegment) { + serializationProperties += isSingleLineSegmentMask; + } + + //TODO look into how the isLargerThanHemisphere is created + if (version == 2) { + if (isLargerThanHemisphere) { + serializationProperties += isLargerThanHemisphereMask; + } + } + } + + private int determineWkbCapacity() { + int totalSize = 0; + + totalSize+=6; // SRID + version + SerializationPropertiesByte + + if (isSinglePoint || isSingleLineSegment) { + totalSize += 16 * numberOfPoints; + + if (hasZvalues) { + totalSize += 8 * numberOfPoints; + } + + if (hasMvalues) { + totalSize += 8 * numberOfPoints; + } + + return totalSize; + } + + int pointSize = 16; + if (hasZvalues) { + pointSize += 8; + } + + if (hasMvalues) { + pointSize += 8; + } + + totalSize += 12; // 4 bytes for 3 ints, each representing the number of points, shapes and figures + totalSize += numberOfPoints * pointSize; + totalSize += numberOfFigures * 5; + totalSize += numberOfShapes * 9; + + if (version == 2) { + totalSize += 4; // 4 bytes for 1 int, representing the number of segments + totalSize += numberOfSegments; + } + + return totalSize; + } + + private void interpretSerializationPropBytes() { + hasZvalues = (serializationProperties & hasZvaluesMask) != 0; + hasMvalues = (serializationProperties & hasMvaluesMask) != 0; + isValid = (serializationProperties & isValidMask) != 0; + isSinglePoint = (serializationProperties & isSinglePointMask) != 0; + isSingleLineSegment = (serializationProperties & isSingleLineSegmentMask) != 0; + isLargerThanHemisphere = (serializationProperties & isLargerThanHemisphereMask) != 0; + } + + private void readNumberOfPoints() { + if (isSinglePoint) { + numberOfPoints = 1; + } else if (isSingleLineSegment) { + numberOfPoints = 2; + } else { + numberOfPoints = buffer.getInt(); + } + } + + 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 readNumberOfFigures() { + numberOfFigures = buffer.getInt(); + } + + private void readFigures() { + byte fa; + int po; + figures = new Figure[numberOfFigures]; + for (int i = 0; i < numberOfFigures; i++) { + fa = buffer.get(); + po = buffer.getInt(); + figures[i] = new Figure(fa, po); + } + } + + private void readNumberOfShapes() { + numberOfShapes = buffer.getInt(); + } + + private void readShapes() { + int po; + int fo; + byte ogt; + shapes = new Shape[numberOfShapes]; + for (int i = 0; i < numberOfShapes; i++) { + po = buffer.getInt(); + fo = buffer.getInt(); + ogt = buffer.get(); + shapes[i] = new Shape(po, fo, ogt); + } + } + + private void readNumberOfSegments() { + numberOfSegments = buffer.getInt(); + } + + private void readSegments() { + byte st; + segments = new Segment[numberOfSegments]; + for (int i = 0; i < numberOfSegments; i++) { + st = buffer.get(); + segments[i] = new Segment(st); + } + } + + private void determineInternalType() { + if (isSinglePoint) { + internalType = InternalSpatialDatatype.POINT; + } else if (isSingleLineSegment) { + internalType = InternalSpatialDatatype.LINESTRING; + } else { + internalType = InternalSpatialDatatype.valueOf(shapes[0].getOpenGISType()); + } + } + + private void incrementPointNumStartIfPointNotReused(int pointEndIndex) { + // We need to increment PointNumStart if the last point was actually not re-used in the points array. + // 0 for pointNumEnd indicates that this check is not applicable. + if (currentPointIndex + 1 >= pointEndIndex) { + currentPointIndex++; + } + } + + private void constructGeometryCollectionWKThelper(int shapeEndIndex) { + //phase 1: assume that there is no multi - stuff and no geometrycollection + while (currentShapeIndex < shapeEndIndex) { + InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(shapes[currentShapeIndex].getOpenGISType()); + + int figureIndex = shapes[currentShapeIndex].getFigureOffset(); + int pointIndexEnd = numberOfPoints; + int figureIndexEnd = numberOfFigures; + int segmentIndexEnd = numberOfSegments; + int shapeIndexEnd = numberOfShapes; + int figureIndexIncrement = 0; + int segmentIndexIncrement = 0; + int localCurrentSegmentIndex = 0; + int localCurrentShapeIndex = 0; + + switch (isd) { + case POINT: + figureIndexIncrement++; + currentShapeIndex++; + break; + case LINESTRING: + case CIRCULARSTRING: + figureIndexIncrement++; + currentShapeIndex++; + pointIndexEnd = figures[figureIndex + 1].getPointOffset(); + break; + case POLYGON: + case CURVEPOLYGON: + if (currentShapeIndex < shapes.length - 1) { + figureIndexEnd = shapes[currentShapeIndex + 1].getFigureOffset(); + } + + figureIndexIncrement = figureIndexEnd - currentFigureIndex; + currentShapeIndex++; + + // Needed to keep track of which segment we are at, inside the for loop + localCurrentSegmentIndex = currentSegmentIndex; + + if (isd.equals(InternalSpatialDatatype.CURVEPOLYGON)) { + // assume Version 2 + + for (int i = currentFigureIndex; i < figureIndexEnd; i++) { + // Only Compoundcurves (with figure attribute 3) can have segments + if (figures[i].getFiguresAttribute() == 3) { + + int pointOffsetEnd; + if (i == figures.length - 1) { + pointOffsetEnd = numberOfPoints; + } else { + pointOffsetEnd = figures[i + 1].getPointOffset(); + } + + int increment = calculateSegmentIncrement(localCurrentSegmentIndex, pointOffsetEnd - figures[i].getPointOffset()); + + segmentIndexIncrement = segmentIndexIncrement + increment; + localCurrentSegmentIndex = localCurrentSegmentIndex + increment; + } + } + } + + segmentIndexEnd = localCurrentSegmentIndex; + + break; + case MULTIPOINT: + case MULTILINESTRING: + case MULTIPOLYGON: + //Multipoint and MultiLineString can go on for multiple Shapes, but eventually + //the parentOffset will signal the end of the object, or it's reached the end of the + //shapes array. + //There is also no possibility that a MultiPoint or MultiLineString would branch + //into another parent. + + int thisShapesParentOffset = shapes[currentShapeIndex].getParentOffset(); + + // Increment shapeStartIndex to account for the shape index that either Multipoint, MultiLineString + // or MultiPolygon takes up + currentShapeIndex++; + while (currentShapeIndex < shapes.length - 1 && + shapes[currentShapeIndex].getParentOffset() != thisShapesParentOffset) { + figureIndexEnd = shapes[currentShapeIndex + 1].getFigureOffset(); + currentShapeIndex++; + } + + figureIndexIncrement = figureIndexEnd - currentFigureIndex; + break; + case GEOMETRYCOLLECTION: + appendToWKTBuffers(isd.getTypeName()); + appendToWKTBuffers("("); + + int geometryCollectionParentIndex = shapes[currentShapeIndex].getParentOffset(); + + // Needed to keep track of which shape we are at, inside the for loop + localCurrentShapeIndex = currentShapeIndex; + + + while (localCurrentShapeIndex < shapes.length - 1 && + shapes[localCurrentShapeIndex + 1].getParentOffset() > geometryCollectionParentIndex) { + localCurrentShapeIndex++; + } + // increment localCurrentShapeIndex one more time since it will be used as a shapeEndIndex parameter + // for constructGeometryCollectionWKT, and the shapeEndIndex parameter is used non-inclusively + localCurrentShapeIndex++; + + currentShapeIndex++; + constructGeometryCollectionWKThelper(localCurrentShapeIndex); + + if (currentShapeIndex < shapeEndIndex) { + appendToWKTBuffers("), "); + } else { + appendToWKTBuffers(")"); + } + + continue; + case COMPOUNDCURVE: + if (currentFigureIndex == figures.length - 1) { + pointIndexEnd = numberOfPoints; + } else { + pointIndexEnd = figures[currentFigureIndex + 1].getPointOffset(); + } + + int increment = calculateSegmentIncrement(currentSegmentIndex, pointIndexEnd - + figures[currentFigureIndex].getPointOffset()); + + segmentIndexIncrement = increment; + segmentIndexEnd = currentSegmentIndex + increment; + figureIndexIncrement++; + currentShapeIndex++; + break; + case FULLGLOBE: + appendToWKTBuffers("FULLGLOBE"); + break; + default: + break; + } + + constructWKT(isd, pointIndexEnd, figureIndexEnd, segmentIndexEnd, shapeIndexEnd); + currentFigureIndex = currentFigureIndex + figureIndexIncrement; + currentSegmentIndex = currentSegmentIndex + segmentIndexIncrement; + + if (currentShapeIndex < shapeEndIndex) { + appendToWKTBuffers(", "); + } + } + } + + //Calculates how many segments will be used by this CompoundCurve + private int calculateSegmentIncrement(int segmentStart, + int pointDifference) { + + int segmentIncrement = 0; + + while (pointDifference > 0) { + switch (segments[segmentStart].getSegmentType()) { + case 0: + pointDifference = pointDifference - 1; + + if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment + break; + } else if (segments[segmentStart + 1].getSegmentType() != 0) { // one point will be reused + pointDifference = pointDifference + 1; + } + break; + case 1: + pointDifference = pointDifference - 2; + + if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment + break; + } else if (segments[segmentStart + 1].getSegmentType() != 1) { // one point will be reused + pointDifference = pointDifference + 1; + } + break; + case 2: + pointDifference = pointDifference - 2; + + if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment + break; + } else if (segments[segmentStart + 1].getSegmentType() != 0) { // one point will be reused + pointDifference = pointDifference + 1; + } + break; + case 3: + pointDifference = pointDifference - 3; + + if (segmentStart == segments.length - 1 || pointDifference < 1) { // last segment + break; + } else if (segments[segmentStart + 1].getSegmentType() != 1) { // one point will be reused + pointDifference = pointDifference + 1; + } + break; + default: + return segmentIncrement; + } + segmentStart++; + segmentIncrement++; + } + + return segmentIncrement; + } + + private void skipFirstPointWkt() { + int numOfCoordinates = 0; + + while (numOfCoordinates < 4) { + if (wkt.charAt(currentWktPos) == '-') { + currentWktPos++; + } + + if (wkt.charAt(currentWktPos) == ')') { + break; + } + + while (currentWktPos < wkt.length() && + (Character.isDigit(wkt.charAt(currentWktPos)) + || wkt.charAt(currentWktPos) == '.' + || wkt.charAt(currentWktPos) == 'E' + || wkt.charAt(currentWktPos) == 'e')) { + currentWktPos++; + } + + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + skipWhiteSpaces(); + numOfCoordinates++; + break; + } + skipWhiteSpaces(); + + numOfCoordinates++; + } + } + + private void readComma() { + skipWhiteSpaces(); + if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + skipWhiteSpaces(); + } else { + throw new IllegalArgumentException(); + } + } + + private void skipWhiteSpaces() { + while (currentWktPos < wkt.length() && Character.isWhitespace(wkt.charAt(currentWktPos))) { + currentWktPos++; + } + } + + protected void appendToWKTBuffers(Object o) { + WKTsb.append(o); + WKTsbNoZM.append(o); + } +} + + +class Figure { + private byte figuresAttribute; + private int pointOffset; + + Figure(byte figuresAttribute, int pointOffset) { + this.figuresAttribute = figuresAttribute; + this.pointOffset = pointOffset; + } + + public byte getFiguresAttribute() { + return figuresAttribute; + } + + public int getPointOffset() { + return pointOffset; + } + + public void setFiguresAttribute(byte fa) { + figuresAttribute = fa; + } +} + +class Shape { + private int parentOffset; + private int figureOffset; + private byte openGISType; + + Shape(int parentOffset, int figureOffset, byte openGISType) { + this.parentOffset = parentOffset; + this.figureOffset = figureOffset; + this.openGISType = openGISType; + } + + public int getParentOffset() { + return parentOffset; + } + + public int getFigureOffset() { + return figureOffset; + } + + public byte getOpenGISType() { + return openGISType; + } +} + +class Segment { + private byte segmentType; + + Segment(byte segmentType) { + this.segmentType = segmentType; + } + + public byte getSegmentType() { + return segmentType; + } +} + +class Point { + private final double x; + private final double y; + private final double z; + private final double m; + + Point(double x, double y, double z, double m) { + this.x = x; + this.y = y; + this.z = z; + this.m = m; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + public double getZ() { + return z; + } + + public double getM() { + return m; + } +} \ No newline at end of file diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java index 447ec0549..64906464e 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java @@ -1632,7 +1632,10 @@ else if (JDBCType.SQL_VARIANT == jdbcType) { op.execute(this, String.valueOf(value)); } else if (JDBCType.GEOMETRY == jdbcType) { - op.execute(this, ((Geometry) value).getWkb()); + op.execute(this, ((Geometry) value).serialize()); + } + else if (JDBCType.GEOGRAPHY == jdbcType) { + op.execute(this, ((Geography) value).serialize()); } else { if (null != cryptoMeta) { diff --git a/src/main/java/microsoft/sql/Types.java b/src/main/java/microsoft/sql/Types.java index ecd7eddca..b2e7cb5d0 100644 --- a/src/main/java/microsoft/sql/Types.java +++ b/src/main/java/microsoft/sql/Types.java @@ -58,5 +58,13 @@ private Types() { */ public static final int SQL_VARIANT = -156; + /** + * The constant in the Java programming language, sometimes referred to as a type code, that identifies the Microsoft SQL type GEOMETRY. + */ public static final int GEOMETRY = -157; + + /** + * The constant in the Java programming language, sometimes referred to as a type code, that identifies the Microsoft SQL type GEOGRAPHY. + */ + public static final int GEOGRAPHY = -158; } From 07d0a9f553d98a0ec35909819d010e9a1006afec Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Wed, 1 Nov 2017 15:46:23 -0700 Subject: [PATCH 08/15] Handle Empty cases and added sanity testing for spatial datatypes --- .../com/microsoft/sqlserver/jdbc/DDC.java | 2 +- .../microsoft/sqlserver/jdbc/Geography.java | 322 +++++---- .../microsoft/sqlserver/jdbc/Geometry.java | 303 ++++---- .../jdbc/SQLServerSpatialDatatype.java | 681 +++++++++++++----- .../SQLServerSpatialDatatypeTest.java | 458 ++++++++++++ 5 files changed, 1328 insertions(+), 438 deletions(-) create mode 100644 src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java index 9ec99c7ba..cedc5a459 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java @@ -617,7 +617,7 @@ static final Object convertStreamToObject(BaseInputStream stream, } else if (JDBCType.GEOMETRY == jdbcType) { return Geometry.STGeomFromWKB(byteValue); } else if (JDBCType.GEOGRAPHY == jdbcType) { - return new Geography(byteValue); + return Geography.STGeomFromWKB(byteValue); } else { String hexString = Util.bytesToHexString(byteValue, byteValue.length); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java index 25020f379..2c79a0a0d 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java @@ -1,3 +1,11 @@ +/* + * Microsoft JDBC Driver for SQL Server + * + * Copyright(c) Microsoft Corporation All rights reserved. + * + * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information. + */ + package com.microsoft.sqlserver.jdbc; import java.nio.ByteBuffer; @@ -5,16 +13,23 @@ import java.util.Locale; public class Geography extends SQLServerSpatialDatatype { - public Geography(String WellKnownText, int srid) { + + /** + * Private constructor used for creating a Geography object from WKT and srid. + */ + private Geography(String WellKnownText, int srid) { this.wkt = WellKnownText; this.srid = srid; - parseWKTForSerialization(currentWktPos, -1, false); + parseWKTForSerialization(this, currentWktPos, -1, false); serializeToWkb(false); isNull = false; } - public Geography(byte[] wkb) { + /** + * Private constructor used for creating a Geography object from WKB. + */ + private Geography(byte[] wkb) { this.wkb = wkb; buffer = ByteBuffer.wrap(wkb); buffer.order(ByteOrder.LITTLE_ENDIAN); @@ -24,7 +39,7 @@ public Geography(byte[] wkb) { WKTsb = new StringBuffer(); WKTsbNoZM = new StringBuffer(); - constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); + constructWKT(this, internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); wkt = WKTsb.toString(); wktNoZM = WKTsbNoZM.toString(); @@ -35,26 +50,67 @@ public Geography() { // TODO Auto-generated constructor stub } + /** + * Returns a Geography instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) + * representation augmented with any Z (elevation) and M (measure) values carried by the instance. + * + * @param wkt + * @param srid + * @return + */ public static Geography STGeomFromText(String wkt, int srid) { return new Geography(wkt, srid); } + /** + * Returns a Geography instance from an Open Geospatial Consortium (OGC) + * Well-Known Binary (WKB) representation. + * + * @param wkb + * @return + */ public static Geography STGeomFromWKB(byte[] wkb) { return new Geography(wkb); } + /** + * Returns a constructed Geography from an internal SQL Server format for spatial data. + * + * @param wkb + * @return + */ public static Geography deserialize(byte[] wkb) { return new Geography(wkb); } + /** + * Returns a Geography instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation. + * + * @param wkt + * @return + */ public static Geography parse(String wkt) { return new Geography(wkt, 0); } + /** + * Constructs a Geography instance that represents a Point instance from its X and Y values and an SRID. + * + * @param x + * @param y + * @param srid + * @return + */ public static Geography point(double x, double y, int srid) { return new Geography("POINT (" + x + " " + y + ")", srid); } - + + /** + * Returns the Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation of a + * Geography instance. This text will not contain any Z (elevation) or M (measure) values carried by the instance. + * + * @return the WKT representation without the Z and M values. + */ public String STAsText() { if (null == wktNoZM) { buffer = ByteBuffer.wrap(wkb); @@ -64,12 +120,17 @@ public String STAsText() { WKTsb = new StringBuffer(); WKTsbNoZM = new StringBuffer(); - constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); + constructWKT(this, internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); wktNoZM = WKTsbNoZM.toString(); } return wktNoZM; } + /** + * Returns the Open Geospatial Consortium (OGC) Well-Known Binary (WKB) representation of a + * Geography instance. This value will not contain any Z or M values carried by the instance. + * @return + */ public byte[] STAsBinary() { if (null == wkbNoZM) { serializeToWkb(true); @@ -77,6 +138,11 @@ public byte[] STAsBinary() { return wkbNoZM; } + /** + * Returns the bytes that represent an internal SQL Server format of Geography type. + * + * @return + */ public byte[] serialize() { return wkb; } @@ -129,6 +195,11 @@ public int STNumPoints() { return numberOfPoints; } + /** + * Returns the Open Geospatial Consortium (OGC) type name represented by a Geography instance. + * + * @return + */ public String STGeographyType() { if (null != internalType) { return internalType.getTypeName(); @@ -143,158 +214,119 @@ public String asTextZM() { public String toString() { return wkt; } - - protected void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) { - if (null == points || numberOfPoints == 0) { - if (isd.getTypeCode() == 11) { // FULLGLOBE - appendToWKTBuffers("FULLGLOBE"); - return; - } - appendToWKTBuffers(internalType + " EMPTY"); - return; - } - - appendToWKTBuffers(isd.getTypeName()); - appendToWKTBuffers("("); - switch (isd) { - case POINT: - constructPointWKT(currentPointIndex); - break; - case LINESTRING: - case CIRCULARSTRING: - constructLineWKT(currentPointIndex, pointIndexEnd); - break; - case POLYGON: - case MULTIPOINT: - case MULTILINESTRING: - constructShapeWKT(currentFigureIndex, figureIndexEnd); - break; - case COMPOUNDCURVE: - constructCompoundcurveWKT(currentSegmentIndex, segmentIndexEnd, pointIndexEnd); - break; - case MULTIPOLYGON: - constructMultipolygonWKT(currentFigureIndex, figureIndexEnd); - break; - case GEOMETRYCOLLECTION: - constructGeometryCollectionWKT(shapeIndexEnd); - break; - case CURVEPOLYGON: - constructCurvepolygonWKT(currentFigureIndex, figureIndexEnd, currentSegmentIndex, segmentIndexEnd); - break; - default: - break; + protected void serializeToWkb(boolean noZM) { + ByteBuffer buf = ByteBuffer.allocate(determineWkbCapacity()); + createSerializationProperties(); + + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(srid); + buf.put(version); + buf.put(serializationProperties); + + if (!isSinglePoint && !isSingleLineSegment) { + buf.putInt(numberOfPoints); } - appendToWKTBuffers(")"); - } - - protected void parseWKTForSerialization(int startPos, int parentShapeIndex, boolean isGeoCollection) { - //after every iteration of this while loop, the currentWktPosition will be set to the - //end of the geometry/geography shape, except for the very first iteration of it. - //This means that there has to be comma (that separates the previous shape with the next shape), - //or we expect a ')' that will close the entire shape and exit the method. + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(points[2 * i + 1]); + buf.putDouble(points[2 * i]); + } - parse: while (hasMoreToken()) { - if (startPos != 0) { - if (wkt.charAt(currentWktPos) == ')') { - return; - } else if (wkt.charAt(currentWktPos) == ',') { - currentWktPos++; + if (!noZM) { + if (hasZvalues) { + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(zValues[i]); } } - - String nextToken = getNextStringToken().toUpperCase(Locale.US); - int thisShapeIndex; - InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(nextToken); - byte fa = 0; - // check for FULLGLOBE before reading the first open bracket, since FULLGLOBE doesn't have one. - if (nextToken.equals("FULLGLOBE")) { - if (startPos != 0) { - throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + if (hasMvalues) { + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(mValues[i]); } - - shapeList.add(new Shape(parentShapeIndex, -1, isd.getTypeCode())); - isLargerThanHemisphere = true; - version = 2; - break parse; } - - readOpenBracket(); - - if (version == 1 && (nextToken.equals("CIRCULARSTRING") || nextToken.equals("COMPOUNDCURVE") || - nextToken.equals("CURVEPOLYGON"))) { - version = 2; + } + + if (isSinglePoint || isSingleLineSegment) { + wkb = buf.array(); + return; + } + + buf.putInt(numberOfFigures); + for (int i = 0; i < numberOfFigures; i++) { + buf.put(figures[i].getFiguresAttribute()); + buf.putInt(figures[i].getPointOffset()); + } + + buf.putInt(numberOfShapes); + for (int i = 0; i < numberOfShapes; i++) { + buf.putInt(shapes[i].getParentOffset()); + buf.putInt(shapes[i].getFigureOffset()); + buf.put(shapes[i].getOpenGISType()); + } + + if (version == 2 && null != segments) { + buf.putInt(numberOfSegments); + for (int i = 0; i < numberOfSegments; i++) { + buf.put(segments[i].getSegmentType()); } + } + + if (noZM) { + wkbNoZM = buf.array(); + } else { + wkb = buf.array(); - switch (nextToken) { - case "POINT": - if (startPos == 0 && nextToken.toUpperCase().equals("POINT")) { - isSinglePoint = true; - } - - if (isGeoCollection) { - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - figureList.add(new Figure(FA_LINE, pointList.size())); - } - - readPointWkt(); - break; - case "LINESTRING": - case "CIRCULARSTRING": - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - fa = isd.getTypeCode() == InternalSpatialDatatype.LINESTRING.getTypeCode() ? FA_STROKE : FA_EXTERIOR_RING; - figureList.add(new Figure(fa, pointList.size())); - - readLineWkt(); - - if (startPos == 0 && nextToken.toUpperCase().equals("LINESTRING") && pointList.size() == 2) { - isSingleLineSegment = true; - } - break; - case "POLYGON": - case "MULTIPOINT": - case "MULTILINESTRING": - thisShapeIndex = shapeList.size(); - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - - readShapeWkt(thisShapeIndex, nextToken); - - break; - case "MULTIPOLYGON": - thisShapeIndex = shapeList.size(); - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - - readMultiPolygonWkt(thisShapeIndex, nextToken); - - break; - case "COMPOUNDCURVE": - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - figureList.add(new Figure(FA_COMPOSITE_CURVE, pointList.size())); - - readCompoundCurveWkt(true); - - break; - case "CURVEPOLYGON": - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + } + return; + } + + protected void parseWkb() { + srid = buffer.getInt(); + version = buffer.get(); + serializationProperties = buffer.get(); + + interpretSerializationPropBytes(); + + readNumberOfPoints(); + + readPoints(); + + if (hasZvalues) { + readZvalues(); + } + + if (hasMvalues) { + readMvalues(); + } + + //TODO: do I need to do anything when it's isSinglePoint or isSingleLineSegment? + if (!(isSinglePoint || isSingleLineSegment)) { + readNumberOfFigures(); + + readFigures(); + + readNumberOfShapes(); + + readShapes(); + } + + determineInternalType(); - readCurvePolygon(); - - break; - case "GEOMETRYCOLLECTION": - thisShapeIndex = shapeList.size(); - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - - parseWKTForSerialization(currentWktPos, thisShapeIndex, true); - - break; - default: - throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + if (buffer.hasRemaining()) { + if (version == 2 && internalType.getTypeCode() != 8 && internalType.getTypeCode() != 11) { + readNumberOfSegments(); + + readSegments(); } - readCloseBracket(); } - - populateStructures(); + } + + private void readPoints() { + points = new double[2 * numberOfPoints]; + for (int i = 0; i < numberOfPoints; i++) { + points[2 * i + 1] = buffer.getDouble(); + points[2 * i] = buffer.getDouble(); + } } } \ No newline at end of file diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index da18702d0..0b955f07c 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -1,3 +1,11 @@ +/* + * Microsoft JDBC Driver for SQL Server + * + * Copyright(c) Microsoft Corporation All rights reserved. + * + * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information. + */ + package com.microsoft.sqlserver.jdbc; import java.nio.ByteBuffer; @@ -5,15 +13,22 @@ import java.util.Locale; public class Geometry extends SQLServerSpatialDatatype { + + /** + * Private constructor used for creating a Geometry object from WKT and srid. + */ private Geometry(String WellKnownText, int srid) { this.wkt = WellKnownText; this.srid = srid; - parseWKTForSerialization(currentWktPos, -1, false); + parseWKTForSerialization(this, currentWktPos, -1, false); serializeToWkb(false); isNull = false; } + /** + * Private constructor used for creating a Geometry object from WKB. + */ private Geometry(byte[] wkb) { this.wkb = wkb; buffer = ByteBuffer.wrap(wkb); @@ -24,7 +39,7 @@ private Geometry(byte[] wkb) { WKTsb = new StringBuffer(); WKTsbNoZM = new StringBuffer(); - constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); + constructWKT(this, internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); wkt = WKTsb.toString(); wktNoZM = WKTsbNoZM.toString(); @@ -35,26 +50,67 @@ public Geometry() { // TODO Auto-generated constructor stub } + /** + * Returns a Geometry instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) + * representation augmented with any Z (elevation) and M (measure) values carried by the instance. + * + * @param wkt + * @param srid + * @return + */ public static Geometry STGeomFromText(String wkt, int srid) { return new Geometry(wkt, srid); } + /** + * Returns a Geometry instance from an Open Geospatial Consortium (OGC) + * Well-Known Binary (WKB) representation. + * + * @param wkb + * @return + */ public static Geometry STGeomFromWKB(byte[] wkb) { return new Geometry(wkb); } + /** + * Returns a constructed Geometry from an internal SQL Server format for spatial data. + * + * @param wkb + * @return + */ public static Geometry deserialize(byte[] wkb) { return new Geometry(wkb); } + /** + * Returns a Geometry instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation. + * + * @param wkt + * @return + */ public static Geometry parse(String wkt) { return new Geometry(wkt, 0); } + /** + * Constructs a Geometry instance that represents a Point instance from its X and Y values and an SRID. + * + * @param x + * @param y + * @param srid + * @return + */ public static Geometry point(double x, double y, int srid) { return new Geometry("POINT (" + x + " " + y + ")", srid); } + /** + * Returns the Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation of a + * Geometry instance. This text will not contain any Z (elevation) or M (measure) values carried by the instance. + * + * @return the WKT representation without the Z and M values. + */ public String STAsText() { if (null == wktNoZM) { buffer = ByteBuffer.wrap(wkb); @@ -64,12 +120,17 @@ public String STAsText() { WKTsb = new StringBuffer(); WKTsbNoZM = new StringBuffer(); - constructWKT(internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); + constructWKT(this, internalType, numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); wktNoZM = WKTsbNoZM.toString(); } return wktNoZM; } + /** + * Returns the Open Geospatial Consortium (OGC) Well-Known Binary (WKB) representation of a + * Geometry instance. This value will not contain any Z or M values carried by the instance. + * @return + */ public byte[] STAsBinary() { if (null == wkbNoZM) { serializeToWkb(true); @@ -77,6 +138,11 @@ public byte[] STAsBinary() { return wkbNoZM; } + /** + * Returns the bytes that represent an internal SQL Server format of Geometry type. + * + * @return + */ public byte[] serialize() { return wkb; } @@ -129,6 +195,11 @@ public int STNumPoints() { return numberOfPoints; } + /** + * Returns the Open Geospatial Consortium (OGC) type name represented by a geometry instance. + * + * @return + */ public String STGeometryType() { if (null != internalType) { return internalType.getTypeName(); @@ -144,146 +215,118 @@ public String toString() { return wkt; } - protected void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) { - if (null == points || numberOfPoints == 0) { - if (isd.getTypeCode() == 11) { // FULLGLOBE - throw new IllegalArgumentException("Fullglobe is not supported for Geometry."); + protected void serializeToWkb(boolean noZM) { + ByteBuffer buf = ByteBuffer.allocate(determineWkbCapacity()); + createSerializationProperties(); + + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(srid); + buf.put(version); + buf.put(serializationProperties); + + if (!isSinglePoint && !isSingleLineSegment) { + buf.putInt(numberOfPoints); + } + + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(points[2 * i]); + buf.putDouble(points[2 * i + 1]); + } + + if (!noZM ) { + if (hasZvalues) { + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(zValues[i]); + } } - appendToWKTBuffers(internalType + " EMPTY"); + + if (hasMvalues) { + for (int i = 0; i < numberOfPoints; i++) { + buf.putDouble(mValues[i]); + } + } + } + + if (isSinglePoint || isSingleLineSegment) { + wkb = buf.array(); return; } - appendToWKTBuffers(isd.getTypeName()); - appendToWKTBuffers("("); - - switch (isd) { - case POINT: - constructPointWKT(currentPointIndex); - break; - case LINESTRING: - case CIRCULARSTRING: - constructLineWKT(currentPointIndex, pointIndexEnd); - break; - case POLYGON: - case MULTIPOINT: - case MULTILINESTRING: - constructShapeWKT(currentFigureIndex, figureIndexEnd); - break; - case COMPOUNDCURVE: - constructCompoundcurveWKT(currentSegmentIndex, segmentIndexEnd, pointIndexEnd); - break; - case MULTIPOLYGON: - constructMultipolygonWKT(currentFigureIndex, figureIndexEnd); - break; - case GEOMETRYCOLLECTION: - constructGeometryCollectionWKT(shapeIndexEnd); - break; - case CURVEPOLYGON: - constructCurvepolygonWKT(currentFigureIndex, figureIndexEnd, currentSegmentIndex, segmentIndexEnd); - break; - default: - throw new IllegalArgumentException(); + buf.putInt(numberOfFigures); + for (int i = 0; i < numberOfFigures; i++) { + buf.put(figures[i].getFiguresAttribute()); + buf.putInt(figures[i].getPointOffset()); } - appendToWKTBuffers(")"); - } - - protected void parseWKTForSerialization(int startPos, int parentShapeIndex, boolean isGeoCollection) { - //after every iteration of this while loop, the currentWktPosition will be set to the - //end of the geometry/geography shape, except for the very first iteration of it. - //This means that there has to be comma (that separates the previous shape with the next shape), - //or we expect a ')' that will close the entire shape and exit the method. + buf.putInt(numberOfShapes); + for (int i = 0; i < numberOfShapes; i++) { + buf.putInt(shapes[i].getParentOffset()); + buf.putInt(shapes[i].getFigureOffset()); + buf.put(shapes[i].getOpenGISType()); + } - while (hasMoreToken()) { - if (startPos != 0) { - if (wkt.charAt(currentWktPos) == ')') { - return; - } else if (wkt.charAt(currentWktPos) == ',') { - currentWktPos++; - } + if (version == 2 && null != segments) { + buf.putInt(numberOfSegments); + for (int i = 0; i < numberOfSegments; i++) { + buf.put(segments[i].getSegmentType()); } + } + + if (noZM) { + wkbNoZM = buf.array(); + } else { + wkb = buf.array(); - String nextToken = getNextStringToken().toUpperCase(Locale.US); - int thisShapeIndex; - InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(nextToken); - byte fa = 0; + } + return; + } + + protected void parseWkb() { + srid = buffer.getInt(); + version = buffer.get(); + serializationProperties = buffer.get(); + + interpretSerializationPropBytes(); + + readNumberOfPoints(); + + readPoints(); + + if (hasZvalues) { + readZvalues(); + } + + if (hasMvalues) { + readMvalues(); + } + + //TODO: do I need to do anything when it's isSinglePoint or isSingleLineSegment? + if (!(isSinglePoint || isSingleLineSegment)) { + readNumberOfFigures(); - readOpenBracket(); + readFigures(); - if (version == 1 && (nextToken.equals("CIRCULARSTRING") || nextToken.equals("COMPOUNDCURVE") || - nextToken.equals("CURVEPOLYGON"))) { - version = 2; - } - - switch (nextToken) { - case "POINT": - if (startPos == 0 && nextToken.toUpperCase().equals("POINT")) { - isSinglePoint = true; - } - - if (isGeoCollection) { - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - figureList.add(new Figure(FA_LINE, pointList.size())); - } - - readPointWkt(); - break; - case "LINESTRING": - case "CIRCULARSTRING": - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - fa = isd.getTypeCode() == InternalSpatialDatatype.LINESTRING.getTypeCode() ? FA_STROKE : FA_EXTERIOR_RING; - figureList.add(new Figure(fa, pointList.size())); - - readLineWkt(); - - if (startPos == 0 && nextToken.toUpperCase().equals("LINESTRING") && pointList.size() == 2) { - isSingleLineSegment = true; - } - break; - case "POLYGON": - case "MULTIPOINT": - case "MULTILINESTRING": - thisShapeIndex = shapeList.size(); - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - - readShapeWkt(thisShapeIndex, nextToken); - - break; - case "MULTIPOLYGON": - thisShapeIndex = shapeList.size(); - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - - readMultiPolygonWkt(thisShapeIndex, nextToken); - - break; - case "COMPOUNDCURVE": - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - figureList.add(new Figure(FA_COMPOSITE_CURVE, pointList.size())); - - readCompoundCurveWkt(true); - - break; - case "CURVEPOLYGON": - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + readNumberOfShapes(); + + readShapes(); + } + + determineInternalType(); - readCurvePolygon(); - - break; - case "GEOMETRYCOLLECTION": - thisShapeIndex = shapeList.size(); - shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); - - parseWKTForSerialization(currentWktPos, thisShapeIndex, true); - - break; - case "FULLGLOBE": - throw new IllegalArgumentException("Fullglobe is not supported for Geometry."); - default: - throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + if (buffer.hasRemaining()) { + if (version == 2 && internalType.getTypeCode() != 8 && internalType.getTypeCode() != 11) { + readNumberOfSegments(); + + readSegments(); } - readCloseBracket(); } - - populateStructures(); + } + + 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(); + } } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java index 6dbb7e4cf..3b8ff354a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java @@ -1,3 +1,11 @@ +/* + * Microsoft JDBC Driver for SQL Server + * + * Copyright(c) Microsoft Corporation All rights reserved. + * + * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information. + */ + package com.microsoft.sqlserver.jdbc; import java.math.BigDecimal; @@ -7,7 +15,13 @@ import java.util.List; import java.util.Locale; +import com.sun.mail.imap.protocol.INTERNALDATE; + abstract class SQLServerSpatialDatatype { + + /**WKT = Well-Known-Text, WKB = Well-Knwon-Binary */ + /**As a general rule, the ~IndexEnd variables are non-inclusive (i.e. pointIndexEnd = 8 means the shape using it will + * only go up to the 7th index of the array) */ protected ByteBuffer buffer; protected InternalSpatialDatatype internalType; protected String wkt; @@ -25,6 +39,7 @@ abstract class SQLServerSpatialDatatype { protected int currentPointIndex = 0; protected int currentFigureIndex = 0; protected int currentSegmentIndex = 0; + protected int currentShapeIndex = 0; protected double points[]; protected double zValues[]; protected double mValues[]; @@ -56,9 +71,7 @@ abstract class SQLServerSpatialDatatype { protected List
figureList = new ArrayList
(); protected List shapeList = new ArrayList(); protected List segmentList = new ArrayList(); - - private int currentShapeIndex = 0; - private byte serializationProperties = 0; + protected byte serializationProperties = 0; private final byte SEGMENT_LINE = 0; private final byte SEGMENT_ARC = 1; @@ -73,117 +86,226 @@ abstract class SQLServerSpatialDatatype { private final byte isLargerThanHemisphereMask = 0b00100000; private List version_one_shape_indexes = new ArrayList(); - - protected abstract void constructWKT(InternalSpatialDatatype isd, int pointIndexEnd, - int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd); - protected abstract void parseWKTForSerialization(int startPos, int parentShapeIndex, boolean isGeoCollection); + /** + * Serializes the Geogemetry/Geography instance to WKB. + * + * @param noZM flag to indicate if Z and M coordinates should be included + */ + protected abstract void serializeToWkb(boolean noZM); - protected void serializeToWkb(boolean noZM) { - ByteBuffer buf = ByteBuffer.allocate(determineWkbCapacity()); - createSerializationProperties(); - - buf.order(ByteOrder.LITTLE_ENDIAN); - buf.putInt(srid); - buf.put(version); - buf.put(serializationProperties); - - if (!isSinglePoint && !isSingleLineSegment) { - buf.putInt(numberOfPoints); - } - - for (int i = 0; i < numberOfPoints; i++) { - buf.putDouble(points[2 * i]); - buf.putDouble(points[2 * i + 1]); - } - - if (!noZM ) { - if (hasZvalues) { - for (int i = 0; i < numberOfPoints; i++) { - buf.putDouble(zValues[i]); + /** + * Deserialize the buffer (that contains WKB representation of Geometry/Geography data), and stores it + * into multiple corresponding data structures. + * + */ + protected abstract void parseWkb(); + + /** + * Create the WKT representation of Geometry/Geography from the deserialized data. + * + * @param sd the Geometry/Geography instance. + * @param isd + * @param pointIndexEnd + * @param figureIndexEnd + * @param segmentIndexEnd + * @param shapeIndexEnd + */ + protected void constructWKT(SQLServerSpatialDatatype sd, InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, + int segmentIndexEnd, int shapeIndexEnd) { + if (null == points || numberOfPoints == 0) { + if (isd.getTypeCode() == 11) { // FULLGLOBE + if (sd instanceof Geometry) { + throw new IllegalArgumentException("Fullglobe is not supported for Geometry."); + } else { + appendToWKTBuffers("FULLGLOBE"); + return; } } - - if (hasMvalues) { - for (int i = 0; i < numberOfPoints; i++) { - buf.putDouble(mValues[i]); - } + // handle the case of GeometryCollection having empty objects + if (isd.getTypeCode() == 7 && currentShapeIndex != shapeIndexEnd - 1) { + currentShapeIndex++; + appendToWKTBuffers(isd.getTypeName() + "("); + constructWKT(this, InternalSpatialDatatype.valueOf(shapes[currentShapeIndex].getOpenGISType()), + numberOfPoints, numberOfFigures, numberOfSegments, numberOfShapes); + appendToWKTBuffers(")"); + return; } - } - - if (isSinglePoint || isSingleLineSegment) { - wkb = buf.array(); + appendToWKTBuffers(isd.getTypeName() + " EMPTY"); return; } - buf.putInt(numberOfFigures); - for (int i = 0; i < numberOfFigures; i++) { - buf.put(figures[i].getFiguresAttribute()); - buf.putInt(figures[i].getPointOffset()); - } - - buf.putInt(numberOfShapes); - for (int i = 0; i < numberOfShapes; i++) { - buf.putInt(shapes[i].getParentOffset()); - buf.putInt(shapes[i].getFigureOffset()); - buf.put(shapes[i].getOpenGISType()); - } - - if (version == 2 && null != segments) { - buf.putInt(numberOfSegments); - for (int i = 0; i < numberOfSegments; i++) { - buf.put(segments[i].getSegmentType()); - } - } - - if (noZM) { - wkbNoZM = buf.array(); - } else { - wkb = buf.array(); + appendToWKTBuffers(isd.getTypeName()); + appendToWKTBuffers("("); + switch (isd) { + case POINT: + constructPointWKT(currentPointIndex); + break; + case LINESTRING: + case CIRCULARSTRING: + constructLineWKT(currentPointIndex, pointIndexEnd); + break; + case POLYGON: + constructShapeWKT(currentFigureIndex, figureIndexEnd); + break; + case MULTIPOINT: + case MULTILINESTRING: + constructMultiShapeWKT(currentShapeIndex, shapeIndexEnd); + break; + case COMPOUNDCURVE: + constructCompoundcurveWKT(currentSegmentIndex, segmentIndexEnd, pointIndexEnd); + break; + case MULTIPOLYGON: + constructMultipolygonWKT(currentShapeIndex, shapeIndexEnd); + break; + case GEOMETRYCOLLECTION: + constructGeometryCollectionWKT(shapeIndexEnd); + break; + case CURVEPOLYGON: + constructCurvepolygonWKT(currentFigureIndex, figureIndexEnd, currentSegmentIndex, segmentIndexEnd); + break; + default: + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } - return; + + appendToWKTBuffers(")"); } - protected void parseWkb() { - srid = buffer.getInt(); - version = buffer.get(); - serializationProperties = buffer.get(); - - interpretSerializationPropBytes(); - - readNumberOfPoints(); - - readPoints(); + /** + * Parses WKT and populates the data structures of the Geometry/Geography instance. + * + * @param sd the Geometry/Geography instance. + * @param startPos The index to start from from the WKT. + * @param parentShapeIndex The index of the parent's Shape in the shapes array. Used to determine this shape's parent. + * @param isGeoCollection flag to indicate if this is part of a GeometryCollection. + */ + protected void parseWKTForSerialization(SQLServerSpatialDatatype sd, int startPos, int parentShapeIndex, boolean isGeoCollection) { + //after every iteration of this while loop, the currentWktPosition will be set to the + //end of the geometry/geography shape, except for the very first iteration of it. + //This means that there has to be comma (that separates the previous shape with the next shape), + //or we expect a ')' that will close the entire shape and exit the method. - if (hasZvalues) { - readZvalues(); - } - - if (hasMvalues) { - readMvalues(); - } - - //TODO: do I need to do anything when it's isSinglePoint or isSingleLineSegment? - if (!(isSinglePoint || isSingleLineSegment)) { - readNumberOfFigures(); - - readFigures(); + while (hasMoreToken()) { + if (startPos != 0) { + if (wkt.charAt(currentWktPos) == ')') { + return; + } else if (wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + } + } + + String nextToken = getNextStringToken().toUpperCase(Locale.US); + int thisShapeIndex; + InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(nextToken); + byte fa = 0; - readNumberOfShapes(); + if (version == 1 && (nextToken.equals("CIRCULARSTRING") || nextToken.equals("COMPOUNDCURVE") || + nextToken.equals("CURVEPOLYGON"))) { + version = 2; + } - readShapes(); - } - - determineInternalType(); + // check for FULLGLOBE before reading the first open bracket, since FULLGLOBE doesn't have one. + if (nextToken.equals("FULLGLOBE")) { + if (sd instanceof Geometry) { + throw new IllegalArgumentException("Fullglobe is not supported for Geometry."); + } + + if (startPos != 0) { + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + } + + shapeList.add(new Shape(parentShapeIndex, -1, isd.getTypeCode())); + isLargerThanHemisphere = true; + version = 2; + break; + } - if (version == 2 && internalType.getTypeCode() != 8 && internalType.getTypeCode() != 11) { - readNumberOfSegments(); + // if next keyword is empty, continue the loop. + if (checkEmptyKeyword(parentShapeIndex, isd, false)) { + continue; + } + + readOpenBracket(); - readSegments(); + switch (nextToken) { + case "POINT": + if (startPos == 0 && nextToken.toUpperCase().equals("POINT")) { + isSinglePoint = true; + } + + if (isGeoCollection) { + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + figureList.add(new Figure(FA_LINE, pointList.size())); + } + + readPointWkt(); + break; + case "LINESTRING": + case "CIRCULARSTRING": + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + fa = isd.getTypeCode() == InternalSpatialDatatype.LINESTRING.getTypeCode() ? FA_STROKE : FA_EXTERIOR_RING; + figureList.add(new Figure(fa, pointList.size())); + + readLineWkt(); + + if (startPos == 0 && nextToken.toUpperCase().equals("LINESTRING") && pointList.size() == 2) { + isSingleLineSegment = true; + } + break; + case "POLYGON": + case "MULTIPOINT": + case "MULTILINESTRING": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + readShapeWkt(thisShapeIndex, nextToken); + + break; + case "MULTIPOLYGON": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + readMultiPolygonWkt(thisShapeIndex, nextToken); + + break; + case "COMPOUNDCURVE": + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + figureList.add(new Figure(FA_COMPOSITE_CURVE, pointList.size())); + + readCompoundCurveWkt(true); + + break; + case "CURVEPOLYGON": + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + readCurvePolygon(); + + break; + case "GEOMETRYCOLLECTION": + thisShapeIndex = shapeList.size(); + shapeList.add(new Shape(parentShapeIndex, figureList.size(), isd.getTypeCode())); + + parseWKTForSerialization(this, currentWktPos, thisShapeIndex, true); + + break; + default: + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + } + readCloseBracket(); } + + populateStructures(); } + /** + * Constructs and appends a Point type in WKT form to the stringbuffer. + * There are two stringbuffers - WKTsb and WKTsbNoZM. WKTsb contains the X, Y, Z and M coordinates, + * whereas WKTsbNoZM contains only X and Y coordinates. + * + * @param pointIndex indicates which point to append to the stringbuffer. + * + */ protected void constructPointWKT(int pointIndex) { int firstPointIndex = pointIndex * 2; int secondPointIndex = firstPointIndex + 1; @@ -204,7 +326,7 @@ protected void constructPointWKT(int pointIndex) { } appendToWKTBuffers(" "); - if (hasZvalues && !Double.isNaN(zValues[zValueIndex])) { + if (hasZvalues && !Double.isNaN(zValues[zValueIndex]) && !(zValues[zValueIndex] == 0)) { if (zValues[zValueIndex] % 1 == 0) { WKTsb.append((int) zValues[zValueIndex]); } else { @@ -212,7 +334,7 @@ protected void constructPointWKT(int pointIndex) { } WKTsb.append(" "); - if (hasMvalues && !Double.isNaN(mValues[mValueIndex])) { + if (hasMvalues && !Double.isNaN(mValues[mValueIndex]) && !(mValues[mValueIndex] <= 0)) { if (mValues[mValueIndex] % 1 == 0) { WKTsb.append((int) mValues[mValueIndex]); } else { @@ -228,6 +350,12 @@ protected void constructPointWKT(int pointIndex) { WKTsbNoZM.setLength(WKTsbNoZM.length() - 1); } + /** + * Constructs a line in WKT form. + * + * @param pointStartIndex + * @param pointEndIndex + */ protected void constructLineWKT(int pointStartIndex, int pointEndIndex) { for (int i = pointStartIndex; i < pointEndIndex; i++) { constructPointWKT(i); @@ -239,8 +367,13 @@ protected void constructLineWKT(int pointStartIndex, int pointEndIndex) { } } + /** + * Constructs a shape (simple Geometry/Geography entities that are contained within a single bracket) in WKT form. + * + * @param figureStartIndex + * @param figureEndIndex + */ protected void constructShapeWKT(int figureStartIndex, int figureEndIndex) { - // Method for constructing shapes (simple Geometry/Geography entities that are contained within a single bracket) for (int i = figureStartIndex; i < figureEndIndex; i++) { appendToWKTBuffers("("); if (i != numberOfFigures - 1) { //not the last figure @@ -257,6 +390,32 @@ protected void constructShapeWKT(int figureStartIndex, int figureEndIndex) { } } + /** + * Constructs a mutli-shape (MultiPoint / MultiLineString) in WKT form. + * + * @param figureStartIndex + * @param figureEndIndex + */ + protected void constructMultiShapeWKT(int shapeStartIndex, int shapeEndIndex) { + for (int i = shapeStartIndex + 1; i < shapeEndIndex; i++) { + if (shapes[i].getFigureOffset() == -1) { // EMPTY + appendToWKTBuffers("EMPTY"); + } else { + constructShapeWKT(shapes[i].getFigureOffset(), shapes[i].getFigureOffset() + 1); + } + if (i != shapeEndIndex - 1) { + appendToWKTBuffers(", "); + } + } + } + + /** + * Constructs a CompoundCurve in WKT form. + * + * @param segmentStartIndex + * @param segmentEndIndex + * @param pointEndIndex + */ protected void constructCompoundcurveWKT(int segmentStartIndex, int segmentEndIndex, int pointEndIndex) { for (int i = segmentStartIndex; i < segmentEndIndex; i++) { byte segment = segments[i].getSegmentType(); @@ -286,31 +445,78 @@ protected void constructCompoundcurveWKT(int segmentStartIndex, int segmentEndIn } } - protected void constructMultipolygonWKT(int figureStartIndex, int figureEndIndex) { - for (int i = figureStartIndex; i < figureEndIndex; i++) { - if (figures[i].getFiguresAttribute() == 2) { // exterior ring - appendToWKTBuffers("(("); - } else { // interior ring - appendToWKTBuffers("("); + /** + * Constructs a MultiPolygon in WKT form. + * + * @param segmentStartIndex + * @param segmentEndIndex + * @param pointEndIndex + */ + protected void constructMultipolygonWKT(int shapeStartIndex, int shapeEndIndex) { + int figureStartIndex; + int figureEndIndex; + + for (int i = shapeStartIndex + 1; i < shapeEndIndex; i++) { + figureEndIndex = figures.length; + if (shapes[i].getFigureOffset() == -1) { // EMPTY + appendToWKTBuffers("EMPTY"); + if (!(i == shapeEndIndex - 1)) { // not the last exterior polygon of this multipolygon, add a comma + appendToWKTBuffers(", "); + } + continue; } - - if (i == figures.length - 1) { // last figure - constructLineWKT(figures[i].getPointOffset(), numberOfPoints); + figureStartIndex = shapes[i].getFigureOffset(); + if (i == shapes.length - 1) { // last shape + figureEndIndex = figures.length; } else { - constructLineWKT(figures[i].getPointOffset(), figures[i + 1].getPointOffset()); + // look ahead and find the next shape that doesn't have -1 as its figure offset (which signifies EMPTY) + int tempCurrentShapeIndex = i + 1; + // We need to iterate this through until the very end of the shapes list, since if the last shape + // in this MultiPolygon is an EMPTY, it won't know what the correct figureEndIndex would be. + while (tempCurrentShapeIndex < shapes.length) { + if (shapes[tempCurrentShapeIndex].getFigureOffset() == -1) { + tempCurrentShapeIndex++; + continue; + } else { + figureEndIndex = shapes[tempCurrentShapeIndex].getFigureOffset(); + break; + } + } } + + appendToWKTBuffers("("); - if (i == figureEndIndex - 1) { // last polygon of this multipolygon, close off the Multipolygon and return - appendToWKTBuffers("))"); - return; - } else if (figures[i + 1].getFiguresAttribute() == 2) { // not the last polygon, followed by an exterior ring - appendToWKTBuffers(")), "); - } else { // not the last polygon, followed by an interior ring - appendToWKTBuffers("), "); + for (int j = figureStartIndex; j < figureEndIndex; j++) { + appendToWKTBuffers("(");// interior ring + + if (j == figures.length - 1) { // last figure + constructLineWKT(figures[j].getPointOffset(), numberOfPoints); + } else { + constructLineWKT(figures[j].getPointOffset(), figures[j + 1].getPointOffset()); + } + + if (j == figureEndIndex - 1) { // last polygon of this multipolygon, close off the Multipolygon + appendToWKTBuffers(")"); + } else { // not the last polygon, followed by an interior ring + appendToWKTBuffers("), "); + } + } + + appendToWKTBuffers(")"); + + if (!(i == shapeEndIndex - 1)) { // not the last exterior polygon of this multipolygon, add a comma + appendToWKTBuffers(", "); } } } + /** + * Constructs a CurvePolygon in WKT form. + * + * @param segmentStartIndex + * @param segmentEndIndex + * @param pointEndIndex + */ protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex, int segmentStartIndex, int segmentEndIndex) { for (int i = figureStartIndex; i < figureEndIndex; i++) { switch (figures[i].getFiguresAttribute()) { @@ -321,7 +527,6 @@ protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex constructLineWKT(currentPointIndex, numberOfPoints); } else { constructLineWKT(currentPointIndex, figures[i + 1].getPointOffset()); - //currentPointIndex = figures[i + 1].getPointOffset(); } appendToWKTBuffers(")"); @@ -333,7 +538,6 @@ protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex constructLineWKT(currentPointIndex, numberOfPoints); } else { constructLineWKT(currentPointIndex, figures[i + 1].getPointOffset()); - //currentPointIndex = figures[i + 1].getPointOffset(); } appendToWKTBuffers(")"); @@ -395,6 +599,18 @@ protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex } } + /** + * Constructs a Segment in WKT form. + * SQL Server re-uses the last point of a segment if the following segment is of type 3 (first arc) or + * type 2 (first line). This makes sense because the last point of a segment and the first point of the next + * segment have to match for a valid curve. This means that the code has to look ahead and decide to decrement + * the currentPointIndex depending on what segment comes next, since it may have been reused (and it's reflected + * in the array of points) + * + * @param segmentStartIndex + * @param segmentEndIndex + * @param pointEndIndex + */ protected void constructSegmentWKT(int currentSegment, byte segment, int pointEndIndex) { switch (segment) { case 0: @@ -450,11 +666,20 @@ protected void constructSegmentWKT(int currentSegment, byte segment, int pointEn } } + /** + * The starting point for constructing a GeometryCollection type in WKT form. + * + * @param shapeEndIndex + */ protected void constructGeometryCollectionWKT(int shapeEndIndex) { currentShapeIndex++; constructGeometryCollectionWKThelper(shapeEndIndex); } + /** + * Reads Point WKT and adds it to the list of points. + * This method will read up until and including the comma that may come at the end of the Point WKT. + */ protected void readPointWkt() { int numOfCoordinates = 0; double sign; @@ -485,7 +710,7 @@ protected void readPointWkt() { coords[numOfCoordinates] = sign * new BigDecimal(wkt.substring(startPos, currentWktPos)).doubleValue(); } catch (Exception e) { //modify to conversion exception - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } skipWhiteSpaces(); @@ -510,15 +735,32 @@ protected void readPointWkt() { pointList.add(new Point(coords[0], coords[1], coords[2], coords[3])); } + /** + * Reads a series of Point types. + */ protected void readLineWkt() { while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { readPointWkt(); } } + /** + * Reads a shape (simple Geometry/Geography entities that are contained within a single bracket) WKT. + * + * @param parentShapeIndex + * @param nextToken + */ protected void readShapeWkt(int parentShapeIndex, String nextToken) { byte fa = FA_POINT; while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + + // if next keyword is empty, continue the loop. + // Do not check this for polygon. + if (!nextToken.equals("POLYGON") && + checkEmptyKeyword(parentShapeIndex, InternalSpatialDatatype.valueOf(nextToken), true)) { + continue; + } + if (nextToken.equals("MULTIPOINT")) { shapeList.add(new Shape(parentShapeIndex, figureList.size(), InternalSpatialDatatype.POINT.getTypeCode())); } else if (nextToken.equals("MULTILINESTRING")) { @@ -551,11 +793,14 @@ protected void readShapeWkt(int parentShapeIndex, String nextToken) { } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop continue; } else { // unexpected input - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } } } + /** + * Reads a CurvePolygon WKT + */ protected void readCurvePolygon() { while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { String nextPotentialToken = getNextStringToken().toUpperCase(Locale.US); @@ -575,7 +820,7 @@ protected void readCurvePolygon() { readLineWkt(); readCloseBracket(); } else { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } if (wkt.charAt(currentWktPos) == ',') { // more polygons to follow @@ -583,13 +828,19 @@ protected void readCurvePolygon() { } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop continue; } else { // unexpected input - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } } } + /** + * Reads a MultiPolygon WKT + */ protected void readMultiPolygonWkt(int thisShapeIndex, String nextToken) { while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { + if (checkEmptyKeyword(thisShapeIndex, InternalSpatialDatatype.valueOf(nextToken), true)) { + continue; + } shapeList.add(new Shape(thisShapeIndex, figureList.size(), InternalSpatialDatatype.POLYGON.getTypeCode())); //exterior polygon readOpenBracket(); readShapeWkt(thisShapeIndex, nextToken); @@ -600,12 +851,14 @@ protected void readMultiPolygonWkt(int thisShapeIndex, String nextToken) { } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop continue; } else { // unexpected input - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } } } - + /** + * Reads a Segment WKT + */ protected void readSegmentWkt(int segmentType, boolean isFirstIteration) { segmentList.add(new Segment((byte) segmentType)); @@ -635,6 +888,9 @@ protected void readSegmentWkt(int segmentType, boolean isFirstIteration) { } } + /** + * Reads a CompoundCurve WKT + */ protected void readCompoundCurveWkt(boolean isFirstIteration) { while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { String nextPotentialToken = getNextStringToken().toUpperCase(Locale.US); @@ -647,7 +903,7 @@ protected void readCompoundCurveWkt(boolean isFirstIteration) { readSegmentWkt(SEGMENT_FIRST_LINE, isFirstIteration); readCloseBracket(); } else { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } isFirstIteration = false; @@ -657,11 +913,17 @@ protected void readCompoundCurveWkt(boolean isFirstIteration) { } else if (wkt.charAt(currentWktPos) == ')') { // about to exit while loop continue; } else { // unexpected input - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } } } + /** + * Reads the next string token (usually POINT, LINESTRING, etc.). + * Then increments currentWktPos to the end of the string token. + * + * @return the next string token + */ protected String getNextStringToken() { skipWhiteSpaces(); int endIndex = currentWktPos; @@ -675,6 +937,9 @@ protected String getNextStringToken() { return wkt.substring(temp, endIndex); } + /** + * Populates the various data structures contained within the Geometry/Geography instace. + */ protected void populateStructures() { if (pointList.size() > 0) { points = new double[pointList.size() * 2]; @@ -716,6 +981,14 @@ protected void populateStructures() { } } + // There is an edge case of empty GeometryCollections being inside other GeometryCollections. In this case, + // the figure offset of the very first shape (GeometryCollections) has to be -1, but this is not possible to know until + // We've parsed through the entire WKT and confirmed that there are 0 points. + // Therefore, if so, we make the figure offset of the first shape to be -1. + if (pointList.size() == 0 && shapeList.size() > 0 && shapeList.get(0).getOpenGISType() == 7) { + shapeList.get(0).setFigureOffset(-1); + } + if (shapeList.size() > 0) { shapes = new Shape[shapeList.size()]; @@ -744,7 +1017,7 @@ protected void readOpenBracket() { currentWktPos++; skipWhiteSpaces(); } else { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } } @@ -754,7 +1027,7 @@ protected void readCloseBracket() { currentWktPos++; skipWhiteSpaces(); } else { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } } @@ -763,7 +1036,7 @@ protected boolean hasMoreToken() { return currentWktPos < wkt.length(); } - private void createSerializationProperties() { + protected void createSerializationProperties() { serializationProperties = 0; if (hasZvalues) { serializationProperties += hasZvaluesMask; @@ -793,7 +1066,7 @@ private void createSerializationProperties() { } } - private int determineWkbCapacity() { + protected int determineWkbCapacity() { int totalSize = 0; totalSize+=6; // SRID + version + SerializationPropertiesByte @@ -833,8 +1106,17 @@ private int determineWkbCapacity() { return totalSize; } + + /** + * Append the data to both stringbuffers. + * @param o data to append to the stringbuffers. + */ + protected void appendToWKTBuffers(Object o) { + WKTsb.append(o); + WKTsbNoZM.append(o); + } - private void interpretSerializationPropBytes() { + protected void interpretSerializationPropBytes() { hasZvalues = (serializationProperties & hasZvaluesMask) != 0; hasMvalues = (serializationProperties & hasMvaluesMask) != 0; isValid = (serializationProperties & isValidMask) != 0; @@ -843,7 +1125,7 @@ private void interpretSerializationPropBytes() { isLargerThanHemisphere = (serializationProperties & isLargerThanHemisphereMask) != 0; } - private void readNumberOfPoints() { + protected void readNumberOfPoints() { if (isSinglePoint) { numberOfPoints = 1; } else if (isSingleLineSegment) { @@ -852,34 +1134,26 @@ private void readNumberOfPoints() { numberOfPoints = buffer.getInt(); } } - - 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() { + protected void readZvalues() { zValues = new double[numberOfPoints]; for (int i = 0; i < numberOfPoints; i++) { zValues[i] = buffer.getDouble(); } } - private void readMvalues() { + protected void readMvalues() { mValues = new double[numberOfPoints]; for (int i = 0; i < numberOfPoints; i++) { mValues[i] = buffer.getDouble(); } } - private void readNumberOfFigures() { + protected void readNumberOfFigures() { numberOfFigures = buffer.getInt(); } - private void readFigures() { + protected void readFigures() { byte fa; int po; figures = new Figure[numberOfFigures]; @@ -890,11 +1164,11 @@ private void readFigures() { } } - private void readNumberOfShapes() { + protected void readNumberOfShapes() { numberOfShapes = buffer.getInt(); } - private void readShapes() { + protected void readShapes() { int po; int fo; byte ogt; @@ -907,11 +1181,11 @@ private void readShapes() { } } - private void readNumberOfSegments() { + protected void readNumberOfSegments() { numberOfSegments = buffer.getInt(); } - private void readSegments() { + protected void readSegments() { byte st; segments = new Segment[numberOfSegments]; for (int i = 0; i < numberOfSegments; i++) { @@ -920,7 +1194,7 @@ private void readSegments() { } } - private void determineInternalType() { + protected void determineInternalType() { if (isSinglePoint) { internalType = InternalSpatialDatatype.POINT; } else if (isSingleLineSegment) { @@ -930,6 +1204,44 @@ private void determineInternalType() { } } + protected boolean checkEmptyKeyword(int parentShapeIndex, InternalSpatialDatatype isd, boolean isInsideAnotherShape) { + String potentialEmptyKeyword = getNextStringToken().toUpperCase(Locale.US); + if (potentialEmptyKeyword.equals("EMPTY")) { + + byte typeCode = 0; + + if (isInsideAnotherShape) { + byte parentTypeCode = isd.getTypeCode(); + if (parentTypeCode == 4) { // MultiPoint + typeCode = InternalSpatialDatatype.POINT.getTypeCode(); + } else if (parentTypeCode == 5) { // MultiLineString + typeCode = InternalSpatialDatatype.LINESTRING.getTypeCode(); + } else if (parentTypeCode == 6) { // MultiPolygon + typeCode = InternalSpatialDatatype.POLYGON.getTypeCode(); + } else if (parentTypeCode == 7) { // GeometryCollection + typeCode = InternalSpatialDatatype.GEOMETRYCOLLECTION.getTypeCode(); + } else { + throw new IllegalArgumentException("Illegal parentTypeCode."); + } + } else { + typeCode = isd.getTypeCode(); + } + + shapeList.add(new Shape(parentShapeIndex, -1, typeCode)); + skipWhiteSpaces(); + if (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) == ',') { + currentWktPos++; + skipWhiteSpaces(); + } + return true; + } + + if (!potentialEmptyKeyword.equals("")) { + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + } + return false; + } + private void incrementPointNumStartIfPointNotReused(int pointEndIndex) { // We need to increment PointNumStart if the last point was actually not re-used in the points array. // 0 for pointNumEnd indicates that this check is not applicable. @@ -938,6 +1250,10 @@ private void incrementPointNumStartIfPointNotReused(int pointEndIndex) { } } + /** + * Helper used for resurcive iteration for constructing GeometryCollection in WKT form. + * @param shapeEndIndex + */ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { //phase 1: assume that there is no multi - stuff and no geometrycollection while (currentShapeIndex < shapeEndIndex) { @@ -950,6 +1266,7 @@ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { int shapeIndexEnd = numberOfShapes; int figureIndexIncrement = 0; int segmentIndexIncrement = 0; + int shapeIndexIncrement = 0; int localCurrentSegmentIndex = 0; int localCurrentShapeIndex = 0; @@ -1012,19 +1329,37 @@ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { int thisShapesParentOffset = shapes[currentShapeIndex].getParentOffset(); + int tempShapeIndex = currentShapeIndex; + // Increment shapeStartIndex to account for the shape index that either Multipoint, MultiLineString // or MultiPolygon takes up - currentShapeIndex++; - while (currentShapeIndex < shapes.length - 1 && - shapes[currentShapeIndex].getParentOffset() != thisShapesParentOffset) { - figureIndexEnd = shapes[currentShapeIndex + 1].getFigureOffset(); - currentShapeIndex++; + tempShapeIndex++; + while (tempShapeIndex < shapes.length && + shapes[tempShapeIndex].getParentOffset() != thisShapesParentOffset) { + if (!(tempShapeIndex == shapes.length - 1) && // last iteration, don't check for shapes[tempShapeIndex + 1] + !(shapes[tempShapeIndex + 1].getFigureOffset() == -1)) { // disregard EMPTY cases + figureIndexEnd = shapes[tempShapeIndex + 1].getFigureOffset(); + } + tempShapeIndex++; } figureIndexIncrement = figureIndexEnd - currentFigureIndex; + shapeIndexIncrement = tempShapeIndex - currentShapeIndex; + shapeIndexEnd = tempShapeIndex; break; case GEOMETRYCOLLECTION: appendToWKTBuffers(isd.getTypeName()); + + // handle Empty GeometryCollection cases + if (shapes[currentShapeIndex].getFigureOffset() == -1) { + appendToWKTBuffers(" EMPTY"); + currentShapeIndex++; + if (currentShapeIndex < shapeEndIndex) { + appendToWKTBuffers(", "); + } + continue; + } + appendToWKTBuffers("("); int geometryCollectionParentIndex = shapes[currentShapeIndex].getParentOffset(); @@ -1032,7 +1367,6 @@ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { // Needed to keep track of which shape we are at, inside the for loop localCurrentShapeIndex = currentShapeIndex; - while (localCurrentShapeIndex < shapes.length - 1 && shapes[localCurrentShapeIndex + 1].getParentOffset() > geometryCollectionParentIndex) { localCurrentShapeIndex++; @@ -1073,9 +1407,10 @@ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { break; } - constructWKT(isd, pointIndexEnd, figureIndexEnd, segmentIndexEnd, shapeIndexEnd); + constructWKT(this, isd, pointIndexEnd, figureIndexEnd, segmentIndexEnd, shapeIndexEnd); currentFigureIndex = currentFigureIndex + figureIndexIncrement; currentSegmentIndex = currentSegmentIndex + segmentIndexIncrement; + currentShapeIndex = currentShapeIndex + shapeIndexIncrement; if (currentShapeIndex < shapeEndIndex) { appendToWKTBuffers(", "); @@ -1083,7 +1418,14 @@ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { } } - //Calculates how many segments will be used by this CompoundCurve + /** + * Calculates how many segments will be used by this shape. + * Needed to determine when the shape that uses segments (e.g. CompoundCurve) needs to stop reading + * in cases where the CompoundCurve is included as part of GeometryCollection. + * @param segmentStart + * @param pointDifference + * @return the number of segments that will be used by this shape. + */ private int calculateSegmentIncrement(int segmentStart, int pointDifference) { @@ -1176,7 +1518,7 @@ private void readComma() { currentWktPos++; skipWhiteSpaces(); } else { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } } @@ -1185,14 +1527,12 @@ private void skipWhiteSpaces() { currentWktPos++; } } - - protected void appendToWKTBuffers(Object o) { - WKTsb.append(o); - WKTsbNoZM.append(o); - } } - +/** + * Class to hold and represent the internal makings of a Figure. + * + */ class Figure { private byte figuresAttribute; private int pointOffset; @@ -1215,6 +1555,10 @@ public void setFiguresAttribute(byte fa) { } } +/** + * Class to hold and represent the internal makings of a Shape. + * + */ class Shape { private int parentOffset; private int figureOffset; @@ -1237,8 +1581,17 @@ public int getFigureOffset() { public byte getOpenGISType() { return openGISType; } + + public void setFigureOffset(int fo) { + figureOffset = fo; + } + } +/** + * Class to hold and represent the internal makings of a Segment. + * + */ class Segment { private byte segmentType; @@ -1251,6 +1604,10 @@ public byte getSegmentType() { } } +/** + * Class to hold and represent the internal makings of a Point. + * + */ class Point { private final double x; private final double y; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java new file mode 100644 index 000000000..c87b0c230 --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java @@ -0,0 +1,458 @@ +/* + * Microsoft JDBC Driver for SQL Server + * + * Copyright(c) Microsoft Corporation All rights reserved. + * + * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information. + */ +package com.microsoft.sqlserver.jdbc.datatypes; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.jdbc.Geography; +import com.microsoft.sqlserver.jdbc.Geometry; +import com.microsoft.sqlserver.jdbc.SQLServerConnection; +import com.microsoft.sqlserver.jdbc.SQLServerException; +import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; +import com.microsoft.sqlserver.jdbc.SQLServerResultSet; +import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.Utils; + +/** + * Test Geometry / Geography classes + * + */ +@RunWith(JUnitPlatform.class) +public class SQLServerSpatialDatatypeTest extends AbstractTest { + + static SQLServerConnection con = null; + static Statement stmt = null; + static String geomTableName = "geometryTestTable"; + static String geogTableName = "geographyTestTable"; + static SQLServerPreparedStatement pstmt = null; + static SQLServerResultSet rs = null; + + @Test + public void testPointWkb() throws DecoderException { + String geoWKT = "POINT(3 40 5 6)"; + + byte[] geomWKB = Hex.decodeHex("00000000010F0000000000000840000000000000444000000000000014400000000000001840".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E6100000010F0000000000004440000000000000084000000000000014400000000000001840".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testLineStringWkb() throws DecoderException { + String geoWKT = "LINESTRING(1 0, 0 1, -1 0)"; + + byte[] geomWKB = Hex.decodeHex("00000000010403000000000000000000F03F00000000000000000000000000000000000000000000F03F000000000000F0BF000000000000000001000000010000000001000000FFFFFFFF0000000002".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E61000000104030000000000000000000000000000000000F03F000000000000F03F00000000000000000000000000000000000000000000F0BF01000000010000000001000000FFFFFFFF0000000002".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testPolygonWkb() throws DecoderException { + String geoWKT = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1))"; + + byte[] geomWKB = Hex.decodeHex("000000000104090000000000000000000000000000000000000000000000000000000000000000000840000000000000084000000000000008400000000000000840000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F00000000000000400000000000000040000000000000F03F000000000000F03F000000000000F03F020000000200000000000500000001000000FFFFFFFF0000000003".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E61000000200090000000000000000000000000000000000000000000000000008400000000000000000000000000000084000000000000008400000000000000000000000000000084000000000000000000000000000000000000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F020000000100000000010500000001000000FFFFFFFF0000000003".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testMultiPointWkb() throws DecoderException { + String geoWKT = "MULTIPOINT((2 3), (7 8 9.5))"; + + byte[] geomWKB = Hex.decodeHex("00000000010502000000000000000000004000000000000008400000000000001C400000000000002040000000000000F8FF0000000000002340020000000100000000010100000003000000FFFFFFFF0000000004000000000000000001000000000100000001".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E61000000105020000000000000000000840000000000000004000000000000020400000000000001C40000000000000F8FF0000000000002340020000000100000000010100000003000000FFFFFFFF0000000004000000000000000001000000000100000001".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testMultiLineStringWkb() throws DecoderException { + String geoWKT = "MULTILINESTRING((0 2, 1 1), (1 0, 1 1))"; + + byte[] geomWKB = Hex.decodeHex("0000000001040400000000000000000000000000000000000040000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F020000000100000000010200000003000000FFFFFFFF0000000005000000000000000002000000000100000002".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E610000001040400000000000000000000400000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F000000000000F03F020000000100000000010200000003000000FFFFFFFF0000000005000000000000000002000000000100000002".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testMultiPolygonWkb() throws DecoderException { + String geoWKT = "MULTIPOLYGON(((1 1, 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; + + byte[] geomWKB = Hex.decodeHex("0000000001010D000000000000000000F03F000000000000F03F000000000000F03F00000000000000400000000000000040000000000000F03F000000000000F03F000000000000F03F000000000000000000000000000000000000000000000000000000000000084000000000000008400000000000000840000000000000084000000000000000000000000000000000000000000000000000000000000022400000000000002240000000000000224000000000000024400000000000002440000000000000224000000000000022400000000000002240000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0000000000001C40000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0300000002000000000004000000020900000003000000FFFFFFFF0000000006000000000000000003000000000200000003".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E610000002010D000000000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F000000000000000000000000000000000000000000000840000000000000000000000000000008400000000000000840000000000000000000000000000008400000000000000000000000000000000000000000000022400000000000002240000000000000244000000000000022400000000000002240000000000000244000000000000022400000000000002240000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0000000000001C40000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0300000001000000000104000000010900000003000000FFFFFFFF0000000006000000000000000003000000000200000003".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testGeometryCollectionWkb() throws DecoderException { + String geoWKT = "GEOMETRYCOLLECTION(POINT(3 3 1), LINESTRING(1 0, 0 1, -1 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2)), MULTIPOINT((2 3), (7 8 9.5)), MULTILINESTRING((0 2, 1 1), (1 0, 1 1)), MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), ((9 9, 9 10, 10 9, 9 9))), COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1)), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778))), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; + + byte[] geomWKB = Hex.decodeHex("0100000002014A00000000000000000008400000000000000840000000000000F03F00000000000000000000000000000000000000000000F03F000000000000F0BF0000000000000000000000000000F03F00000000000008400000000000000840000000000000144000000000000010400000000000001C400000000000001C400000000000000840000000000000F03F000000000000084000000000000000000000000000000000000000000000F03F0000000000002440000000000000F03F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000002440000000000000F03F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000002440000000000000F03F000000000000000000000000000000000000000000000000000000000000004000000000000008400000000000001C40000000000000204000000000000000000000000000000040000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000000000000000000000000000000000000840000000000000084000000000000008400000000000000840000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F00000000000000400000000000000040000000000000F03F000000000000F03F000000000000F03F00000000000022400000000000002240000000000000224000000000000024400000000000002440000000000000224000000000000022400000000000002240000000000000F03F00000000000000000000000000000000000000000000F03F0000000000002240000000000000184000000000000020400000000000001C40000000000000F0BF00000000000000000000000000001C40000000000000224000000000000024C00000000000000040000000000040534000000000004053400000000000005640000000000000564000000000000000400000000000001840000000000000084000000000000008400000000000001C400000000000001C40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C7D79E59127037C00000000000000000C7D79E591270374000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C00000000000000000C7D79E59127037C00000000000001C400000000000001C400000000000000000C7D79E5912703740000000000000204000000000000020400000000000002040000000000000204000000000008046C0C7D79E591270374000000000008056C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C000000000000000000000000000000000000000000000F03F0000000000002440000000000000F03F000000000000000000000000000000000000000000000000000000000000F03F000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000004000000000000008400000000000001040000000000000004000000000000000400000000000000840000000000000104000000000000000400000000000000040000000000000084000000000000010400000000000000040000000000000F8FF0000000000002340000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF00000000000008400000000000000840000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000405340000000000000564000000000000010400000000000001840000000000000F03F000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0000000000000040000000000000084000000000000010400000000000000040120000000100000000010100000002040000000109000000010D00000001110000000115000000011600000001170000000119000000011B00000001200000000124000000032800000001340000000338000000033C000000014600000011000000FFFFFFFF0000000007000000000000000001000000000100000002000000000200000008000000000300000003000000000600000004050000000600000001050000000700000001000000000800000005080000000800000002080000000900000002000000000A000000060B0000000A000000030B0000000C00000003000000000D00000009000000000E0000000A0000000011000000031000000003010302000002000203020003010203".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E610000002214A000000000000000000084000000000000008400000000000000000000000000000F03F000000000000F03F00000000000000000000000000000000000000000000F0BF0000000000000840000000000000F03F000000000000144000000000000008400000000000001C40000000000000104000000000000008400000000000001C400000000000000840000000000000F03F000000000000000000000000000000000000000000002440000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000000000000000000000000000000000000000000000002440000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000000000000000000000000000000000000000000000002440000000000000F03F0000000000000000000000000000F03F000000000000000000000000000000000000000000000840000000000000004000000000000020400000000000001C4000000000000000400000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000000000000000000008400000000000000000000000000000084000000000000008400000000000000000000000000000084000000000000000000000000000000000000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F000000000000224000000000000022400000000000002440000000000000224000000000000022400000000000002440000000000000224000000000000022400000000000000000000000000000F03F000000000000F03F0000000000000000000000000000184000000000000022400000000000001C4000000000000020400000000000000000000000000000F0BF00000000000022400000000000001C40000000000000004000000000000024C0000000000040534000000000004053400000000000005640000000000000564000000000000018400000000000000040000000000000084000000000000008400000000000001C400000000000001C4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C7D79E59127037C00000000000000000C7D79E59127037400000000000000000C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C000000000000000000000000000001C400000000000001C40C7D79E591270374000000000000000000000000000002040000000000000204000000000000020400000000000002040C7D79E591270374000000000008046C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000000000000000000000000000000000000000000000002440000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000000000000000F03F000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000004000000000000008400000000000001040000000000000004000000000000000400000000000000840000000000000104000000000000000400000000000000040000000000000084000000000000010400000000000000040000000000000F8FF0000000000002340000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF00000000000008400000000000000840000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000405340000000000000564000000000000010400000000000001840000000000000F03F000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0000000000000040000000000000084000000000000010400000000000000040120000000100000000010100000002040000000109000000010D00000001110000000115000000011600000001170000000119000000011B00000001200000000124000000032800000001340000000338000000033C000000014600000011000000FFFFFFFF0000000007000000000000000001000000000100000002000000000200000008000000000300000003000000000600000004050000000600000001050000000700000001000000000800000005080000000800000002080000000900000002000000000A000000060B0000000A000000030B0000000C00000003000000000D00000009000000000E0000000A0000000011000000031000000003010302000002000203020003010203".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testCircularStringWkb() throws DecoderException { + String geoWKT = "CIRCULARSTRING(2 1 3 4, 1 2 3, 0 7 3, 1 0 3, 2 1 3)"; + + byte[] geomWKB = Hex.decodeHex("000000000207050000000000000000000040000000000000F03F000000000000F03F000000000000004000000000000000000000000000001C40000000000000F03F00000000000000000000000000000040000000000000F03F000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000001040000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF01000000020000000001000000FFFFFFFF0000000008".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E6100000020705000000000000000000F03F00000000000000400000000000000040000000000000F03F0000000000001C4000000000000000000000000000000000000000000000F03F000000000000F03F0000000000000040000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000001040000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF01000000020000000001000000FFFFFFFF0000000008".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testCompoundCurveWkb() throws DecoderException { + String geoWKT = "COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1))"; + + byte[] geomWKB = Hex.decodeHex("0000000002050C000000000000000000F03F00000000000000000000000000000000000000000000F03F0000000000002240000000000000184000000000000020400000000000001C40000000000000F0BF00000000000000000000000000001C40000000000000224000000000000024C00000000000000040000000000040534000000000004053400000000000005640000000000000564000000000000000400000000000001840000000000000084000000000000008400000000000001C400000000000001C4000000000000008400000000000000840000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000405340000000000000564000000000000010400000000000001840000000000000F03F01000000030000000001000000FFFFFFFF0000000009080000000301030200000200".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E610000002050C0000000000000000000000000000000000F03F000000000000F03F0000000000000000000000000000184000000000000022400000000000001C4000000000000020400000000000000000000000000000F0BF00000000000022400000000000001C40000000000000004000000000000024C0000000000040534000000000004053400000000000005640000000000000564000000000000018400000000000000040000000000000084000000000000008400000000000001C400000000000001C4000000000000008400000000000000840000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000405340000000000000564000000000000010400000000000001840000000000000F03F01000000030000000001000000FFFFFFFF0000000009080000000301030200000200".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testCurvePolygonWkb() throws DecoderException { + String geoWKT = "CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778)))"; + + byte[] geomWKB = Hex.decodeHex("0A00000002001700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000008400000000000000840000000000000144000000000000010400000000000001C400000000000001C400000000000000840000000000000F03F00000000000008400000000000000000C7D79E59127037C00000000000000000C7D79E591270374000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C00000000000000000C7D79E59127037C00000000000001C400000000000001C400000000000000000C7D79E5912703740000000000000204000000000000020400000000000002040000000000000204000000000008046C0C7D79E591270374000000000008056C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E6100000020017000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000840000000000000F03F000000000000144000000000000008400000000000001C40000000000000104000000000000008400000000000001C400000000000000840000000000000F03FC7D79E59127037C00000000000000000C7D79E59127037400000000000000000C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C000000000000000000000000000001C400000000000001C40C7D79E591270374000000000000000000000000000002040000000000000204000000000000020400000000000002040C7D79E591270374000000000008046C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C0000000000000000004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); + + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testFullGlobeWkb() throws DecoderException { + String geoWKT = "FULLGLOBE"; + + byte[] geogWKB = Hex.decodeHex("E61000000224000000000000000001000000FFFFFFFFFFFFFFFF0B".toCharArray()); + + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geogWKT.asTextZM(), geoWKT); + } + + @Test + public void testPointWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "POINT(3 40 5 6)"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "POINT EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testLineStringWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "LINESTRING(1 0, 0 1, -1 0)"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "LINESTRING EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testPolygonWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "POLYGON EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testMultiPointWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "MULTIPOINT((2 3), (7 8 9.5))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "MULTIPOINT EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testMultiLineStringWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "MULTILINESTRING((0 2, 1 1), (1 0, 1 1))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "MULTILINESTRING EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testMultiPolygonWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "MULTIPOLYGON(((1 1, 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "MULTIPOLYGON EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testGeometryCollectionWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "GEOMETRYCOLLECTION(POINT(3 3 1), LINESTRING(1 0, 0 1, -1 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2)), MULTIPOINT((2 3), (7 8 9.5)), MULTILINESTRING((0 2, 1 1), (1 0, 1 1)), MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), ((9 9, 9 10, 10 9, 9 9))), COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1)), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778))), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "GEOMETRYCOLLECTION EMPTY"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY)"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "GEOMETRYCOLLECTION(POINT(3 3 1), GEOMETRYCOLLECTION EMPTY, MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), EMPTY, EMPTY, ((0 0, 1 1, 2 2, 0 0)), EMPTY), GEOMETRYCOLLECTION EMPTY)"; + + testWkt(geoWKT); + } + + @Test + public void testCircularStringWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "CIRCULARSTRING(2 1 3 4, 1 2 3, 0 7 3, 1 0 3, 2 1 3)"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "CIRCULARSTRING EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testCompoundCurveWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "COMPOUNDCURVE EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testCurvePolygonWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778)))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "CURVEPOLYGON EMPTY"; + + testWkt(geoWKT); + } + + @Test + public void testFullGlobeWkt() throws SQLException { + beforeEachSetup(); + + String geoWKT = "FULLGLOBE"; + + Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); + + try { + Geometry.STGeomFromText(geoWKT, 0); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Fullglobe is not supported for Geometry."); + } + + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); + + pstmt.setGeography(1, geogWKT); + + pstmt.execute(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); + rs.next(); + assertEquals(rs.getGeography(1).asTextZM(), geoWKT); + } + + private void beforeEachSetup() throws SQLException { + Utils.dropTableIfExists(geomTableName, stmt); + Utils.dropTableIfExists(geogTableName, stmt); + stmt.executeUpdate("Create table " + geomTableName + " (c1 geometry)"); + stmt.executeUpdate("Create table " + geogTableName + " (c1 geography)"); + } + + private void testWkt(String geoWKT) throws SQLException { + Geometry geomWKT = Geometry.STGeomFromText(geoWKT, 0); + Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); + + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geomTableName + " values (?)"); + + pstmt.setGeometry(1, geomWKT); + + pstmt.execute(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); + rs.next(); + assertEquals(rs.getGeometry(1).asTextZM(), geoWKT); + + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); + + pstmt.setGeography(1, geogWKT); + + pstmt.execute(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); + rs.next(); + assertEquals(rs.getGeography(1).asTextZM(), geoWKT); + } + + /** + * Prepare test + * + * @throws SQLException + * @throws SecurityException + * @throws IOException + */ + @BeforeAll + public static void setupHere() throws SQLException, SecurityException, IOException { + con = (SQLServerConnection) DriverManager.getConnection(connectionString); + stmt = con.createStatement(); + } + + /** + * drop the tables + * + * @throws SQLException + */ + @AfterAll + public static void afterAll() throws SQLException { + Utils.dropTableIfExists(geomTableName, stmt); + Utils.dropTableIfExists(geogTableName, stmt); + + if (null != stmt) { + stmt.close(); + } + + if (null != pstmt) { + pstmt.close(); + } + + if (null != rs) { + rs.close(); + } + + if (null != con) { + con.close(); + } + } +} From a747361423aa02f26518902bf80040c2eb3d7ea1 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Tue, 21 Nov 2017 09:55:22 -0800 Subject: [PATCH 09/15] more testing --- .../microsoft/sqlserver/jdbc/Geography.java | 12 +- .../microsoft/sqlserver/jdbc/Geometry.java | 10 +- .../jdbc/SQLServerSpatialDatatype.java | 38 +- .../SQLServerSpatialDatatypeTest.java | 554 +++++++++++++++++- 4 files changed, 594 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java index 2c79a0a0d..aab2715b5 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java @@ -21,7 +21,13 @@ private Geography(String WellKnownText, int srid) { this.wkt = WellKnownText; this.srid = srid; - parseWKTForSerialization(this, currentWktPos, -1, false); + try { + parseWKTForSerialization(this, currentWktPos, -1, false); + } + catch (StringIndexOutOfBoundsException e) { + throw new IllegalArgumentException("Reached unexpected end of WKT. Please make sure WKT is valid."); + } + serializeToWkb(false); isNull = false; } @@ -85,12 +91,13 @@ public static Geography deserialize(byte[] wkb) { /** * Returns a Geography instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation. + * SRID is defaulted to 4326. * * @param wkt * @return */ public static Geography parse(String wkt) { - return new Geography(wkt, 0); + return new Geography(wkt, 4326); } /** @@ -300,7 +307,6 @@ protected void parseWkb() { readMvalues(); } - //TODO: do I need to do anything when it's isSinglePoint or isSingleLineSegment? if (!(isSinglePoint || isSingleLineSegment)) { readNumberOfFigures(); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index 0b955f07c..fef747efd 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -21,7 +21,13 @@ private Geometry(String WellKnownText, int srid) { this.wkt = WellKnownText; this.srid = srid; - parseWKTForSerialization(this, currentWktPos, -1, false); + try { + parseWKTForSerialization(this, currentWktPos, -1, false); + } + catch (StringIndexOutOfBoundsException e) { + throw new IllegalArgumentException("Reached unexpected end of WKT. Please make sure WKT is valid."); + } + serializeToWkb(false); isNull = false; } @@ -85,6 +91,7 @@ public static Geometry deserialize(byte[] wkb) { /** * Returns a Geometry instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation. + * SRID is defaulted to 0. * * @param wkt * @return @@ -300,7 +307,6 @@ protected void parseWkb() { readMvalues(); } - //TODO: do I need to do anything when it's isSinglePoint or isSingleLineSegment? if (!(isSinglePoint || isSingleLineSegment)) { readNumberOfFigures(); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java index 3b8ff354a..d26eacc13 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java @@ -197,7 +197,13 @@ protected void parseWKTForSerialization(SQLServerSpatialDatatype sd, int startPo String nextToken = getNextStringToken().toUpperCase(Locale.US); int thisShapeIndex; - InternalSpatialDatatype isd = InternalSpatialDatatype.valueOf(nextToken); + InternalSpatialDatatype isd = InternalSpatialDatatype.INVALID_TYPE; + try { + isd = InternalSpatialDatatype.valueOf(nextToken); + } + catch (Exception e) { + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + } byte fa = 0; if (version == 1 && (nextToken.equals("CIRCULARSTRING") || nextToken.equals("COMPOUNDCURVE") || @@ -558,10 +564,7 @@ protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex byte segment = segments[segmentStartIndex].getSegmentType(); constructSegmentWKT(segmentStartIndex, segment, pointEndIndex); - if (segmentStartIndex >= segmentEndIndex - 1) { - appendToWKTBuffers(")"); - // about to exit while loop, but not the last segment = we are closing Compoundcurve. - } else if (!(currentPointIndex < pointEndIndex)) { + if (!(currentPointIndex < pointEndIndex)) { appendToWKTBuffers("))"); } else { switch (segment) { @@ -590,12 +593,10 @@ protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex return; } - if (i == figureEndIndex - 1) { - appendToWKTBuffers(")"); - } else { + //Append a comma if this is not the last figure of the shape. + if (i != figureEndIndex - 1) { appendToWKTBuffers(", "); } - } } @@ -713,16 +714,28 @@ protected void readPointWkt() { throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); } + numOfCoordinates++; + skipWhiteSpaces(); + + // After skipping white space after the 4th coordinate has been read, the next + // character has to be either a , or ), or the WKT is invalid. + if (numOfCoordinates == 4) { + if (wkt.charAt(currentWktPos) != ',' && wkt.charAt(currentWktPos) != ')') { + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + } + } + if (wkt.charAt(currentWktPos) == ',') { + // need at least 2 coordinates + if (numOfCoordinates == 1) { + throw new IllegalArgumentException("Illegal character at wkt position " + currentWktPos); + } currentWktPos++; skipWhiteSpaces(); - numOfCoordinates++; break; } skipWhiteSpaces(); - - numOfCoordinates++; } if (numOfCoordinates == 4) { @@ -1058,7 +1071,6 @@ protected void createSerializationProperties() { serializationProperties += isSingleLineSegmentMask; } - //TODO look into how the isLargerThanHemisphere is created if (version == 2) { if (isLargerThanHemisphere) { serializationProperties += isLargerThanHemisphereMask; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java index c87b0c230..cdbaca8c0 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java @@ -13,6 +13,8 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; @@ -42,6 +44,7 @@ public class SQLServerSpatialDatatypeTest extends AbstractTest { static Statement stmt = null; static String geomTableName = "geometryTestTable"; static String geogTableName = "geographyTestTable"; + static String spatialDatatypeTableName = "spatialDatatypeTestTable"; static SQLServerPreparedStatement pstmt = null; static SQLServerResultSet rs = null; @@ -384,6 +387,539 @@ public void testFullGlobeWkt() throws SQLException { assertEquals(rs.getGeography(1).asTextZM(), geoWKT); } + @Test + public void testIrregularCases() throws SQLException { + beforeEachSetup(); + + String geoWKT = " GeOMETRyCOLlECTION(POINT( 3e2 2E1 1 ), GEOMETRYCOLLECTION EmPTy , GeometryCollection(GEometryCOLLEction(GEometryCOLLEction Empty)), " + + "POLYGON( (0 0 2, 1 10 3, 1 0 4, 0 0 2)) )"; + + String geoWKTSS = "GEOMETRYCOLLECTION(POINT(300 20 1), GEOMETRYCOLLECTION EMPTY, GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY)), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; + + testWkt(geoWKT, geoWKTSS); + } + + @Test + public void testIllegalCases() throws SQLException { + //Not enough closing bracket case + String geoWKT = "MULTIPOLYGON(((1 1, 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Reached unexpected end of WKT. Please make sure WKT is valid."); + } + + //Not enough closing and opening bracket case + geoWKT = "MULTIPOLYGON((1 1, 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Illegal character at wkt position 14"); + } + + //Too many closing bracket + geoWKT = "MULTIPOLYGON(((1 1, 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9))))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Illegal character at wkt position 91"); + } + + //Too many opening bracket + geoWKT = "MULTIPOLYGON((((1 1, 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Illegal character at wkt position 15"); + } + + //Too many coordinates + geoWKT = "MULTIPOLYGON(((1 1 3 4 5, 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Illegal character at wkt position 23"); + } + + //Too little coordinates + geoWKT = "MULTIPOLYGON(((1 , 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Illegal character at wkt position 17"); + } + + //Incorrect data type + geoWKT = "IvnalidPolygon(((1 , 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Illegal character at wkt position 14"); + } + + // too many commas + geoWKT = "MULTIPOLYGON(((1 1, 1 2, 2 1, 1 1),, (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Illegal character at wkt position 35"); + } + + // too little commas + geoWKT = "MULTIPOLYGON(((1 1, 1 2, 2 1, 1 1) (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; + + try { + testWkt(geoWKT); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Illegal character at wkt position 35"); + } + } + + @Test + public void testAllTypes() throws SQLException { + beforeEachSetup(); + + String geoWKTPoint = "POINT(30 12.12312312 5 6)"; + String geoWKTLineString = "LINESTRING(1 1, 2 4 3, 3 9 123 332)"; + String geoWKTCircularString = "CIRCULARSTRING(1 1, 2 4, 3 9)"; + String geoWKTCompoundCurve = "COMPOUNDCURVE((1 1, 1 3), (1 3, 3 3), (3 3, 3 1), (3 1, 1 1))"; + String geoWKTCurvePolygon = "CURVEPOLYGON(CIRCULARSTRING(2 4, 4 2, 6 4, 4 6, 2 4))"; + String geoWKTPolygon = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1))"; + String geoWKTMultiPoint = "MULTIPOINT((2 3), (7 8 9.5))"; + String geoWKTMultiLineString = "MULTILINESTRING((0 2, 1 1), (1 0, 1 1))"; + String geoWKTMultiPolygon = "MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), ((9 9, 9 10, 10 9, 9 9)))"; + String geoWKTGeometryCollection = "GEOMETRYCOLLECTION(POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)), POINT(3 3 1 2.5), LINESTRING(1 0, 0 1, -1 0), GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(1 2 3 4))), GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778))))"; + + List geoWKTList = new ArrayList(); + + geoWKTList.add(geoWKTPoint); + geoWKTList.add(geoWKTLineString); + geoWKTList.add(geoWKTCircularString); + geoWKTList.add(geoWKTCompoundCurve); + geoWKTList.add(geoWKTCurvePolygon); + geoWKTList.add(geoWKTPolygon); + geoWKTList.add(geoWKTMultiPoint); + geoWKTList.add(geoWKTMultiLineString); + geoWKTList.add(geoWKTMultiPolygon); + geoWKTList.add(geoWKTGeometryCollection); + + Geometry geomWKT; + Geography geogWKT; + + //Geometry + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geomTableName + " values (?)"); + + geomWKT = Geometry.STGeomFromText(geoWKTPoint, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTLineString, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTCircularString, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTCompoundCurve, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTCurvePolygon, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTPolygon, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiPoint, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiLineString, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiPolygon, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTGeometryCollection, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); + for (int i = 0; i < geoWKTList.size(); i++) { + rs.next(); + assertEquals(rs.getGeometry(1).asTextZM(), geoWKTList.get(i)); + } + + //Geography + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); + + geogWKT = Geography.STGeomFromText(geoWKTPoint, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTLineString, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTCircularString, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTCompoundCurve, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTCurvePolygon, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTPolygon, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTMultiPoint, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTMultiLineString, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTMultiPolygon, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTGeometryCollection, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); + for (int i = 0; i < geoWKTList.size(); i++) { + rs.next(); + assertEquals(rs.getGeography(1).asTextZM(), geoWKTList.get(i)); + } + } + + @Test + public void testMixedAllTypes() throws SQLException { + beforeEachSetupSpatialDatatype(); + + String geoWKTPoint = "POINT(30 12.12312312 5 6)"; + String geoWKTLineString = "LINESTRING(1 1, 2 4 3, 3 9 123 332)"; + String geoWKTCircularString = "CIRCULARSTRING(1 1, 2 4, 3 9)"; + String geoWKTCompoundCurve = "COMPOUNDCURVE((1 1, 1 3), (1 3, 3 3), (3 3, 3 1), (3 1, 1 1))"; + String geoWKTCurvePolygon = "CURVEPOLYGON(CIRCULARSTRING(2 4, 4 2, 6 4, 4 6, 2 4))"; + String geoWKTPolygon = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1))"; + String geoWKTMultiPoint = "MULTIPOINT((2 3), (7 8 9.5))"; + String geoWKTMultiLineString = "MULTILINESTRING((0 2, 1 1), (1 0, 1 1))"; + String geoWKTMultiPolygon = "MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), ((9 9, 9 10, 10 9, 9 9)))"; + String geoWKTGeometryCollection = "GEOMETRYCOLLECTION(POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)), POINT(3 3 1 2.5), LINESTRING(1 0, 0 1, -1 0), GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(1 2 3 4))), GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778))))"; + + String s = "some string"; + Double d = 31.34; + int i2 = 5; + + List geoWKTList = new ArrayList(); + + geoWKTList.add(geoWKTPoint); + geoWKTList.add(geoWKTLineString); + geoWKTList.add(geoWKTCircularString); + geoWKTList.add(geoWKTCompoundCurve); + geoWKTList.add(geoWKTCurvePolygon); + geoWKTList.add(geoWKTPolygon); + geoWKTList.add(geoWKTMultiPoint); + geoWKTList.add(geoWKTMultiLineString); + geoWKTList.add(geoWKTMultiPolygon); + geoWKTList.add(geoWKTGeometryCollection); + + Geometry geomWKT; + Geography geogWKT; + + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ spatialDatatypeTableName + " values (?, ?, ?, ?, ?)"); + + geomWKT = Geometry.STGeomFromText(geoWKTPoint, 0); + geogWKT = Geography.STGeomFromText(geoWKTPoint, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTLineString, 0); + geogWKT = Geography.STGeomFromText(geoWKTLineString, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTCircularString, 0); + geogWKT = Geography.STGeomFromText(geoWKTCircularString, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTCompoundCurve, 0); + geogWKT = Geography.STGeomFromText(geoWKTCompoundCurve, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTCurvePolygon, 0); + geogWKT = Geography.STGeomFromText(geoWKTCurvePolygon, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTPolygon, 0); + geogWKT = Geography.STGeomFromText(geoWKTPolygon, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiPoint, 0); + geogWKT = Geography.STGeomFromText(geoWKTMultiPoint, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiLineString, 0); + geogWKT = Geography.STGeomFromText(geoWKTMultiLineString, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiPolygon, 0); + geogWKT = Geography.STGeomFromText(geoWKTMultiPolygon, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTGeometryCollection, 0); + geogWKT = Geography.STGeomFromText(geoWKTGeometryCollection, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + rs = (SQLServerResultSet) stmt.executeQuery("select * from " + spatialDatatypeTableName); + for (int i = 0; i < geoWKTList.size(); i++) { + rs.next(); + assertEquals(rs.getGeometry(1).asTextZM(), geoWKTList.get(i)); + assertEquals(rs.getGeography(2).asTextZM(), geoWKTList.get(i)); + assertEquals(rs.getString(3), s); + assertEquals((Double) rs.getDouble(4), d); + assertEquals(rs.getInt(5), i2); + } + } + + @Test + public void testDecimalRounding() throws SQLException { + beforeEachSetup(); + + String geoWKT = "POINT(3 40.7777777777777777777 5 6)"; + + String geoWKTSS = "POINT(3 40.77777777777778 5 6)"; + + testWkt(geoWKT, geoWKTSS); + } + + @Test + public void testParse() throws SQLException { + beforeEachSetup(); + + String geoWKT = "GEOMETRYCOLLECTION(POINT(300 20 1), GEOMETRYCOLLECTION EMPTY, GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY)), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; + + Geometry geomWKT = Geometry.parse(geoWKT); + Geography geogWKT = Geography.parse(geoWKT); + + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geomTableName + " values (?)"); + + pstmt.setGeometry(1, geomWKT); + + pstmt.execute(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); + rs.next(); + assertEquals(rs.getGeometry(1).asTextZM(), geoWKT); + assertEquals(rs.getGeometry(1).getSrid(), 0); + + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); + + pstmt.setGeography(1, geogWKT); + + pstmt.execute(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); + rs.next(); + assertEquals(rs.getGeography(1).asTextZM(), geoWKT); + assertEquals(rs.getGeography(1).getSrid(), 4326); + } + + @Test + public void testPoint() throws SQLException { + beforeEachSetup(); + + String geoWKT = "POINT(1 2)"; + + Geometry geomWKT = Geometry.point(1, 2, 0); + Geography geogWKT = Geography.point(1, 2, 4326); + + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geomTableName + " values (?)"); + + pstmt.setGeometry(1, geomWKT); + + pstmt.execute(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); + rs.next(); + assertEquals(rs.getGeometry(1).asTextZM(), geoWKT); + assertEquals(rs.getGeometry(1).getSrid(), 0); + + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); + + pstmt.setGeography(1, geogWKT); + + pstmt.execute(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); + rs.next(); + assertEquals(rs.getGeography(1).asTextZM(), geoWKT); + assertEquals(rs.getGeography(1).getSrid(), 4326); + } + + @Test + public void testSTAsText() throws SQLException { + beforeEachSetup(); + + String geoWKT = "GEOMETRYCOLLECTION(POINT(300 20 1), GEOMETRYCOLLECTION EMPTY, GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY)), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; + + String geoWKTSS = "GEOMETRYCOLLECTION(POINT(300 20), GEOMETRYCOLLECTION EMPTY, GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY)), POLYGON((0 0, 1 10, 1 0, 0 0)))"; + + Geometry geomWKT = Geometry.STGeomFromText(geoWKT, 0); + Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); + + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geomTableName + " values (?)"); + + pstmt.setGeometry(1, geomWKT); + + pstmt.execute(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); + rs.next(); + assertEquals(rs.getGeometry(1).STAsText(), geoWKTSS); + + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); + + pstmt.setGeography(1, geogWKT); + + pstmt.execute(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); + rs.next(); + assertEquals(rs.getGeography(1).STAsText(), geoWKTSS); + } + + @Test + public void testSTAsBinary() throws SQLException { + beforeEachSetup(); + + String geoWKT = "POINT(3 40 5 6)"; + String geoWKT2 = "POINT(3 40)"; + + Geometry geomWKT = Geometry.STGeomFromText(geoWKT, 0); + Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); + + byte[] geomWKB = geomWKT.STAsBinary(); + byte[] geogWKB = geogWKT.STAsBinary(); + + Geometry geomWKT2 = Geometry.STGeomFromText(geoWKT2, 0); + Geography geogWKT2 = Geography.STGeomFromText(geoWKT2, 4326); + + byte[] geomWKB2 = geomWKT2.STAsBinary(); + byte[] geogWKB2 = geogWKT2.STAsBinary(); + + assertEquals(geomWKB, geomWKB2); + assertEquals(geogWKB, geogWKB2); + } + private void beforeEachSetup() throws SQLException { Utils.dropTableIfExists(geomTableName, stmt); Utils.dropTableIfExists(geogTableName, stmt); @@ -391,7 +927,21 @@ private void beforeEachSetup() throws SQLException { stmt.executeUpdate("Create table " + geogTableName + " (c1 geography)"); } + private void beforeEachSetupSpatialDatatype() throws SQLException { + Utils.dropTableIfExists(spatialDatatypeTableName, stmt); + stmt.executeUpdate("Create table " + spatialDatatypeTableName + + " (c1 geometry," + + "c2 geography," + + "c3 nvarchar(512)," + + "c4 decimal(28,4)," + + "c5 int)"); + } + private void testWkt(String geoWKT) throws SQLException { + testWkt(geoWKT, geoWKT); + } + + private void testWkt(String geoWKT, String geoWKTSS) throws SQLException { Geometry geomWKT = Geometry.STGeomFromText(geoWKT, 0); Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); @@ -403,7 +953,7 @@ private void testWkt(String geoWKT) throws SQLException { rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); rs.next(); - assertEquals(rs.getGeometry(1).asTextZM(), geoWKT); + assertEquals(rs.getGeometry(1).asTextZM(), geoWKTSS); pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); @@ -413,7 +963,7 @@ private void testWkt(String geoWKT) throws SQLException { rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); rs.next(); - assertEquals(rs.getGeography(1).asTextZM(), geoWKT); + assertEquals(rs.getGeography(1).asTextZM(), geoWKTSS); } /** From 4ae99af236f1b0085e5cae7e5c1a3c055cc5b275 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Mon, 27 Nov 2017 16:40:22 -0800 Subject: [PATCH 10/15] javadoc updates --- .../microsoft/sqlserver/jdbc/Geography.java | 32 ++++----- .../microsoft/sqlserver/jdbc/Geometry.java | 32 ++++----- .../jdbc/SQLServerSpatialDatatype.java | 68 +++++++++++-------- 3 files changed, 71 insertions(+), 61 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java index aab2715b5..0c0f94c2a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java @@ -60,9 +60,9 @@ public Geography() { * Returns a Geography instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) * representation augmented with any Z (elevation) and M (measure) values carried by the instance. * - * @param wkt - * @param srid - * @return + * @param wkt WKT + * @param srid SRID + * @return Geography instance */ public static Geography STGeomFromText(String wkt, int srid) { return new Geography(wkt, srid); @@ -72,8 +72,8 @@ public static Geography STGeomFromText(String wkt, int srid) { * Returns a Geography instance from an Open Geospatial Consortium (OGC) * Well-Known Binary (WKB) representation. * - * @param wkb - * @return + * @param wkb WKB + * @return Geography instance */ public static Geography STGeomFromWKB(byte[] wkb) { return new Geography(wkb); @@ -82,8 +82,8 @@ public static Geography STGeomFromWKB(byte[] wkb) { /** * Returns a constructed Geography from an internal SQL Server format for spatial data. * - * @param wkb - * @return + * @param wkb WKB + * @return Geography instance */ public static Geography deserialize(byte[] wkb) { return new Geography(wkb); @@ -93,8 +93,8 @@ public static Geography deserialize(byte[] wkb) { * Returns a Geography instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation. * SRID is defaulted to 4326. * - * @param wkt - * @return + * @param wkt WKt + * @return Geography instance */ public static Geography parse(String wkt) { return new Geography(wkt, 4326); @@ -103,10 +103,10 @@ public static Geography parse(String wkt) { /** * Constructs a Geography instance that represents a Point instance from its X and Y values and an SRID. * - * @param x - * @param y - * @param srid - * @return + * @param x x coordinate + * @param y y coordinate + * @param srid SRID + * @return Geography instance */ public static Geography point(double x, double y, int srid) { return new Geography("POINT (" + x + " " + y + ")", srid); @@ -136,7 +136,7 @@ public String STAsText() { /** * Returns the Open Geospatial Consortium (OGC) Well-Known Binary (WKB) representation of a * Geography instance. This value will not contain any Z or M values carried by the instance. - * @return + * @return WKB */ public byte[] STAsBinary() { if (null == wkbNoZM) { @@ -148,7 +148,7 @@ public byte[] STAsBinary() { /** * Returns the bytes that represent an internal SQL Server format of Geography type. * - * @return + * @return WKB */ public byte[] serialize() { return wkb; @@ -205,7 +205,7 @@ public int STNumPoints() { /** * Returns the Open Geospatial Consortium (OGC) type name represented by a Geography instance. * - * @return + * @return type name */ public String STGeographyType() { if (null != internalType) { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index fef747efd..aff52f1a0 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -60,9 +60,9 @@ public Geometry() { * Returns a Geometry instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) * representation augmented with any Z (elevation) and M (measure) values carried by the instance. * - * @param wkt - * @param srid - * @return + * @param wkt WKT + * @param srid SRID + * @return Geometry instance */ public static Geometry STGeomFromText(String wkt, int srid) { return new Geometry(wkt, srid); @@ -72,8 +72,8 @@ public static Geometry STGeomFromText(String wkt, int srid) { * Returns a Geometry instance from an Open Geospatial Consortium (OGC) * Well-Known Binary (WKB) representation. * - * @param wkb - * @return + * @param wkb WKB + * @return Geometry instance */ public static Geometry STGeomFromWKB(byte[] wkb) { return new Geometry(wkb); @@ -82,8 +82,8 @@ public static Geometry STGeomFromWKB(byte[] wkb) { /** * Returns a constructed Geometry from an internal SQL Server format for spatial data. * - * @param wkb - * @return + * @param wkb WKB + * @return Geometry instance */ public static Geometry deserialize(byte[] wkb) { return new Geometry(wkb); @@ -93,8 +93,8 @@ public static Geometry deserialize(byte[] wkb) { * Returns a Geometry instance from an Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation. * SRID is defaulted to 0. * - * @param wkt - * @return + * @param wkt WKT + * @return Geometry instance */ public static Geometry parse(String wkt) { return new Geometry(wkt, 0); @@ -103,10 +103,10 @@ public static Geometry parse(String wkt) { /** * Constructs a Geometry instance that represents a Point instance from its X and Y values and an SRID. * - * @param x - * @param y - * @param srid - * @return + * @param x x coordinate + * @param y y coordinate + * @param srid SRID + * @return Geometry instance */ public static Geometry point(double x, double y, int srid) { return new Geometry("POINT (" + x + " " + y + ")", srid); @@ -136,7 +136,7 @@ public String STAsText() { /** * Returns the Open Geospatial Consortium (OGC) Well-Known Binary (WKB) representation of a * Geometry instance. This value will not contain any Z or M values carried by the instance. - * @return + * @return WKB */ public byte[] STAsBinary() { if (null == wkbNoZM) { @@ -148,7 +148,7 @@ public byte[] STAsBinary() { /** * Returns the bytes that represent an internal SQL Server format of Geometry type. * - * @return + * @return WKB */ public byte[] serialize() { return wkb; @@ -205,7 +205,7 @@ public int STNumPoints() { /** * Returns the Open Geospatial Consortium (OGC) type name represented by a geometry instance. * - * @return + * @return type name */ public String STGeometryType() { if (null != internalType) { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java index d26eacc13..f290959f3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java @@ -105,11 +105,11 @@ abstract class SQLServerSpatialDatatype { * Create the WKT representation of Geometry/Geography from the deserialized data. * * @param sd the Geometry/Geography instance. - * @param isd - * @param pointIndexEnd - * @param figureIndexEnd - * @param segmentIndexEnd - * @param shapeIndexEnd + * @param isd internal spatial datatype object + * @param pointIndexEnd upper bound for reading points + * @param figureIndexEnd upper bound for reading figures + * @param segmentIndexEnd upper bound for reading segments + * @param shapeIndexEnd upper bound for reading shapes */ protected void constructWKT(SQLServerSpatialDatatype sd, InternalSpatialDatatype isd, int pointIndexEnd, int figureIndexEnd, int segmentIndexEnd, int shapeIndexEnd) { @@ -359,8 +359,8 @@ protected void constructPointWKT(int pointIndex) { /** * Constructs a line in WKT form. * - * @param pointStartIndex - * @param pointEndIndex + * @param pointStartIndex . + * @param pointEndIndex . */ protected void constructLineWKT(int pointStartIndex, int pointEndIndex) { for (int i = pointStartIndex; i < pointEndIndex; i++) { @@ -376,8 +376,8 @@ protected void constructLineWKT(int pointStartIndex, int pointEndIndex) { /** * Constructs a shape (simple Geometry/Geography entities that are contained within a single bracket) in WKT form. * - * @param figureStartIndex - * @param figureEndIndex + * @param figureStartIndex . + * @param figureEndIndex . */ protected void constructShapeWKT(int figureStartIndex, int figureEndIndex) { for (int i = figureStartIndex; i < figureEndIndex; i++) { @@ -399,8 +399,8 @@ protected void constructShapeWKT(int figureStartIndex, int figureEndIndex) { /** * Constructs a mutli-shape (MultiPoint / MultiLineString) in WKT form. * - * @param figureStartIndex - * @param figureEndIndex + * @param shapeStartIndex . + * @param shapeEndIndex . */ protected void constructMultiShapeWKT(int shapeStartIndex, int shapeEndIndex) { for (int i = shapeStartIndex + 1; i < shapeEndIndex; i++) { @@ -418,9 +418,9 @@ protected void constructMultiShapeWKT(int shapeStartIndex, int shapeEndIndex) { /** * Constructs a CompoundCurve in WKT form. * - * @param segmentStartIndex - * @param segmentEndIndex - * @param pointEndIndex + * @param segmentStartIndex . + * @param segmentEndIndex . + * @param pointEndIndex . */ protected void constructCompoundcurveWKT(int segmentStartIndex, int segmentEndIndex, int pointEndIndex) { for (int i = segmentStartIndex; i < segmentEndIndex; i++) { @@ -454,9 +454,8 @@ protected void constructCompoundcurveWKT(int segmentStartIndex, int segmentEndIn /** * Constructs a MultiPolygon in WKT form. * - * @param segmentStartIndex - * @param segmentEndIndex - * @param pointEndIndex + * @param shapeStartIndex . + * @param shapeEndIndex . */ protected void constructMultipolygonWKT(int shapeStartIndex, int shapeEndIndex) { int figureStartIndex; @@ -519,9 +518,10 @@ protected void constructMultipolygonWKT(int shapeStartIndex, int shapeEndIndex) /** * Constructs a CurvePolygon in WKT form. * - * @param segmentStartIndex - * @param segmentEndIndex - * @param pointEndIndex + * @param figureStartIndex . + * @param figureEndIndex . + * @param segmentStartIndex . + * @param segmentEndIndex . */ protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex, int segmentStartIndex, int segmentEndIndex) { for (int i = figureStartIndex; i < figureEndIndex; i++) { @@ -608,9 +608,9 @@ protected void constructCurvepolygonWKT(int figureStartIndex, int figureEndIndex * the currentPointIndex depending on what segment comes next, since it may have been reused (and it's reflected * in the array of points) * - * @param segmentStartIndex - * @param segmentEndIndex - * @param pointEndIndex + * @param currentSegment . + * @param segment . + * @param pointEndIndex . */ protected void constructSegmentWKT(int currentSegment, byte segment, int pointEndIndex) { switch (segment) { @@ -670,7 +670,7 @@ protected void constructSegmentWKT(int currentSegment, byte segment, int pointEn /** * The starting point for constructing a GeometryCollection type in WKT form. * - * @param shapeEndIndex + * @param shapeEndIndex . */ protected void constructGeometryCollectionWKT(int shapeEndIndex) { currentShapeIndex++; @@ -760,8 +760,8 @@ protected void readLineWkt() { /** * Reads a shape (simple Geometry/Geography entities that are contained within a single bracket) WKT. * - * @param parentShapeIndex - * @param nextToken + * @param parentShapeIndex shape index of the parent shape that called this method + * @param nextToken next string token */ protected void readShapeWkt(int parentShapeIndex, String nextToken) { byte fa = FA_POINT; @@ -848,6 +848,9 @@ protected void readCurvePolygon() { /** * Reads a MultiPolygon WKT + * + * @param thisShapeIndex shape index of current shape + * @param nextToken next string token */ protected void readMultiPolygonWkt(int thisShapeIndex, String nextToken) { while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { @@ -871,6 +874,9 @@ protected void readMultiPolygonWkt(int thisShapeIndex, String nextToken) { /** * Reads a Segment WKT + * + * @param segmentType segment type + * @param isFirstIteration flag that indicates if this is the first iteration from the loop outside */ protected void readSegmentWkt(int segmentType, boolean isFirstIteration) { segmentList.add(new Segment((byte) segmentType)); @@ -903,6 +909,8 @@ protected void readSegmentWkt(int segmentType, boolean isFirstIteration) { /** * Reads a CompoundCurve WKT + * + * @param isFirstIteration flag that indicates if this is the first iteration from the loop outside */ protected void readCompoundCurveWkt(boolean isFirstIteration) { while (currentWktPos < wkt.length() && wkt.charAt(currentWktPos) != ')') { @@ -1264,7 +1272,8 @@ private void incrementPointNumStartIfPointNotReused(int pointEndIndex) { /** * Helper used for resurcive iteration for constructing GeometryCollection in WKT form. - * @param shapeEndIndex + * + * @param shapeEndIndex . */ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { //phase 1: assume that there is no multi - stuff and no geometrycollection @@ -1434,8 +1443,9 @@ private void constructGeometryCollectionWKThelper(int shapeEndIndex) { * Calculates how many segments will be used by this shape. * Needed to determine when the shape that uses segments (e.g. CompoundCurve) needs to stop reading * in cases where the CompoundCurve is included as part of GeometryCollection. - * @param segmentStart - * @param pointDifference + * + * @param segmentStart . + * @param pointDifference number of points that were assigned to this segment to be used. * @return the number of segments that will be used by this shape. */ private int calculateSegmentIncrement(int segmentStart, From bc1e531a3bca321af3bd67828360d9ed3dbb2667 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Wed, 28 Feb 2018 13:55:04 -0800 Subject: [PATCH 11/15] test changes to make it compatible with sql server 2008 --- .../SQLServerSpatialDatatypeTest.java | 230 ++++++++++-------- 1 file changed, 130 insertions(+), 100 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java index cdbaca8c0..5b76d0961 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java @@ -27,7 +27,6 @@ import com.microsoft.sqlserver.jdbc.Geography; import com.microsoft.sqlserver.jdbc.Geometry; import com.microsoft.sqlserver.jdbc.SQLServerConnection; -import com.microsoft.sqlserver.jdbc.SQLServerException; import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; import com.microsoft.sqlserver.jdbc.SQLServerResultSet; import com.microsoft.sqlserver.testframework.AbstractTest; @@ -47,6 +46,7 @@ public class SQLServerSpatialDatatypeTest extends AbstractTest { static String spatialDatatypeTableName = "spatialDatatypeTestTable"; static SQLServerPreparedStatement pstmt = null; static SQLServerResultSet rs = null; + static boolean isDenaliOrLater = false; @Test public void testPointWkb() throws DecoderException { @@ -176,16 +176,18 @@ public void testCompoundCurveWkb() throws DecoderException { @Test public void testCurvePolygonWkb() throws DecoderException { - String geoWKT = "CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778)))"; - - byte[] geomWKB = Hex.decodeHex("0A00000002001700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000008400000000000000840000000000000144000000000000010400000000000001C400000000000001C400000000000000840000000000000F03F00000000000008400000000000000000C7D79E59127037C00000000000000000C7D79E591270374000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C00000000000000000C7D79E59127037C00000000000001C400000000000001C400000000000000000C7D79E5912703740000000000000204000000000000020400000000000002040000000000000204000000000008046C0C7D79E591270374000000000008056C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); - byte[] geogWKB = Hex.decodeHex("E6100000020017000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000840000000000000F03F000000000000144000000000000008400000000000001C40000000000000104000000000000008400000000000001C400000000000000840000000000000F03FC7D79E59127037C00000000000000000C7D79E59127037400000000000000000C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C000000000000000000000000000001C400000000000001C40C7D79E591270374000000000000000000000000000002040000000000000204000000000000020400000000000002040C7D79E591270374000000000008046C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C0000000000000000004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); + if (isDenaliOrLater) { + String geoWKT = "CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778)))"; + + byte[] geomWKB = Hex.decodeHex("0A00000002001700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000008400000000000000840000000000000144000000000000010400000000000001C400000000000001C400000000000000840000000000000F03F00000000000008400000000000000000C7D79E59127037C00000000000000000C7D79E591270374000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C00000000000000000C7D79E59127037C00000000000001C400000000000001C400000000000000000C7D79E5912703740000000000000204000000000000020400000000000002040000000000000204000000000008046C0C7D79E591270374000000000008056C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E6100000020017000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000840000000000000F03F000000000000144000000000000008400000000000001C40000000000000104000000000000008400000000000001C400000000000000840000000000000F03FC7D79E59127037C00000000000000000C7D79E59127037400000000000000000C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C000000000000000000000000000001C400000000000001C40C7D79E591270374000000000000000000000000000002040000000000000204000000000000020400000000000002040C7D79E591270374000000000008046C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C0000000000000000004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); - Geometry geomWKT = Geometry.deserialize(geomWKB); - Geography geogWKT = Geography.deserialize(geogWKB); - - assertEquals(geomWKT.asTextZM(), geoWKT); - assertEquals(geogWKT.asTextZM(), geoWKT); + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); + } } @Test @@ -291,11 +293,14 @@ public void testMultiPolygonWkt() throws SQLException { @Test public void testGeometryCollectionWkt() throws SQLException { - beforeEachSetup(); - - String geoWKT = "GEOMETRYCOLLECTION(POINT(3 3 1), LINESTRING(1 0, 0 1, -1 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2)), MULTIPOINT((2 3), (7 8 9.5)), MULTILINESTRING((0 2, 1 1), (1 0, 1 1)), MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), ((9 9, 9 10, 10 9, 9 9))), COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1)), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778))), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; - - testWkt(geoWKT); + String geoWKT; + if (isDenaliOrLater) { + beforeEachSetup(); + + geoWKT = "GEOMETRYCOLLECTION(POINT(3 3 1), LINESTRING(1 0, 0 1, -1 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2)), MULTIPOINT((2 3), (7 8 9.5)), MULTILINESTRING((0 2, 1 1), (1 0, 1 1)), MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), ((9 9, 9 10, 10 9, 9 9))), COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1)), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778))), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; + + testWkt(geoWKT); + } beforeEachSetup(); @@ -318,32 +323,36 @@ public void testGeometryCollectionWkt() throws SQLException { @Test public void testCircularStringWkt() throws SQLException { - beforeEachSetup(); - - String geoWKT = "CIRCULARSTRING(2 1 3 4, 1 2 3, 0 7 3, 1 0 3, 2 1 3)"; - - testWkt(geoWKT); - - beforeEachSetup(); - - geoWKT = "CIRCULARSTRING EMPTY"; - - testWkt(geoWKT); + if (isDenaliOrLater) { + beforeEachSetup(); + + String geoWKT = "CIRCULARSTRING(2 1 3 4, 1 2 3, 0 7 3, 1 0 3, 2 1 3)"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "CIRCULARSTRING EMPTY"; + + testWkt(geoWKT); + } } @Test public void testCompoundCurveWkt() throws SQLException { - beforeEachSetup(); - - String geoWKT = "COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1))"; - - testWkt(geoWKT); - - beforeEachSetup(); - - geoWKT = "COMPOUNDCURVE EMPTY"; - - testWkt(geoWKT); + if (isDenaliOrLater) { + beforeEachSetup(); + + String geoWKT = "COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "COMPOUNDCURVE EMPTY"; + + testWkt(geoWKT); + } } @Test @@ -363,28 +372,30 @@ public void testCurvePolygonWkt() throws SQLException { @Test public void testFullGlobeWkt() throws SQLException { - beforeEachSetup(); - - String geoWKT = "FULLGLOBE"; + if (isDenaliOrLater) { + beforeEachSetup(); + + String geoWKT = "FULLGLOBE"; - Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); + Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); - try { - Geometry.STGeomFromText(geoWKT, 0); - } - catch (IllegalArgumentException e) { - assertEquals(e.getMessage(), "Fullglobe is not supported for Geometry."); + try { + Geometry.STGeomFromText(geoWKT, 0); + } + catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "Fullglobe is not supported for Geometry."); + } + + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); + + pstmt.setGeography(1, geogWKT); + + pstmt.execute(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); + rs.next(); + assertEquals(rs.getGeography(1).asTextZM(), geoWKT); } - - pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); - - pstmt.setGeography(1, geogWKT); - - pstmt.execute(); - - rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); - rs.next(); - assertEquals(rs.getGeography(1).asTextZM(), geoWKT); } @Test @@ -536,20 +547,22 @@ public void testAllTypes() throws SQLException { pstmt.executeUpdate(); - geomWKT = Geometry.STGeomFromText(geoWKTCircularString, 0); - pstmt.setGeometry(1, geomWKT); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTCompoundCurve, 0); - pstmt.setGeometry(1, geomWKT); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTCurvePolygon, 0); - pstmt.setGeometry(1, geomWKT); - - pstmt.executeUpdate(); + if (isDenaliOrLater) { + geomWKT = Geometry.STGeomFromText(geoWKTCircularString, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTCompoundCurve, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTCurvePolygon, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + } geomWKT = Geometry.STGeomFromText(geoWKTPolygon, 0); pstmt.setGeometry(1, geomWKT); @@ -699,36 +712,38 @@ public void testMixedAllTypes() throws SQLException { pstmt.executeUpdate(); - geomWKT = Geometry.STGeomFromText(geoWKTCircularString, 0); - geogWKT = Geography.STGeomFromText(geoWKTCircularString, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTCompoundCurve, 0); - geogWKT = Geography.STGeomFromText(geoWKTCompoundCurve, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTCurvePolygon, 0); - geogWKT = Geography.STGeomFromText(geoWKTCurvePolygon, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - + if (isDenaliOrLater) { + geomWKT = Geometry.STGeomFromText(geoWKTCircularString, 0); + geogWKT = Geography.STGeomFromText(geoWKTCircularString, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTCompoundCurve, 0); + geogWKT = Geography.STGeomFromText(geoWKTCompoundCurve, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTCurvePolygon, 0); + geogWKT = Geography.STGeomFromText(geoWKTCurvePolygon, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + } + geomWKT = Geometry.STGeomFromText(geoWKTPolygon, 0); geogWKT = Geography.STGeomFromText(geoWKTPolygon, 4326); pstmt.setGeometry(1, geomWKT); @@ -977,6 +992,21 @@ private void testWkt(String geoWKT, String geoWKTSS) throws SQLException { public static void setupHere() throws SQLException, SecurityException, IOException { con = (SQLServerConnection) DriverManager.getConnection(connectionString); stmt = con.createStatement(); + + rs = (SQLServerResultSet) stmt.executeQuery("select SERVERPROPERTY ( 'ProductVersion' )"); + + rs.next(); + + try { + int version = Integer.parseInt(rs.getString(1).substring(0, 2)); + + // if major version is greater than or equal to 11, it's SQL Server 2012 or above. + if (version >= 11) { + isDenaliOrLater = true; + } + } catch (Exception e) { + // Do nothing. + } } /** From ec553a005ef36c0d49b9d0ceb8ebab8ac4edf81f Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Wed, 28 Feb 2018 14:20:59 -0800 Subject: [PATCH 12/15] more test changes --- .../SQLServerSpatialDatatypeTest.java | 518 +++++++++--------- 1 file changed, 259 insertions(+), 259 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java index 5b76d0961..81a7dcd1d 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java @@ -176,7 +176,6 @@ public void testCompoundCurveWkb() throws DecoderException { @Test public void testCurvePolygonWkb() throws DecoderException { - if (isDenaliOrLater) { String geoWKT = "CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778)))"; byte[] geomWKB = Hex.decodeHex("0A00000002001700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000008400000000000000840000000000000144000000000000010400000000000001C400000000000001C400000000000000840000000000000F03F00000000000008400000000000000000C7D79E59127037C00000000000000000C7D79E591270374000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C00000000000000000C7D79E59127037C00000000000001C400000000000001C400000000000000000C7D79E5912703740000000000000204000000000000020400000000000002040000000000000204000000000008046C0C7D79E591270374000000000008056C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); @@ -187,7 +186,6 @@ public void testCurvePolygonWkb() throws DecoderException { assertEquals(geomWKT.asTextZM(), geoWKT); assertEquals(geogWKT.asTextZM(), geoWKT); - } } @Test @@ -357,17 +355,19 @@ public void testCompoundCurveWkt() throws SQLException { @Test public void testCurvePolygonWkt() throws SQLException { - beforeEachSetup(); - - String geoWKT = "CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778)))"; - - testWkt(geoWKT); - - beforeEachSetup(); - - geoWKT = "CURVEPOLYGON EMPTY"; - - testWkt(geoWKT); + if (isDenaliOrLater) { + beforeEachSetup(); + + String geoWKT = "CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778)))"; + + testWkt(geoWKT); + + beforeEachSetup(); + + geoWKT = "CURVEPOLYGON EMPTY"; + + testWkt(geoWKT); + } } @Test @@ -505,49 +505,49 @@ public void testIllegalCases() throws SQLException { @Test public void testAllTypes() throws SQLException { - beforeEachSetup(); - - String geoWKTPoint = "POINT(30 12.12312312 5 6)"; - String geoWKTLineString = "LINESTRING(1 1, 2 4 3, 3 9 123 332)"; - String geoWKTCircularString = "CIRCULARSTRING(1 1, 2 4, 3 9)"; - String geoWKTCompoundCurve = "COMPOUNDCURVE((1 1, 1 3), (1 3, 3 3), (3 3, 3 1), (3 1, 1 1))"; - String geoWKTCurvePolygon = "CURVEPOLYGON(CIRCULARSTRING(2 4, 4 2, 6 4, 4 6, 2 4))"; - String geoWKTPolygon = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1))"; - String geoWKTMultiPoint = "MULTIPOINT((2 3), (7 8 9.5))"; - String geoWKTMultiLineString = "MULTILINESTRING((0 2, 1 1), (1 0, 1 1))"; - String geoWKTMultiPolygon = "MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), ((9 9, 9 10, 10 9, 9 9)))"; - String geoWKTGeometryCollection = "GEOMETRYCOLLECTION(POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)), POINT(3 3 1 2.5), LINESTRING(1 0, 0 1, -1 0), GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(1 2 3 4))), GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778))))"; - - List geoWKTList = new ArrayList(); - - geoWKTList.add(geoWKTPoint); - geoWKTList.add(geoWKTLineString); - geoWKTList.add(geoWKTCircularString); - geoWKTList.add(geoWKTCompoundCurve); - geoWKTList.add(geoWKTCurvePolygon); - geoWKTList.add(geoWKTPolygon); - geoWKTList.add(geoWKTMultiPoint); - geoWKTList.add(geoWKTMultiLineString); - geoWKTList.add(geoWKTMultiPolygon); - geoWKTList.add(geoWKTGeometryCollection); - - Geometry geomWKT; - Geography geogWKT; - - //Geometry - pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geomTableName + " values (?)"); - - geomWKT = Geometry.STGeomFromText(geoWKTPoint, 0); - pstmt.setGeometry(1, geomWKT); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTLineString, 0); - pstmt.setGeometry(1, geomWKT); - - pstmt.executeUpdate(); - if (isDenaliOrLater) { + beforeEachSetup(); + + String geoWKTPoint = "POINT(30 12.12312312 5 6)"; + String geoWKTLineString = "LINESTRING(1 1, 2 4 3, 3 9 123 332)"; + String geoWKTCircularString = "CIRCULARSTRING(1 1, 2 4, 3 9)"; + String geoWKTCompoundCurve = "COMPOUNDCURVE((1 1, 1 3), (1 3, 3 3), (3 3, 3 1), (3 1, 1 1))"; + String geoWKTCurvePolygon = "CURVEPOLYGON(CIRCULARSTRING(2 4, 4 2, 6 4, 4 6, 2 4))"; + String geoWKTPolygon = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1))"; + String geoWKTMultiPoint = "MULTIPOINT((2 3), (7 8 9.5))"; + String geoWKTMultiLineString = "MULTILINESTRING((0 2, 1 1), (1 0, 1 1))"; + String geoWKTMultiPolygon = "MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), ((9 9, 9 10, 10 9, 9 9)))"; + String geoWKTGeometryCollection = "GEOMETRYCOLLECTION(POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)), POINT(3 3 1 2.5), LINESTRING(1 0, 0 1, -1 0), GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(1 2 3 4))), GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778))))"; + + List geoWKTList = new ArrayList(); + + geoWKTList.add(geoWKTPoint); + geoWKTList.add(geoWKTLineString); + geoWKTList.add(geoWKTCircularString); + geoWKTList.add(geoWKTCompoundCurve); + geoWKTList.add(geoWKTCurvePolygon); + geoWKTList.add(geoWKTPolygon); + geoWKTList.add(geoWKTMultiPoint); + geoWKTList.add(geoWKTMultiLineString); + geoWKTList.add(geoWKTMultiPolygon); + geoWKTList.add(geoWKTGeometryCollection); + + Geometry geomWKT; + Geography geogWKT; + + //Geometry + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geomTableName + " values (?)"); + + geomWKT = Geometry.STGeomFromText(geoWKTPoint, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTLineString, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + geomWKT = Geometry.STGeomFromText(geoWKTCircularString, 0); pstmt.setGeometry(1, geomWKT); @@ -562,157 +562,157 @@ public void testAllTypes() throws SQLException { pstmt.setGeometry(1, geomWKT); pstmt.executeUpdate(); - } - - geomWKT = Geometry.STGeomFromText(geoWKTPolygon, 0); - pstmt.setGeometry(1, geomWKT); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTMultiPoint, 0); - pstmt.setGeometry(1, geomWKT); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTMultiLineString, 0); - pstmt.setGeometry(1, geomWKT); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTMultiPolygon, 0); - pstmt.setGeometry(1, geomWKT); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTGeometryCollection, 0); - pstmt.setGeometry(1, geomWKT); - - pstmt.executeUpdate(); - - rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); - for (int i = 0; i < geoWKTList.size(); i++) { - rs.next(); - assertEquals(rs.getGeometry(1).asTextZM(), geoWKTList.get(i)); - } - - //Geography - pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); - - geogWKT = Geography.STGeomFromText(geoWKTPoint, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTLineString, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTCircularString, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTCompoundCurve, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTCurvePolygon, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTPolygon, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTMultiPoint, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTMultiLineString, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTMultiPolygon, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - geogWKT = Geography.STGeomFromText(geoWKTGeometryCollection, 4326); - pstmt.setGeography(1, geogWKT); - - pstmt.executeUpdate(); - - rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); - for (int i = 0; i < geoWKTList.size(); i++) { - rs.next(); - assertEquals(rs.getGeography(1).asTextZM(), geoWKTList.get(i)); + + geomWKT = Geometry.STGeomFromText(geoWKTPolygon, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiPoint, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiLineString, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiPolygon, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTGeometryCollection, 0); + pstmt.setGeometry(1, geomWKT); + + pstmt.executeUpdate(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); + for (int i = 0; i < geoWKTList.size(); i++) { + rs.next(); + assertEquals(rs.getGeometry(1).asTextZM(), geoWKTList.get(i)); + } + + //Geography + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); + + geogWKT = Geography.STGeomFromText(geoWKTPoint, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTLineString, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTCircularString, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTCompoundCurve, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTCurvePolygon, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTPolygon, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTMultiPoint, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTMultiLineString, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTMultiPolygon, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + geogWKT = Geography.STGeomFromText(geoWKTGeometryCollection, 4326); + pstmt.setGeography(1, geogWKT); + + pstmt.executeUpdate(); + + rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); + for (int i = 0; i < geoWKTList.size(); i++) { + rs.next(); + assertEquals(rs.getGeography(1).asTextZM(), geoWKTList.get(i)); + } } } @Test public void testMixedAllTypes() throws SQLException { - beforeEachSetupSpatialDatatype(); - - String geoWKTPoint = "POINT(30 12.12312312 5 6)"; - String geoWKTLineString = "LINESTRING(1 1, 2 4 3, 3 9 123 332)"; - String geoWKTCircularString = "CIRCULARSTRING(1 1, 2 4, 3 9)"; - String geoWKTCompoundCurve = "COMPOUNDCURVE((1 1, 1 3), (1 3, 3 3), (3 3, 3 1), (3 1, 1 1))"; - String geoWKTCurvePolygon = "CURVEPOLYGON(CIRCULARSTRING(2 4, 4 2, 6 4, 4 6, 2 4))"; - String geoWKTPolygon = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1))"; - String geoWKTMultiPoint = "MULTIPOINT((2 3), (7 8 9.5))"; - String geoWKTMultiLineString = "MULTILINESTRING((0 2, 1 1), (1 0, 1 1))"; - String geoWKTMultiPolygon = "MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), ((9 9, 9 10, 10 9, 9 9)))"; - String geoWKTGeometryCollection = "GEOMETRYCOLLECTION(POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)), POINT(3 3 1 2.5), LINESTRING(1 0, 0 1, -1 0), GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(1 2 3 4))), GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778))))"; - - String s = "some string"; - Double d = 31.34; - int i2 = 5; - - List geoWKTList = new ArrayList(); - - geoWKTList.add(geoWKTPoint); - geoWKTList.add(geoWKTLineString); - geoWKTList.add(geoWKTCircularString); - geoWKTList.add(geoWKTCompoundCurve); - geoWKTList.add(geoWKTCurvePolygon); - geoWKTList.add(geoWKTPolygon); - geoWKTList.add(geoWKTMultiPoint); - geoWKTList.add(geoWKTMultiLineString); - geoWKTList.add(geoWKTMultiPolygon); - geoWKTList.add(geoWKTGeometryCollection); - - Geometry geomWKT; - Geography geogWKT; - - pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ spatialDatatypeTableName + " values (?, ?, ?, ?, ?)"); - - geomWKT = Geometry.STGeomFromText(geoWKTPoint, 0); - geogWKT = Geography.STGeomFromText(geoWKTPoint, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTLineString, 0); - geogWKT = Geography.STGeomFromText(geoWKTLineString, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - if (isDenaliOrLater) { + beforeEachSetupSpatialDatatype(); + + String geoWKTPoint = "POINT(30 12.12312312 5 6)"; + String geoWKTLineString = "LINESTRING(1 1, 2 4 3, 3 9 123 332)"; + String geoWKTCircularString = "CIRCULARSTRING(1 1, 2 4, 3 9)"; + String geoWKTCompoundCurve = "COMPOUNDCURVE((1 1, 1 3), (1 3, 3 3), (3 3, 3 1), (3 1, 1 1))"; + String geoWKTCurvePolygon = "CURVEPOLYGON(CIRCULARSTRING(2 4, 4 2, 6 4, 4 6, 2 4))"; + String geoWKTPolygon = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1))"; + String geoWKTMultiPoint = "MULTIPOINT((2 3), (7 8 9.5))"; + String geoWKTMultiLineString = "MULTILINESTRING((0 2, 1 1), (1 0, 1 1))"; + String geoWKTMultiPolygon = "MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), ((9 9, 9 10, 10 9, 9 9)))"; + String geoWKTGeometryCollection = "GEOMETRYCOLLECTION(POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)), POINT(3 3 1 2.5), LINESTRING(1 0, 0 1, -1 0), GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(1 2 3 4))), GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778))))"; + + String s = "some string"; + Double d = 31.34; + int i2 = 5; + + List geoWKTList = new ArrayList(); + + geoWKTList.add(geoWKTPoint); + geoWKTList.add(geoWKTLineString); + geoWKTList.add(geoWKTCircularString); + geoWKTList.add(geoWKTCompoundCurve); + geoWKTList.add(geoWKTCurvePolygon); + geoWKTList.add(geoWKTPolygon); + geoWKTList.add(geoWKTMultiPoint); + geoWKTList.add(geoWKTMultiLineString); + geoWKTList.add(geoWKTMultiPolygon); + geoWKTList.add(geoWKTGeometryCollection); + + Geometry geomWKT; + Geography geogWKT; + + pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ spatialDatatypeTableName + " values (?, ?, ?, ?, ?)"); + + geomWKT = Geometry.STGeomFromText(geoWKTPoint, 0); + geogWKT = Geography.STGeomFromText(geoWKTPoint, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTLineString, 0); + geogWKT = Geography.STGeomFromText(geoWKTLineString, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + geomWKT = Geometry.STGeomFromText(geoWKTCircularString, 0); geogWKT = Geography.STGeomFromText(geoWKTCircularString, 4326); pstmt.setGeometry(1, geomWKT); @@ -742,66 +742,66 @@ public void testMixedAllTypes() throws SQLException { pstmt.setInt(5, i2); pstmt.executeUpdate(); - } - geomWKT = Geometry.STGeomFromText(geoWKTPolygon, 0); - geogWKT = Geography.STGeomFromText(geoWKTPolygon, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTMultiPoint, 0); - geogWKT = Geography.STGeomFromText(geoWKTMultiPoint, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTMultiLineString, 0); - geogWKT = Geography.STGeomFromText(geoWKTMultiLineString, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTMultiPolygon, 0); - geogWKT = Geography.STGeomFromText(geoWKTMultiPolygon, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - geomWKT = Geometry.STGeomFromText(geoWKTGeometryCollection, 0); - geogWKT = Geography.STGeomFromText(geoWKTGeometryCollection, 4326); - pstmt.setGeometry(1, geomWKT); - pstmt.setGeography(2, geogWKT); - pstmt.setString(3, s); - pstmt.setDouble(4, d); - pstmt.setInt(5, i2); - - pstmt.executeUpdate(); - - rs = (SQLServerResultSet) stmt.executeQuery("select * from " + spatialDatatypeTableName); - for (int i = 0; i < geoWKTList.size(); i++) { - rs.next(); - assertEquals(rs.getGeometry(1).asTextZM(), geoWKTList.get(i)); - assertEquals(rs.getGeography(2).asTextZM(), geoWKTList.get(i)); - assertEquals(rs.getString(3), s); - assertEquals((Double) rs.getDouble(4), d); - assertEquals(rs.getInt(5), i2); + geomWKT = Geometry.STGeomFromText(geoWKTPolygon, 0); + geogWKT = Geography.STGeomFromText(geoWKTPolygon, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiPoint, 0); + geogWKT = Geography.STGeomFromText(geoWKTMultiPoint, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiLineString, 0); + geogWKT = Geography.STGeomFromText(geoWKTMultiLineString, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTMultiPolygon, 0); + geogWKT = Geography.STGeomFromText(geoWKTMultiPolygon, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + geomWKT = Geometry.STGeomFromText(geoWKTGeometryCollection, 0); + geogWKT = Geography.STGeomFromText(geoWKTGeometryCollection, 4326); + pstmt.setGeometry(1, geomWKT); + pstmt.setGeography(2, geogWKT); + pstmt.setString(3, s); + pstmt.setDouble(4, d); + pstmt.setInt(5, i2); + + pstmt.executeUpdate(); + + rs = (SQLServerResultSet) stmt.executeQuery("select * from " + spatialDatatypeTableName); + for (int i = 0; i < geoWKTList.size(); i++) { + rs.next(); + assertEquals(rs.getGeometry(1).asTextZM(), geoWKTList.get(i)); + assertEquals(rs.getGeography(2).asTextZM(), geoWKTList.get(i)); + assertEquals(rs.getString(3), s); + assertEquals((Double) rs.getDouble(4), d); + assertEquals(rs.getInt(5), i2); + } } } From b32369f668242fde5bddd879b9b8b288261f7429 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Tue, 6 Mar 2018 14:39:32 -0800 Subject: [PATCH 13/15] style changes --- src/main/java/com/microsoft/sqlserver/jdbc/Geography.java | 6 ------ src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java | 6 ------ .../jdbc/datatypes/SQLServerSpatialDatatypeTest.java | 2 -- 3 files changed, 14 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java index 0c0f94c2a..7c9fcaf8a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geography.java @@ -294,9 +294,7 @@ protected void parseWkb() { serializationProperties = buffer.get(); interpretSerializationPropBytes(); - readNumberOfPoints(); - readPoints(); if (hasZvalues) { @@ -309,11 +307,8 @@ protected void parseWkb() { if (!(isSinglePoint || isSingleLineSegment)) { readNumberOfFigures(); - readFigures(); - readNumberOfShapes(); - readShapes(); } @@ -322,7 +317,6 @@ protected void parseWkb() { if (buffer.hasRemaining()) { if (version == 2 && internalType.getTypeCode() != 8 && internalType.getTypeCode() != 11) { readNumberOfSegments(); - readSegments(); } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java index aff52f1a0..5966d5fe9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Geometry.java @@ -294,9 +294,7 @@ protected void parseWkb() { serializationProperties = buffer.get(); interpretSerializationPropBytes(); - readNumberOfPoints(); - readPoints(); if (hasZvalues) { @@ -309,11 +307,8 @@ protected void parseWkb() { if (!(isSinglePoint || isSingleLineSegment)) { readNumberOfFigures(); - readFigures(); - readNumberOfShapes(); - readShapes(); } @@ -322,7 +317,6 @@ protected void parseWkb() { if (buffer.hasRemaining()) { if (version == 2 && internalType.getTypeCode() != 8 && internalType.getTypeCode() != 11) { readNumberOfSegments(); - readSegments(); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java index 81a7dcd1d..689b86984 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java @@ -810,7 +810,6 @@ public void testDecimalRounding() throws SQLException { beforeEachSetup(); String geoWKT = "POINT(3 40.7777777777777777777 5 6)"; - String geoWKTSS = "POINT(3 40.77777777777778 5 6)"; testWkt(geoWKT, geoWKTSS); @@ -885,7 +884,6 @@ public void testSTAsText() throws SQLException { beforeEachSetup(); String geoWKT = "GEOMETRYCOLLECTION(POINT(300 20 1), GEOMETRYCOLLECTION EMPTY, GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY)), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; - String geoWKTSS = "GEOMETRYCOLLECTION(POINT(300 20), GEOMETRYCOLLECTION EMPTY, GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY)), POLYGON((0 0, 1 10, 1 0, 0 0)))"; Geometry geomWKT = Geometry.STGeomFromText(geoWKT, 0); From 85e3d864b57797b20ba1d99d9d22809e6f1f087c Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Wed, 7 Mar 2018 13:45:33 -0800 Subject: [PATCH 14/15] spacing change --- .../SQLServerSpatialDatatypeTest.java | 50 ++++--------------- 1 file changed, 10 insertions(+), 40 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java index 689b86984..1aef8a095 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java @@ -176,16 +176,16 @@ public void testCompoundCurveWkb() throws DecoderException { @Test public void testCurvePolygonWkb() throws DecoderException { - String geoWKT = "CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778)))"; - - byte[] geomWKB = Hex.decodeHex("0A00000002001700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000008400000000000000840000000000000144000000000000010400000000000001C400000000000001C400000000000000840000000000000F03F00000000000008400000000000000000C7D79E59127037C00000000000000000C7D79E591270374000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C00000000000000000C7D79E59127037C00000000000001C400000000000001C400000000000000000C7D79E5912703740000000000000204000000000000020400000000000002040000000000000204000000000008046C0C7D79E591270374000000000008056C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); - byte[] geogWKB = Hex.decodeHex("E6100000020017000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000840000000000000F03F000000000000144000000000000008400000000000001C40000000000000104000000000000008400000000000001C400000000000000840000000000000F03FC7D79E59127037C00000000000000000C7D79E59127037400000000000000000C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C000000000000000000000000000001C400000000000001C40C7D79E591270374000000000000000000000000000002040000000000000204000000000000020400000000000002040C7D79E591270374000000000008046C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C0000000000000000004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); + String geoWKT = "CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778)))"; + + byte[] geomWKB = Hex.decodeHex("0A00000002001700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000008400000000000000840000000000000144000000000000010400000000000001C400000000000001C400000000000000840000000000000F03F00000000000008400000000000000000C7D79E59127037C00000000000000000C7D79E591270374000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C00000000000000000C7D79E59127037C00000000000001C400000000000001C400000000000000000C7D79E5912703740000000000000204000000000000020400000000000002040000000000000204000000000008046C0C7D79E591270374000000000008056C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); + byte[] geogWKB = Hex.decodeHex("E6100000020017000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000840000000000000F03F000000000000144000000000000008400000000000001C40000000000000104000000000000008400000000000001C400000000000000840000000000000F03FC7D79E59127037C00000000000000000C7D79E59127037400000000000000000C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C000000000000000000000000000001C400000000000001C40C7D79E591270374000000000000000000000000000002040000000000000204000000000000020400000000000002040C7D79E591270374000000000008046C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C0000000000000000004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); - Geometry geomWKT = Geometry.deserialize(geomWKB); - Geography geogWKT = Geography.deserialize(geogWKB); - - assertEquals(geomWKT.asTextZM(), geoWKT); - assertEquals(geogWKT.asTextZM(), geoWKT); + Geometry geomWKT = Geometry.deserialize(geomWKB); + Geography geogWKT = Geography.deserialize(geogWKB); + + assertEquals(geomWKT.asTextZM(), geoWKT); + assertEquals(geogWKT.asTextZM(), geoWKT); } @Test @@ -365,7 +365,7 @@ public void testCurvePolygonWkt() throws SQLException { beforeEachSetup(); geoWKT = "CURVEPOLYGON EMPTY"; - + testWkt(geoWKT); } } @@ -376,7 +376,6 @@ public void testFullGlobeWkt() throws SQLException { beforeEachSetup(); String geoWKT = "FULLGLOBE"; - Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); try { @@ -387,9 +386,7 @@ public void testFullGlobeWkt() throws SQLException { } pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); - pstmt.setGeography(1, geogWKT); - pstmt.execute(); rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); @@ -404,7 +401,6 @@ public void testIrregularCases() throws SQLException { String geoWKT = " GeOMETRyCOLlECTION(POINT( 3e2 2E1 1 ), GEOMETRYCOLLECTION EmPTy , GeometryCollection(GEometryCOLLEction(GEometryCOLLEction Empty)), " + "POLYGON( (0 0 2, 1 10 3, 1 0 4, 0 0 2)) )"; - String geoWKTSS = "GEOMETRYCOLLECTION(POINT(300 20 1), GEOMETRYCOLLECTION EMPTY, GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY)), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; testWkt(geoWKT, geoWKTSS); @@ -540,52 +536,42 @@ public void testAllTypes() throws SQLException { geomWKT = Geometry.STGeomFromText(geoWKTPoint, 0); pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); geomWKT = Geometry.STGeomFromText(geoWKTLineString, 0); pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); geomWKT = Geometry.STGeomFromText(geoWKTCircularString, 0); pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); geomWKT = Geometry.STGeomFromText(geoWKTCompoundCurve, 0); pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); geomWKT = Geometry.STGeomFromText(geoWKTCurvePolygon, 0); pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); geomWKT = Geometry.STGeomFromText(geoWKTPolygon, 0); pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); geomWKT = Geometry.STGeomFromText(geoWKTMultiPoint, 0); pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); geomWKT = Geometry.STGeomFromText(geoWKTMultiLineString, 0); pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); geomWKT = Geometry.STGeomFromText(geoWKTMultiPolygon, 0); pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); geomWKT = Geometry.STGeomFromText(geoWKTGeometryCollection, 0); pstmt.setGeometry(1, geomWKT); - pstmt.executeUpdate(); rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); @@ -825,9 +811,7 @@ public void testParse() throws SQLException { Geography geogWKT = Geography.parse(geoWKT); pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geomTableName + " values (?)"); - pstmt.setGeometry(1, geomWKT); - pstmt.execute(); rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); @@ -836,9 +820,7 @@ public void testParse() throws SQLException { assertEquals(rs.getGeometry(1).getSrid(), 0); pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); - pstmt.setGeography(1, geogWKT); - pstmt.execute(); rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); @@ -857,9 +839,7 @@ public void testPoint() throws SQLException { Geography geogWKT = Geography.point(1, 2, 4326); pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geomTableName + " values (?)"); - pstmt.setGeometry(1, geomWKT); - pstmt.execute(); rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); @@ -868,9 +848,7 @@ public void testPoint() throws SQLException { assertEquals(rs.getGeometry(1).getSrid(), 0); pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); - pstmt.setGeography(1, geogWKT); - pstmt.execute(); rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); @@ -890,9 +868,7 @@ public void testSTAsText() throws SQLException { Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geomTableName + " values (?)"); - pstmt.setGeometry(1, geomWKT); - pstmt.execute(); rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); @@ -900,9 +876,7 @@ public void testSTAsText() throws SQLException { assertEquals(rs.getGeometry(1).STAsText(), geoWKTSS); pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); - pstmt.setGeography(1, geogWKT); - pstmt.execute(); rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); @@ -959,9 +933,7 @@ private void testWkt(String geoWKT, String geoWKTSS) throws SQLException { Geography geogWKT = Geography.STGeomFromText(geoWKT, 4326); pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geomTableName + " values (?)"); - pstmt.setGeometry(1, geomWKT); - pstmt.execute(); rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geomTableName); @@ -969,9 +941,7 @@ private void testWkt(String geoWKT, String geoWKTSS) throws SQLException { assertEquals(rs.getGeometry(1).asTextZM(), geoWKTSS); pstmt = (SQLServerPreparedStatement) con.prepareStatement("insert into "+ geogTableName + " values (?)"); - pstmt.setGeography(1, geogWKT); - pstmt.execute(); rs = (SQLServerResultSet) stmt.executeQuery("select c1 from " + geogTableName); From e7bfc3b459d2c38868c002978d190ce92bd7a267 Mon Sep 17 00:00:00 2001 From: Peter Bae Date: Wed, 7 Mar 2018 13:57:38 -0800 Subject: [PATCH 15/15] spacing changes --- .../SQLServerSpatialDatatypeTest.java | 89 ------------------- 1 file changed, 89 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java index 1aef8a095..28bd072db 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/SQLServerSpatialDatatypeTest.java @@ -51,13 +51,10 @@ public class SQLServerSpatialDatatypeTest extends AbstractTest { @Test public void testPointWkb() throws DecoderException { String geoWKT = "POINT(3 40 5 6)"; - byte[] geomWKB = Hex.decodeHex("00000000010F0000000000000840000000000000444000000000000014400000000000001840".toCharArray()); byte[] geogWKB = Hex.decodeHex("E6100000010F0000000000004440000000000000084000000000000014400000000000001840".toCharArray()); - Geometry geomWKT = Geometry.deserialize(geomWKB); Geography geogWKT = Geography.deserialize(geogWKB); - assertEquals(geomWKT.asTextZM(), geoWKT); assertEquals(geogWKT.asTextZM(), geoWKT); } @@ -65,13 +62,10 @@ public void testPointWkb() throws DecoderException { @Test public void testLineStringWkb() throws DecoderException { String geoWKT = "LINESTRING(1 0, 0 1, -1 0)"; - byte[] geomWKB = Hex.decodeHex("00000000010403000000000000000000F03F00000000000000000000000000000000000000000000F03F000000000000F0BF000000000000000001000000010000000001000000FFFFFFFF0000000002".toCharArray()); byte[] geogWKB = Hex.decodeHex("E61000000104030000000000000000000000000000000000F03F000000000000F03F00000000000000000000000000000000000000000000F0BF01000000010000000001000000FFFFFFFF0000000002".toCharArray()); - Geometry geomWKT = Geometry.deserialize(geomWKB); Geography geogWKT = Geography.deserialize(geogWKB); - assertEquals(geomWKT.asTextZM(), geoWKT); assertEquals(geogWKT.asTextZM(), geoWKT); } @@ -79,13 +73,10 @@ public void testLineStringWkb() throws DecoderException { @Test public void testPolygonWkb() throws DecoderException { String geoWKT = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1))"; - byte[] geomWKB = Hex.decodeHex("000000000104090000000000000000000000000000000000000000000000000000000000000000000840000000000000084000000000000008400000000000000840000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F00000000000000400000000000000040000000000000F03F000000000000F03F000000000000F03F020000000200000000000500000001000000FFFFFFFF0000000003".toCharArray()); byte[] geogWKB = Hex.decodeHex("E61000000200090000000000000000000000000000000000000000000000000008400000000000000000000000000000084000000000000008400000000000000000000000000000084000000000000000000000000000000000000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F020000000100000000010500000001000000FFFFFFFF0000000003".toCharArray()); - Geometry geomWKT = Geometry.deserialize(geomWKB); Geography geogWKT = Geography.deserialize(geogWKB); - assertEquals(geomWKT.asTextZM(), geoWKT); assertEquals(geogWKT.asTextZM(), geoWKT); } @@ -93,13 +84,10 @@ public void testPolygonWkb() throws DecoderException { @Test public void testMultiPointWkb() throws DecoderException { String geoWKT = "MULTIPOINT((2 3), (7 8 9.5))"; - byte[] geomWKB = Hex.decodeHex("00000000010502000000000000000000004000000000000008400000000000001C400000000000002040000000000000F8FF0000000000002340020000000100000000010100000003000000FFFFFFFF0000000004000000000000000001000000000100000001".toCharArray()); byte[] geogWKB = Hex.decodeHex("E61000000105020000000000000000000840000000000000004000000000000020400000000000001C40000000000000F8FF0000000000002340020000000100000000010100000003000000FFFFFFFF0000000004000000000000000001000000000100000001".toCharArray()); - Geometry geomWKT = Geometry.deserialize(geomWKB); Geography geogWKT = Geography.deserialize(geogWKB); - assertEquals(geomWKT.asTextZM(), geoWKT); assertEquals(geogWKT.asTextZM(), geoWKT); } @@ -107,13 +95,10 @@ public void testMultiPointWkb() throws DecoderException { @Test public void testMultiLineStringWkb() throws DecoderException { String geoWKT = "MULTILINESTRING((0 2, 1 1), (1 0, 1 1))"; - byte[] geomWKB = Hex.decodeHex("0000000001040400000000000000000000000000000000000040000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F020000000100000000010200000003000000FFFFFFFF0000000005000000000000000002000000000100000002".toCharArray()); byte[] geogWKB = Hex.decodeHex("E610000001040400000000000000000000400000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F000000000000F03F020000000100000000010200000003000000FFFFFFFF0000000005000000000000000002000000000100000002".toCharArray()); - Geometry geomWKT = Geometry.deserialize(geomWKB); Geography geogWKT = Geography.deserialize(geogWKB); - assertEquals(geomWKT.asTextZM(), geoWKT); assertEquals(geogWKT.asTextZM(), geoWKT); } @@ -121,13 +106,10 @@ public void testMultiLineStringWkb() throws DecoderException { @Test public void testMultiPolygonWkb() throws DecoderException { String geoWKT = "MULTIPOLYGON(((1 1, 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; - byte[] geomWKB = Hex.decodeHex("0000000001010D000000000000000000F03F000000000000F03F000000000000F03F00000000000000400000000000000040000000000000F03F000000000000F03F000000000000F03F000000000000000000000000000000000000000000000000000000000000084000000000000008400000000000000840000000000000084000000000000000000000000000000000000000000000000000000000000022400000000000002240000000000000224000000000000024400000000000002440000000000000224000000000000022400000000000002240000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0000000000001C40000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0300000002000000000004000000020900000003000000FFFFFFFF0000000006000000000000000003000000000200000003".toCharArray()); byte[] geogWKB = Hex.decodeHex("E610000002010D000000000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F000000000000000000000000000000000000000000000840000000000000000000000000000008400000000000000840000000000000000000000000000008400000000000000000000000000000000000000000000022400000000000002240000000000000244000000000000022400000000000002240000000000000244000000000000022400000000000002240000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0000000000001C40000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0300000001000000000104000000010900000003000000FFFFFFFF0000000006000000000000000003000000000200000003".toCharArray()); - Geometry geomWKT = Geometry.deserialize(geomWKB); Geography geogWKT = Geography.deserialize(geogWKB); - assertEquals(geomWKT.asTextZM(), geoWKT); assertEquals(geogWKT.asTextZM(), geoWKT); } @@ -135,13 +117,10 @@ public void testMultiPolygonWkb() throws DecoderException { @Test public void testGeometryCollectionWkb() throws DecoderException { String geoWKT = "GEOMETRYCOLLECTION(POINT(3 3 1), LINESTRING(1 0, 0 1, -1 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2)), MULTIPOINT((2 3), (7 8 9.5)), MULTILINESTRING((0 2, 1 1), (1 0, 1 1)), MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), ((9 9, 9 10, 10 9, 9 9))), COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1)), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778))), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; - byte[] geomWKB = Hex.decodeHex("0100000002014A00000000000000000008400000000000000840000000000000F03F00000000000000000000000000000000000000000000F03F000000000000F0BF0000000000000000000000000000F03F00000000000008400000000000000840000000000000144000000000000010400000000000001C400000000000001C400000000000000840000000000000F03F000000000000084000000000000000000000000000000000000000000000F03F0000000000002440000000000000F03F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000002440000000000000F03F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000002440000000000000F03F000000000000000000000000000000000000000000000000000000000000004000000000000008400000000000001C40000000000000204000000000000000000000000000000040000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000000000000000000000000000000000000000000000000840000000000000084000000000000008400000000000000840000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F000000000000F03F00000000000000400000000000000040000000000000F03F000000000000F03F000000000000F03F00000000000022400000000000002240000000000000224000000000000024400000000000002440000000000000224000000000000022400000000000002240000000000000F03F00000000000000000000000000000000000000000000F03F0000000000002240000000000000184000000000000020400000000000001C40000000000000F0BF00000000000000000000000000001C40000000000000224000000000000024C00000000000000040000000000040534000000000004053400000000000005640000000000000564000000000000000400000000000001840000000000000084000000000000008400000000000001C400000000000001C40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C7D79E59127037C00000000000000000C7D79E591270374000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C00000000000000000C7D79E59127037C00000000000001C400000000000001C400000000000000000C7D79E5912703740000000000000204000000000000020400000000000002040000000000000204000000000008046C0C7D79E591270374000000000008056C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C000000000000000000000000000000000000000000000F03F0000000000002440000000000000F03F000000000000000000000000000000000000000000000000000000000000F03F000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000004000000000000008400000000000001040000000000000004000000000000000400000000000000840000000000000104000000000000000400000000000000040000000000000084000000000000010400000000000000040000000000000F8FF0000000000002340000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF00000000000008400000000000000840000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000405340000000000000564000000000000010400000000000001840000000000000F03F000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0000000000000040000000000000084000000000000010400000000000000040120000000100000000010100000002040000000109000000010D00000001110000000115000000011600000001170000000119000000011B00000001200000000124000000032800000001340000000338000000033C000000014600000011000000FFFFFFFF0000000007000000000000000001000000000100000002000000000200000008000000000300000003000000000600000004050000000600000001050000000700000001000000000800000005080000000800000002080000000900000002000000000A000000060B0000000A000000030B0000000C00000003000000000D00000009000000000E0000000A0000000011000000031000000003010302000002000203020003010203".toCharArray()); byte[] geogWKB = Hex.decodeHex("E610000002214A000000000000000000084000000000000008400000000000000000000000000000F03F000000000000F03F00000000000000000000000000000000000000000000F0BF0000000000000840000000000000F03F000000000000144000000000000008400000000000001C40000000000000104000000000000008400000000000001C400000000000000840000000000000F03F000000000000000000000000000000000000000000002440000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000000000000000000000000000000000000000000000002440000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000000000000000000000000000000000000000000000002440000000000000F03F0000000000000000000000000000F03F000000000000000000000000000000000000000000000840000000000000004000000000000020400000000000001C4000000000000000400000000000000000000000000000F03F000000000000F03F0000000000000000000000000000F03F000000000000F03F000000000000F03F0000000000000000000000000000000000000000000008400000000000000000000000000000084000000000000008400000000000000000000000000000084000000000000000000000000000000000000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F0000000000000040000000000000F03F000000000000F03F000000000000224000000000000022400000000000002440000000000000224000000000000022400000000000002440000000000000224000000000000022400000000000000000000000000000F03F000000000000F03F0000000000000000000000000000184000000000000022400000000000001C4000000000000020400000000000000000000000000000F0BF00000000000022400000000000001C40000000000000004000000000000024C0000000000040534000000000004053400000000000005640000000000000564000000000000018400000000000000040000000000000084000000000000008400000000000001C400000000000001C4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C7D79E59127037C00000000000000000C7D79E59127037400000000000000000C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C000000000000000000000000000001C400000000000001C40C7D79E591270374000000000000000000000000000002040000000000000204000000000000020400000000000002040C7D79E591270374000000000008046C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000000000000000000000000000000000000000000000002440000000000000F03F0000000000000000000000000000F03F00000000000000000000000000000000000000000000F03F000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000004000000000000008400000000000001040000000000000004000000000000000400000000000000840000000000000104000000000000000400000000000000040000000000000084000000000000010400000000000000040000000000000F8FF0000000000002340000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF00000000000008400000000000000840000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000405340000000000000564000000000000010400000000000001840000000000000F03F000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF0000000000000040000000000000084000000000000010400000000000000040120000000100000000010100000002040000000109000000010D00000001110000000115000000011600000001170000000119000000011B00000001200000000124000000032800000001340000000338000000033C000000014600000011000000FFFFFFFF0000000007000000000000000001000000000100000002000000000200000008000000000300000003000000000600000004050000000600000001050000000700000001000000000800000005080000000800000002080000000900000002000000000A000000060B0000000A000000030B0000000C00000003000000000D00000009000000000E0000000A0000000011000000031000000003010302000002000203020003010203".toCharArray()); - Geometry geomWKT = Geometry.deserialize(geomWKB); Geography geogWKT = Geography.deserialize(geogWKB); - assertEquals(geomWKT.asTextZM(), geoWKT); assertEquals(geogWKT.asTextZM(), geoWKT); } @@ -149,13 +128,10 @@ public void testGeometryCollectionWkb() throws DecoderException { @Test public void testCircularStringWkb() throws DecoderException { String geoWKT = "CIRCULARSTRING(2 1 3 4, 1 2 3, 0 7 3, 1 0 3, 2 1 3)"; - byte[] geomWKB = Hex.decodeHex("000000000207050000000000000000000040000000000000F03F000000000000F03F000000000000004000000000000000000000000000001C40000000000000F03F00000000000000000000000000000040000000000000F03F000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000001040000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF01000000020000000001000000FFFFFFFF0000000008".toCharArray()); byte[] geogWKB = Hex.decodeHex("E6100000020705000000000000000000F03F00000000000000400000000000000040000000000000F03F0000000000001C4000000000000000000000000000000000000000000000F03F000000000000F03F0000000000000040000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000001040000000000000F8FF000000000000F8FF000000000000F8FF000000000000F8FF01000000020000000001000000FFFFFFFF0000000008".toCharArray()); - Geometry geomWKT = Geometry.deserialize(geomWKB); Geography geogWKT = Geography.deserialize(geogWKB); - assertEquals(geomWKT.asTextZM(), geoWKT); assertEquals(geogWKT.asTextZM(), geoWKT); } @@ -163,13 +139,10 @@ public void testCircularStringWkb() throws DecoderException { @Test public void testCompoundCurveWkb() throws DecoderException { String geoWKT = "COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1))"; - byte[] geomWKB = Hex.decodeHex("0000000002050C000000000000000000F03F00000000000000000000000000000000000000000000F03F0000000000002240000000000000184000000000000020400000000000001C40000000000000F0BF00000000000000000000000000001C40000000000000224000000000000024C00000000000000040000000000040534000000000004053400000000000005640000000000000564000000000000000400000000000001840000000000000084000000000000008400000000000001C400000000000001C4000000000000008400000000000000840000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000405340000000000000564000000000000010400000000000001840000000000000F03F01000000030000000001000000FFFFFFFF0000000009080000000301030200000200".toCharArray()); byte[] geogWKB = Hex.decodeHex("E610000002050C0000000000000000000000000000000000F03F000000000000F03F0000000000000000000000000000184000000000000022400000000000001C4000000000000020400000000000000000000000000000F0BF00000000000022400000000000001C40000000000000004000000000000024C0000000000040534000000000004053400000000000005640000000000000564000000000000018400000000000000040000000000000084000000000000008400000000000001C400000000000001C4000000000000008400000000000000840000000000000084000000000000008400000000000000840000000000000084000000000000008400000000000405340000000000000564000000000000010400000000000001840000000000000F03F01000000030000000001000000FFFFFFFF0000000009080000000301030200000200".toCharArray()); - Geometry geomWKT = Geometry.deserialize(geomWKB); Geography geogWKT = Geography.deserialize(geogWKB); - assertEquals(geomWKT.asTextZM(), geoWKT); assertEquals(geogWKT.asTextZM(), geoWKT); } @@ -177,13 +150,10 @@ public void testCompoundCurveWkb() throws DecoderException { @Test public void testCurvePolygonWkb() throws DecoderException { String geoWKT = "CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778)))"; - byte[] geomWKB = Hex.decodeHex("0A00000002001700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000008400000000000000840000000000000144000000000000010400000000000001C400000000000001C400000000000000840000000000000F03F00000000000008400000000000000000C7D79E59127037C00000000000000000C7D79E591270374000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C00000000000000000C7D79E59127037C00000000000001C400000000000001C400000000000000000C7D79E5912703740000000000000204000000000000020400000000000002040000000000000204000000000008046C0C7D79E591270374000000000008056C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); byte[] geogWKB = Hex.decodeHex("E6100000020017000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000840000000000000F03F000000000000144000000000000008400000000000001C40000000000000104000000000000008400000000000001C400000000000000840000000000000F03FC7D79E59127037C00000000000000000C7D79E59127037400000000000000000C7D79E59127037C000000000008046C0C7D79E59127037C00000000000000000C7D79E59127037C000000000000000000000000000001C400000000000001C40C7D79E591270374000000000000000000000000000002040000000000000204000000000000020400000000000002040C7D79E591270374000000000008046C0C7D79E591270374000000000008056C0C7D79E59127037C000000000008056C0C7D79E59127037C000000000008046C0C7D79E59127037C0000000000000000004000000010000000002040000000309000000030D00000001000000FFFFFFFF000000000A080000000203020003010203".toCharArray()); - Geometry geomWKT = Geometry.deserialize(geomWKB); Geography geogWKT = Geography.deserialize(geogWKB); - assertEquals(geomWKT.asTextZM(), geoWKT); assertEquals(geogWKT.asTextZM(), geoWKT); } @@ -191,101 +161,68 @@ public void testCurvePolygonWkb() throws DecoderException { @Test public void testFullGlobeWkb() throws DecoderException { String geoWKT = "FULLGLOBE"; - byte[] geogWKB = Hex.decodeHex("E61000000224000000000000000001000000FFFFFFFFFFFFFFFF0B".toCharArray()); - Geography geogWKT = Geography.deserialize(geogWKB); - assertEquals(geogWKT.asTextZM(), geoWKT); } @Test public void testPointWkt() throws SQLException { beforeEachSetup(); - String geoWKT = "POINT(3 40 5 6)"; - testWkt(geoWKT); - beforeEachSetup(); - geoWKT = "POINT EMPTY"; - testWkt(geoWKT); } @Test public void testLineStringWkt() throws SQLException { beforeEachSetup(); - String geoWKT = "LINESTRING(1 0, 0 1, -1 0)"; - testWkt(geoWKT); - beforeEachSetup(); - geoWKT = "LINESTRING EMPTY"; - testWkt(geoWKT); } @Test public void testPolygonWkt() throws SQLException { beforeEachSetup(); - String geoWKT = "POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1))"; - testWkt(geoWKT); - beforeEachSetup(); - geoWKT = "POLYGON EMPTY"; - testWkt(geoWKT); } @Test public void testMultiPointWkt() throws SQLException { beforeEachSetup(); - String geoWKT = "MULTIPOINT((2 3), (7 8 9.5))"; - testWkt(geoWKT); - beforeEachSetup(); - geoWKT = "MULTIPOINT EMPTY"; - testWkt(geoWKT); } @Test public void testMultiLineStringWkt() throws SQLException { beforeEachSetup(); - String geoWKT = "MULTILINESTRING((0 2, 1 1), (1 0, 1 1))"; - testWkt(geoWKT); - beforeEachSetup(); - geoWKT = "MULTILINESTRING EMPTY"; - testWkt(geoWKT); } @Test public void testMultiPolygonWkt() throws SQLException { beforeEachSetup(); - String geoWKT = "MULTIPOLYGON(((1 1, 1 2, 2 1, 1 1), (0 0, 0 3, 3 3, 3 0, 0 0 7)), ((9 9, 9 10, 10 9, 9 9)))"; - testWkt(geoWKT); - beforeEachSetup(); - geoWKT = "MULTIPOLYGON EMPTY"; - testWkt(geoWKT); } @@ -294,28 +231,17 @@ public void testGeometryCollectionWkt() throws SQLException { String geoWKT; if (isDenaliOrLater) { beforeEachSetup(); - geoWKT = "GEOMETRYCOLLECTION(POINT(3 3 1), LINESTRING(1 0, 0 1, -1 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2), (0 0 2, 1 10 3, 1 0 4, 0 0 2)), MULTIPOINT((2 3), (7 8 9.5)), MULTILINESTRING((0 2, 1 1), (1 0, 1 1)), MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), ((9 9, 9 10, 10 9, 9 9))), COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1)), CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778))), POLYGON((0 0 2, 1 10 3, 1 0 4, 0 0 2)))"; - testWkt(geoWKT); } - beforeEachSetup(); - geoWKT = "GEOMETRYCOLLECTION EMPTY"; - testWkt(geoWKT); - beforeEachSetup(); - geoWKT = "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY)"; - testWkt(geoWKT); - beforeEachSetup(); - geoWKT = "GEOMETRYCOLLECTION(POINT(3 3 1), GEOMETRYCOLLECTION EMPTY, MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 1, 1 1)), EMPTY, EMPTY, ((0 0, 1 1, 2 2, 0 0)), EMPTY), GEOMETRYCOLLECTION EMPTY)"; - testWkt(geoWKT); } @@ -323,15 +249,10 @@ public void testGeometryCollectionWkt() throws SQLException { public void testCircularStringWkt() throws SQLException { if (isDenaliOrLater) { beforeEachSetup(); - String geoWKT = "CIRCULARSTRING(2 1 3 4, 1 2 3, 0 7 3, 1 0 3, 2 1 3)"; - testWkt(geoWKT); - beforeEachSetup(); - geoWKT = "CIRCULARSTRING EMPTY"; - testWkt(geoWKT); } } @@ -340,15 +261,10 @@ public void testCircularStringWkt() throws SQLException { public void testCompoundCurveWkt() throws SQLException { if (isDenaliOrLater) { beforeEachSetup(); - String geoWKT = "COMPOUNDCURVE(CIRCULARSTRING(1 0 3, 0 1 3, 9 6 3, 8 7 3, -1 0 3), CIRCULARSTRING(-1 0 3, 7 9 3, -10 2 3), (-10 2 3, 77 77 77, 88 88 88, 2 6 4), (2 6 4, 3 3 6, 7 7 1))"; - testWkt(geoWKT); - beforeEachSetup(); - geoWKT = "COMPOUNDCURVE EMPTY"; - testWkt(geoWKT); } } @@ -357,15 +273,10 @@ public void testCompoundCurveWkt() throws SQLException { public void testCurvePolygonWkt() throws SQLException { if (isDenaliOrLater) { beforeEachSetup(); - String geoWKT = "CURVEPOLYGON((0 0, 0 0, 0 0, 0 0), CIRCULARSTRING(1 3, 3 5, 4 7, 7 3, 1 3), COMPOUNDCURVE((0 -23.43778, 0 23.43778), CIRCULARSTRING(0 23.43778, -45 -23.43778, 0 -23.43778)), COMPOUNDCURVE((0 -23.43778, 7 7, 0 23.43778), CIRCULARSTRING(0 23.43778, 8 8, 8 8, -45 23.43778, -90 23.43778), (-90 23.43778, -90 -23.43778), CIRCULARSTRING(-90 -23.43778, -45 -23.43778, 0 -23.43778)))"; - testWkt(geoWKT); - beforeEachSetup(); - geoWKT = "CURVEPOLYGON EMPTY"; - testWkt(geoWKT); } }