package com.revolsys.gis.postgresql.type; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Map; import org.postgresql.util.PGobject; import com.revolsys.beans.Classes; import com.revolsys.collection.map.Maps; import com.revolsys.datatype.DataType; import com.revolsys.datatype.DataTypes; import com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.Lineal; import com.revolsys.geometry.model.LinearRing; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.Polygon; import com.revolsys.geometry.model.Polygonal; import com.revolsys.geometry.model.Punctual; import com.revolsys.util.MathUtil; import com.revolsys.util.function.Consumer3; import com.revolsys.util.number.Doubles; public class PostgreSQLGeometryWrapper extends PGobject { private static final long serialVersionUID = 0L; private static final Map<DataType, Consumer3<PrintWriter, Geometry, Integer>> WRITER_BY_TYPE = Maps .<DataType, Consumer3<PrintWriter, Geometry, Integer>> buildHash() // .add(DataTypes.POINT, PostgreSQLGeometryWrapper::writePoint) .add(DataTypes.LINE_STRING, PostgreSQLGeometryWrapper::writeLineString) .add(DataTypes.LINEAR_RING, PostgreSQLGeometryWrapper::writeLinearRing) .add(DataTypes.POLYGON, PostgreSQLGeometryWrapper::writePolygon) .add(DataTypes.MULTI_POINT, PostgreSQLGeometryWrapper::writeMultiPoint) .add(DataTypes.MULTI_LINE_STRING, PostgreSQLGeometryWrapper::writeMultiLineString) .add(DataTypes.MULTI_POLYGON, PostgreSQLGeometryWrapper::writeMultiPolygon) .add(DataTypes.GEOMETRY_COLLECTION, PostgreSQLGeometryWrapper::writeGeometry) .add(DataTypes.GEOMETRY, PostgreSQLGeometryWrapper::writeGeometry) .getMap(); public static void append(final StringBuilder wkt, final int axisCount, final Point point) { for (int i = 0; i < axisCount; i++) { if (i > 0) { wkt.append(" "); } MathUtil.append(wkt, point.getCoordinate(i)); } } public static void appendLineString(final StringBuilder wkt, final Point... points) { wkt.append("LINESTRING"); int axisCount = 2; for (final Point point : points) { axisCount = Math.max(axisCount, point.getAxisCount()); } if (axisCount > 3) { wkt.append(" ZM"); } else if (axisCount > 2) { wkt.append(" Z"); } boolean first = true; for (final Point point : points) { if (first) { first = false; } else { wkt.append(","); } append(wkt, axisCount, point); } wkt.append(")"); } public static void appendPoint(final StringBuilder wkt, final Point point) { wkt.append("POINT"); final int axisCount = point.getAxisCount(); if (axisCount > 3) { wkt.append(" ZM"); } else if (axisCount > 2) { wkt.append(" Z"); } append(wkt, axisCount, point); wkt.append(")"); } @SuppressWarnings("unchecked") private static <G extends Geometry> G getGeometry(final Geometry geometry, final Class<G> expectedClass) { if (expectedClass.isAssignableFrom(geometry.getClass())) { return (G)geometry; } else if (geometry.isGeometryCollection()) { if (geometry.getGeometryCount() == 1) { final Geometry firstGeometry = geometry.getGeometry(0); if (expectedClass.isAssignableFrom(firstGeometry.getClass())) { return (G)geometry; } else { throw new RuntimeException(geometry.getGeometryType() + " must contain a single " + Classes.className(expectedClass) + " not a " + firstGeometry.getGeometryType()); } } else { throw new RuntimeException(geometry.getGeometryType() + " must only have one " + Classes.className(expectedClass) + " not " + geometry.getGeometryCount()); } } else { throw new RuntimeException( "Expecting a " + Classes.className(expectedClass) + " not " + geometry.getGeometryType()); } } /** * Generates the WKT for a <tt>LINESTRING</tt> * specified by two {@link Coordinates}s. * * @param point1 the first coordinate * @param point2 the second coordinate * * @return the WKT */ public static String lineString(final Point... points) { final StringBuilder wkt = new StringBuilder(); appendLineString(wkt, points); return wkt.toString(); } /** * Generates the WKT for a <tt>POINT</tt> * specified by a {@link Coordinates}. * * @param p0 the point coordinate * * @return the WKT */ public static String point(final Point point) { final StringBuilder wkt = new StringBuilder(); appendPoint(wkt, point); return wkt.toString(); } private static void writeAxis(final PrintWriter out, final int axisCount) { if (axisCount > 3) { out.print("M"); } } private static void writeCoordinates(final PrintWriter out, final LineString coordinates, final int axisCount) { out.print('('); writeLineStringVertex(out, coordinates, 0, axisCount); for (int i = 1; i < coordinates.getVertexCount(); i++) { out.print(','); writeLineStringVertex(out, coordinates, i, axisCount); } out.print(')'); } private static void writeCoordinates(final PrintWriter out, final Point point, final int axisCount) { writeOrdinate(out, point, 0); for (int j = 1; j < axisCount; j++) { out.print(' '); writeOrdinate(out, point, j); } } private static void writeCoordinatesReverse(final PrintWriter out, final LineString coordinates, final int axisCount) { out.print('('); final int lastVertexIndex = coordinates.getVertexCount() - 1; writeLineStringVertex(out, coordinates, lastVertexIndex, axisCount); for (int i = lastVertexIndex - 1; i >= 0; i--) { out.print(','); writeLineStringVertex(out, coordinates, i, axisCount); } out.print(')'); } private static void writeGeometry(final PrintWriter out, final Geometry geometry, final int axisCount) { if (geometry != null) { if (geometry instanceof Point) { final Point point = (Point)geometry; writePoint(out, point, axisCount); } else if (geometry instanceof Punctual) { final Punctual punctual = (Punctual)geometry; writeMultiPoint(out, punctual, axisCount); } else if (geometry instanceof LinearRing) { final LinearRing line = (LinearRing)geometry; writeLinearRing(out, line, axisCount); } else if (geometry instanceof LineString) { final LineString line = (LineString)geometry; writeLineString(out, line, axisCount); } else if (geometry instanceof Lineal) { final Lineal lineal = (Lineal)geometry; writeMultiLineString(out, lineal, axisCount); } else if (geometry instanceof Polygon) { final Polygon polygon = (Polygon)geometry; writePolygon(out, polygon, axisCount); } else if (geometry instanceof Polygonal) { final Polygonal polygonal = (Polygonal)geometry; writeMultiPolygon(out, polygonal, axisCount); } else if (geometry.isGeometryCollection()) { writeGeometryCollection(out, geometry, axisCount); } else { throw new IllegalArgumentException("Unknown geometry type" + geometry.getClass()); } } } private static void writeGeometryCollection(final PrintWriter out, final Geometry geometry, final int axisCount) { writeGeometryType(out, "GEOMETRYCOLLECTION", axisCount); if (geometry.isEmpty()) { out.print(" EMPTY"); } else { out.print("("); Geometry part = geometry.getGeometry(0); writeGeometry(out, part, axisCount); for (int i = 1; i < geometry.getGeometryCount(); i++) { out.print(','); part = part.getGeometry(i); writeGeometry(out, part, axisCount); } out.print(')'); } } private static void writeGeometryType(final PrintWriter out, final String geometryType, final int axisCount) { out.print(geometryType); writeAxis(out, axisCount); } private static void writeLinearRing(final PrintWriter out, final Geometry geometry, final int axisCount) { final LinearRing line = getGeometry(geometry, LinearRing.class); writeLinearRing(out, line, axisCount); } private static void writeLinearRing(final PrintWriter out, final LinearRing line, final int axisCount) { writeGeometryType(out, "LINEARRING", axisCount); if (line.isEmpty()) { out.print(" EMPTY"); } else { writeCoordinates(out, line, axisCount); } } private static void writeLineString(final PrintWriter out, final Geometry geometry, final int axisCount) { final LineString line = getGeometry(geometry, LineString.class); writeLineString(out, line, axisCount); } private static void writeLineString(final PrintWriter out, final LineString line, final int axisCount) { writeGeometryType(out, "LINESTRING", axisCount); if (line.isEmpty()) { out.print(" EMPTY"); } else { final LineString coordinates = line; writeCoordinates(out, coordinates, axisCount); } } private static void writeLineStringVertex(final PrintWriter out, final LineString line, final int index, final int axisCount) { writeOrdinate(out, line, index, 0); for (int j = 1; j < axisCount; j++) { out.print(' '); writeOrdinate(out, line, index, j); } } private static void writeMultiLineString(final PrintWriter out, final Geometry geometry, final int axisCount) { writeGeometryType(out, "MULTILINESTRING", axisCount); if (geometry.isEmpty()) { out.print(" EMPTY"); } else { try { out.print("("); LineString line = geometry.getGeometry(0); writeCoordinates(out, line, axisCount); for (int i = 1; i < geometry.getGeometryCount(); i++) { out.print(","); line = (LineString)geometry.getGeometry(i); writeCoordinates(out, line, axisCount); } out.print(")"); } catch (final ClassCastException e) { throw new IllegalArgumentException( "Expecting a MultiPoint not " + geometry.getGeometryType()); } } } private static void writeMultiPoint(final PrintWriter out, final Geometry geometry, final int axisCount) { writeGeometryType(out, "MULTIPOINT", axisCount); if (geometry.isEmpty()) { out.print(" EMPTY"); } else { try { Point point = geometry.getGeometry(0); out.print("(("); writeCoordinates(out, point, axisCount); for (int i = 1; i < geometry.getGeometryCount(); i++) { out.print("),("); point = geometry.getGeometry(i); writeCoordinates(out, point, axisCount); } out.print("))"); } catch (final ClassCastException e) { throw new IllegalArgumentException( "Expecting a MultiPoint not " + geometry.getGeometryType()); } } } private static void writeMultiPolygon(final PrintWriter out, final Geometry geometry, final int axisCount) { writeGeometryType(out, "MULTIPOLYGON", axisCount); if (geometry.isEmpty()) { out.print(" EMPTY"); } else { try { out.print("("); Polygon polygon = (Polygon)geometry.getGeometry(0); writePolygonCoordinates(out, polygon, axisCount); for (int i = 1; i < geometry.getGeometryCount(); i++) { out.print(","); polygon = (Polygon)geometry.getGeometry(i); writePolygonCoordinates(out, polygon, axisCount); } out.print(")"); } catch (final ClassCastException e) { throw new IllegalArgumentException( "Expecting a MultiPolygon not " + geometry.getGeometryType()); } } } private static void writeOrdinate(final PrintWriter out, final LineString coordinates, final int index, final int ordinateIndex) { if (ordinateIndex > coordinates.getAxisCount()) { out.print(0); } else { final double ordinate = coordinates.getCoordinate(index, ordinateIndex); if (Double.isNaN(ordinate)) { out.print(0); } else { out.print(Doubles.toString(ordinate)); } } } private static void writeOrdinate(final PrintWriter out, final Point coordinates, final int ordinateIndex) { if (ordinateIndex > coordinates.getAxisCount()) { out.print(0); } else { final double ordinate = coordinates.getCoordinate(ordinateIndex); if (Double.isNaN(ordinate)) { out.print(0); } else { out.print(Doubles.toString(ordinate)); } } } private static void writePoint(final PrintWriter out, final Geometry geometry, final int axisCount) { final Point point = getGeometry(geometry, Point.class); writePoint(out, point, axisCount); } private static void writePoint(final PrintWriter out, final Point point, final int axisCount) { writeGeometryType(out, "POINT", axisCount); if (point.isEmpty()) { out.print(" EMPTY"); } else { out.print("("); writeCoordinates(out, point, axisCount); out.print(')'); } } private static void writePolygon(final PrintWriter out, final Geometry geometry, final int axisCount) { final Polygon polygon = getGeometry(geometry, Polygon.class); writePolygon(out, polygon, axisCount); } private static void writePolygon(final PrintWriter out, final Polygon polygon, final int axisCount) { writeGeometryType(out, "POLYGON", axisCount); if (polygon.isEmpty()) { out.print(" EMPTY"); } else { writePolygonCoordinates(out, polygon, axisCount); } } private static void writePolygonCoordinates(final PrintWriter out, final Polygon polygon, final int axisCount) { out.print('('); { final LineString shell = polygon.getShell(); if (shell.isClockwise()) { writeCoordinatesReverse(out, shell, axisCount); } else { writeCoordinates(out, shell, axisCount); } } for (final LineString hole : polygon.holes()) { out.print(','); if (hole.isClockwise()) { writeCoordinates(out, hole, axisCount); } else { writeCoordinatesReverse(out, hole, axisCount); } } out.print(')'); } private Geometry geometry; public PostgreSQLGeometryWrapper() { setType("geometry"); } public PostgreSQLGeometryWrapper(final DataType dataType, final GeometryFactory geometryFactory, final Geometry geometry) { this(); this.geometry = geometry.convertGeometry(geometryFactory); final StringWriter wkt = new StringWriter(); try ( final PrintWriter writer = new PrintWriter(wkt)) { final int srid = geometry.getCoordinateSystemId(); if (srid > 0) { writer.print("SRID="); writer.print(srid); writer.print(';'); } if (this.geometry != null) { final Consumer3<PrintWriter, Geometry, Integer> writeMethod = WRITER_BY_TYPE.get(dataType); writeMethod.accept(writer, this.geometry, geometryFactory.getAxisCount()); } } this.value = wkt.toString(); } @Override public PostgreSQLGeometryWrapper clone() { try { return (PostgreSQLGeometryWrapper)super.clone(); } catch (final CloneNotSupportedException e) { return null; } } public Geometry getGeometry(final GeometryFactory geometryFactory) { if (this.geometry == null) { newGeometry(geometryFactory); } return this.geometry; } public void newGeometry(GeometryFactory geometryFactory) { final String value = getValue().trim(); int srid = -1; String wkt; if (value.startsWith("SRID=")) { final int index = value.indexOf(';', 5); if (index == -1) { throw new IllegalArgumentException("Error parsing Geometry - SRID not delimited with ';' "); } else { srid = Integer.parseInt(value.substring(5, index)); wkt = value.substring(index + 1).trim(); } } else { wkt = value; } if (srid != -1 && geometryFactory.getCoordinateSystemId() != srid) { geometryFactory = GeometryFactory.floating(srid, geometryFactory.getAxisCount()); } if (wkt.startsWith("00") || wkt.startsWith("01")) { this.geometry = parseWkb(geometryFactory, wkt); } else { this.geometry = geometryFactory.geometry(value); } } private Geometry parseCollection(final GeometryFactory geometryFactory, final ValueGetter data) { final int count = data.getInt(); final Geometry[] geoms = new Geometry[count]; parseGeometryArray(geometryFactory, data, geoms); return geometryFactory.geometry(geoms); } private double[] parseCoordinates(final int axisCount, final ValueGetter data, final boolean hasZ, final boolean hasM) { final int vertexCount = data.getInt(); final double[] coordinates = new double[axisCount * vertexCount]; int coordinateIndex = 0; for (int vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) { final double x = data.getDouble(); coordinates[coordinateIndex++] = x; final double y = data.getDouble(); coordinates[coordinateIndex++] = y; if (hasM) { if (hasZ) { final double z = data.getDouble(); coordinates[coordinateIndex++] = z; final double m = data.getDouble(); coordinates[coordinateIndex++] = m; } else { coordinateIndex++; // no z so increment index final double m = data.getDouble(); coordinates[coordinateIndex++] = m; } } else if (hasZ) { final double z = data.getDouble(); coordinates[coordinateIndex++] = z; } } return coordinates; } private Geometry parseGeometry(final GeometryFactory geometryFactory, final ValueGetter data) { final int typeword = data.getInt(); final int realtype = typeword & 0x1FFFFFFF; final boolean hasZ = (typeword & 0x80000000) != 0; final boolean hasM = (typeword & 0x40000000) != 0; final boolean hasS = (typeword & 0x20000000) != 0; GeometryFactory currentGeometryFactory = geometryFactory; if (hasS) { final int coordinateSystemId = data.getInt(); if (coordinateSystemId >= 0 && currentGeometryFactory.getCoordinateSystemId() != coordinateSystemId) { currentGeometryFactory = currentGeometryFactory.convertSrid(coordinateSystemId); } } int axisCount; if (hasM) { axisCount = 4; } else if (hasZ) { axisCount = 3; } else { axisCount = 2; } if (axisCount != currentGeometryFactory.getAxisCount()) { currentGeometryFactory = currentGeometryFactory.convertAxisCount(axisCount); } Geometry geometry; switch (realtype) { case 1: geometry = parsePoint(currentGeometryFactory, data, hasZ, hasM); break; case 2: geometry = parseLineString(currentGeometryFactory, data, hasZ, hasM); break; case 3: geometry = parsePolygon(currentGeometryFactory, data, hasZ, hasM); break; case 4: geometry = parseMultiPoint(currentGeometryFactory, data); break; case 5: geometry = parseMultiLineString(currentGeometryFactory, data); break; case 6: geometry = parseMultiPolygon(currentGeometryFactory, data); break; case 7: geometry = parseCollection(currentGeometryFactory, data); break; default: throw new IllegalArgumentException("Unknown Geometry Type: " + realtype); } if (geometryFactory.isSameCoordinateSystem(currentGeometryFactory)) { return geometry; } else { return geometry.convertGeometry(geometryFactory); } } private void parseGeometryArray(final GeometryFactory geometryFactory, final ValueGetter data, final Geometry[] container) { for (int i = 0; i < container.length; ++i) { data.getByte(); // read endian container[i] = parseGeometry(geometryFactory, data); } } private LinearRing parseLinearRing(final GeometryFactory geometryFactory, final ValueGetter data, final boolean hasZ, final boolean hasM) { final int axisCount = geometryFactory.getAxisCount(); final double[] coordinates = parseCoordinates(axisCount, data, hasZ, hasM); return geometryFactory.linearRing(axisCount, coordinates); } private LineString parseLineString(final GeometryFactory geometryFactory, final ValueGetter data, final boolean hasZ, final boolean hasM) { final int axisCount = geometryFactory.getAxisCount(); final double[] coordinates = parseCoordinates(axisCount, data, hasZ, hasM); return geometryFactory.lineString(axisCount, coordinates); } private Geometry parseMultiLineString(final GeometryFactory geometryFactory, final ValueGetter data) { final int count = data.getInt(); final LineString[] lines = new LineString[count]; parseGeometryArray(geometryFactory, data, lines); if (lines.length == 1) { return lines[0]; } else { return geometryFactory.lineal(lines); } } private Geometry parseMultiPoint(final GeometryFactory geometryFactory, final ValueGetter data) { final Point[] points = new Point[data.getInt()]; parseGeometryArray(geometryFactory, data, points); if (points.length == 1) { return points[0]; } else { return geometryFactory.punctual(points); } } private Geometry parseMultiPolygon(final GeometryFactory geometryFactory, final ValueGetter data) { final int count = data.getInt(); final Polygon[] polys = new Polygon[count]; parseGeometryArray(geometryFactory, data, polys); if (polys.length == 1) { return polys[0]; } else { return geometryFactory.polygonal(polys); } } private Point parsePoint(final GeometryFactory geometryFactory, final ValueGetter data, final boolean hasZ, final boolean hasM) { final double x = data.getDouble(); final double y = data.getDouble(); if (hasM) { if (hasZ) { final double z = data.getDouble(); final double m = data.getDouble(); return geometryFactory.point(x, y, z, m); } else { final double m = data.getDouble(); return geometryFactory.point(x, y, Double.NaN, m); } } else if (hasZ) { final double z = data.getDouble(); return geometryFactory.point(x, y, z); } else { return geometryFactory.point(x, y); } } private Polygon parsePolygon(final GeometryFactory geometryFactory, final ValueGetter data, final boolean hasZ, final boolean hasM) { final int count = data.getInt(); final LinearRing[] rings = new LinearRing[count]; for (int i = 0; i < count; ++i) { rings[i] = parseLinearRing(geometryFactory, data, hasZ, hasM); } return geometryFactory.polygon(rings); } private Geometry parseWkb(final GeometryFactory geometryFactory, final String wkb) { final InputStream in = new StringByteInputStream(wkb); final ValueGetter valueGetter = ValueGetter.newValueGetter(in); return parseGeometry(geometryFactory, valueGetter); } }