package com.vividsolutions.jump.io; import com.vividsolutions.jts.geom.*; import com.vividsolutions.jts.util.*; import java.io.*; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; /** * Outputs the textual representation of a {@link Geometry}. * <p> * The <code>WKTWriter</code> outputs coordinates rounded to the precision * model. No more than the maximum number of necessary decimal places will be * output. * <p> * The Well-known Text format is defined in the <A * HREF="http://www.opengis.org/techno/specs.htm">OpenGIS Simple Features * Specification for SQL </A>. * <p> * A non-standard "LINEARRING" tag is used for LinearRings. The WKT spec does * not define a special tag for LinearRings. The standard tag to use is * "LINESTRING". * * @version 1.4 */ // Writes z-coordinates if they are not NaN. Will be moved into JTS in // the future. [Jon Aquino 2004-10-25] public class FUTURE_JTS_WKTWriter { private static int INDENT = 2; /** * Creates the <code>DecimalFormat</code> used to write * <code>double</code> s with a sufficient number of decimal places. * * @param precisionModel * the <code>PrecisionModel</code> used to determine the number * of decimal places to write. * @return a <code>DecimalFormat</code> that write <code>double</code> s * without scientific notation. */ private static DecimalFormat createFormatter(PrecisionModel precisionModel) { // the default number of decimal places is 16, which is sufficient // to accomodate the maximum precision of a double. int decimalPlaces = precisionModel.getMaximumSignificantDigits(); // specify decimal separator explicitly to avoid problems in other // locales DecimalFormatSymbols symbols = new DecimalFormatSymbols(); symbols.setDecimalSeparator('.'); return new DecimalFormat("#" + (decimalPlaces > 0 ? "." : "") + stringOfChar('#', decimalPlaces), symbols); } /** * Returns a <code>String</code> of repeated characters. * * @param ch * the character to repeat * @param count * the number of times to repeat the character * @return a <code>String</code> of characters */ public static String stringOfChar(char ch, int count) { StringBuffer buf = new StringBuffer(); for (int i = 0; i < count; i++) { buf.append(ch); } return buf.toString(); } private DecimalFormat formatter; private boolean isFormatted = false; private int level = 0; public FUTURE_JTS_WKTWriter() { } /** * Converts a <code>Geometry</code> to its Well-known Text representation. * * @param geometry * a <code>Geometry</code> to process * @return a <Geometry Tagged Text> string (see the OpenGIS Simple Features * Specification) */ public String write(Geometry geometry) { Writer sw = new StringWriter(); try { writeFormatted(geometry, false, sw); } catch (IOException ex) { Assert.shouldNeverReachHere(); } return sw.toString(); } /** * Converts a <code>Geometry</code> to its Well-known Text representation. * * @param geometry * a <code>Geometry</code> to process * @param writer a writer to write the WKT string to (see the * OpenGIS Simple Features Specification) */ public void write(Geometry geometry, Writer writer) throws IOException { writeFormatted(geometry, false, writer); } /** * Same as <code>write</code>, but with newlines and spaces to make the * well-known text more readable. * * @param geometry * a <code>Geometry</code> to process * @return a <Geometry Tagged Text> string (see the OpenGIS Simple Features * Specification), with newlines and spaces */ public String writeFormatted(Geometry geometry) { Writer sw = new StringWriter(); try { writeFormatted(geometry, true, sw); } catch (IOException ex) { Assert.shouldNeverReachHere(); } return sw.toString(); } /** * Same as <code>write</code>, but with newlines and spaces to make the * well-known text more readable. * * @param geometry * a <code>Geometry</code> to process * @param writer a writer to write the WKT string to (see the OpenGIS * Simple Features Specification), with newlines and spaces */ public void writeFormatted(Geometry geometry, Writer writer) throws IOException { writeFormatted(geometry, true, writer); } /** * Converts a <code>Geometry</code> to its Well-known Text representation. * * @param geometry * a <code>Geometry</code> to process * @return a <Geometry Tagged Text> string (see the OpenGIS Simple Features * Specification) */ private void writeFormatted(Geometry geometry, boolean isFormatted, Writer writer) throws IOException { this.isFormatted = isFormatted; formatter = createFormatter(geometry.getPrecisionModel()); appendGeometryTaggedText(geometry, 0, writer); } /** * Converts a <code>Geometry</code> to <Geometry Tagged Text> * format, then appends it to the writer. * * @param geometry * the <code>Geometry</code> to process * @param writer * the output writer to append to */ private void appendGeometryTaggedText(Geometry geometry, int level, Writer writer) throws IOException { indent(level, writer); if (geometry instanceof Point) { Point point = (Point) geometry; appendPointTaggedText(point.getCoordinate(), level, writer, point .getPrecisionModel()); } else if (geometry instanceof LinearRing) { appendLinearRingTaggedText((LinearRing) geometry, level, writer); } else if (geometry instanceof LineString) { appendLineStringTaggedText((LineString) geometry, level, writer); } else if (geometry instanceof Polygon) { appendPolygonTaggedText((Polygon) geometry, level, writer); } else if (geometry instanceof MultiPoint) { appendMultiPointTaggedText((MultiPoint) geometry, level, writer); } else if (geometry instanceof MultiLineString) { appendMultiLineStringTaggedText((MultiLineString) geometry, level, writer); } else if (geometry instanceof MultiPolygon) { appendMultiPolygonTaggedText((MultiPolygon) geometry, level, writer); } else if (geometry instanceof GeometryCollection) { appendGeometryCollectionTaggedText((GeometryCollection) geometry, level, writer); } else { Assert.shouldNeverReachHere("Unsupported Geometry implementation:" + geometry.getClass()); } } /** * Converts a <code>Coordinate</code> to <Point Tagged Text> format, * then appends it to the writer. * * @param coordinate * the <code>Coordinate</code> to process * @param writer * the output writer to append to * @param precisionModel * the <code>PrecisionModel</code> to use to convert from a * precise coordinate to an external coordinate */ private void appendPointTaggedText(Coordinate coordinate, int level, Writer writer, PrecisionModel precisionModel) throws IOException { writer.write("POINT "); appendPointText(coordinate, level, writer, precisionModel); } /** * Converts a <code>LineString</code> to <LineString Tagged Text> * format, then appends it to the writer. * * @param lineString * the <code>LineString</code> to process * @param writer * the output writer to append to */ private void appendLineStringTaggedText(LineString lineString, int level, Writer writer) throws IOException { writer.write("LINESTRING "); appendLineStringText(lineString, level, false, writer); } /** * Converts a <code>LinearRing</code> to <LinearRing Tagged Text> * format, then appends it to the writer. * * @param linearRing * the <code>LinearRing</code> to process * @param writer * the output writer to append to */ private void appendLinearRingTaggedText(LinearRing linearRing, int level, Writer writer) throws IOException { writer.write("LINEARRING "); appendLineStringText(linearRing, level, false, writer); } /** * Converts a <code>Polygon</code> to <Polygon Tagged Text> format, * then appends it to the writer. * * @param polygon * the <code>Polygon</code> to process * @param writer * the output writer to append to */ private void appendPolygonTaggedText(Polygon polygon, int level, Writer writer) throws IOException { writer.write("POLYGON "); appendPolygonText(polygon, level, false, writer); } /** * Converts a <code>MultiPoint</code> to <MultiPoint Tagged Text> * format, then appends it to the writer. * * @param multipoint * the <code>MultiPoint</code> to process * @param writer * the output writer to append to */ private void appendMultiPointTaggedText(MultiPoint multipoint, int level, Writer writer) throws IOException { writer.write("MULTIPOINT "); appendMultiPointText(multipoint, level, writer); } /** * Converts a <code>MultiLineString</code> to <MultiLineString Tagged * Text> format, then appends it to the writer. * * @param multiLineString * the <code>MultiLineString</code> to process * @param writer * the output writer to append to */ private void appendMultiLineStringTaggedText( MultiLineString multiLineString, int level, Writer writer) throws IOException { writer.write("MULTILINESTRING "); appendMultiLineStringText(multiLineString, level, false, writer); } /** * Converts a <code>MultiPolygon</code> to <MultiPolygon Tagged * Text> format, then appends it to the writer. * * @param multiPolygon * the <code>MultiPolygon</code> to process * @param writer * the output writer to append to */ private void appendMultiPolygonTaggedText(MultiPolygon multiPolygon, int level, Writer writer) throws IOException { writer.write("MULTIPOLYGON "); appendMultiPolygonText(multiPolygon, level, writer); } /** * Converts a <code>GeometryCollection</code> to <GeometryCollection * Tagged Text> format, then appends it to the writer. * * @param geometryCollection * the <code>GeometryCollection</code> to process * @param writer * the output writer to append to */ private void appendGeometryCollectionTaggedText( GeometryCollection geometryCollection, int level, Writer writer) throws IOException { writer.write("GEOMETRYCOLLECTION "); appendGeometryCollectionText(geometryCollection, level, writer); } /** * Converts a <code>Coordinate</code> to <Point Text> format, then * appends it to the writer. * * @param coordinate * the <code>Coordinate</code> to process * @param writer * the output writer to append to * @param precisionModel * the <code>PrecisionModel</code> to use to convert from a * precise coordinate to an external coordinate */ private void appendPointText(Coordinate coordinate, int level, Writer writer, PrecisionModel precisionModel) throws IOException { if (coordinate == null) { writer.write("EMPTY"); } else { writer.write("("); appendCoordinate(coordinate, writer, precisionModel); writer.write(")"); } } /** * Converts a <code>Coordinate</code> to <Point> format, then * appends it to the writer. * * @param coordinate * the <code>Coordinate</code> to process * @param writer * the output writer to append to * @param precisionModel * the <code>PrecisionModel</code> to use to convert from a * precise coordinate to an external coordinate */ private void appendCoordinate(Coordinate coordinate, Writer writer, PrecisionModel precisionModel) throws IOException { writer.write(writeNumber(coordinate.x) + " " + writeNumber(coordinate.y) + (Double.isNaN(coordinate.z) ? "" : " " + writeNumber(coordinate.z))); } /** * Converts a <code>double</code> to a <code>String</code>, not in * scientific notation. * * @param d * the <code>double</code> to convert * @return the <code>double</code> as a <code>String</code>, not in * scientific notation */ private String writeNumber(double d) { return formatter.format(d); } /** * Converts a <code>LineString</code> to <LineString Text> format, * then appends it to the writer. * * @param lineString * the <code>LineString</code> to process * @param writer * the output writer to append to */ private void appendLineStringText(LineString lineString, int level, boolean doIndent, Writer writer) throws IOException { if (lineString.isEmpty()) { writer.write("EMPTY"); } else { if (doIndent) indent(level, writer); writer.write("("); for (int i = 0; i < lineString.getNumPoints(); i++) { if (i > 0) { writer.write(", "); if (i % 10 == 0) indent(level + 2, writer); } appendCoordinate(lineString.getCoordinateN(i), writer, lineString.getPrecisionModel()); } writer.write(")"); } } /** * Converts a <code>Polygon</code> to <Polygon Text> format, then * appends it to the writer. * * @param polygon * the <code>Polygon</code> to process * @param writer * the output writer to append to */ private void appendPolygonText(Polygon polygon, int level, boolean indentFirst, Writer writer) throws IOException { if (polygon.isEmpty()) { writer.write("EMPTY"); } else { if (indentFirst) indent(level, writer); writer.write("("); appendLineStringText(polygon.getExteriorRing(), level, false, writer); for (int i = 0; i < polygon.getNumInteriorRing(); i++) { writer.write(", "); appendLineStringText(polygon.getInteriorRingN(i), level + 1, true, writer); } writer.write(")"); } } /** * Converts a <code>MultiPoint</code> to <MultiPoint Text> format, * then appends it to the writer. * * @param multiPoint * the <code>MultiPoint</code> to process * @param writer * the output writer to append to */ private void appendMultiPointText(MultiPoint multiPoint, int level, Writer writer) throws IOException { if (multiPoint.isEmpty()) { writer.write("EMPTY"); } else { writer.write("("); for (int i = 0; i < multiPoint.getNumGeometries(); i++) { if (i > 0) { writer.write(", "); } appendCoordinate(((Point) multiPoint.getGeometryN(i)) .getCoordinate(), writer, multiPoint .getPrecisionModel()); } writer.write(")"); } } /** * Converts a <code>MultiLineString</code> to <MultiLineString Text> * format, then appends it to the writer. * * @param multiLineString * the <code>MultiLineString</code> to process * @param writer * the output writer to append to */ private void appendMultiLineStringText(MultiLineString multiLineString, int level, boolean indentFirst, Writer writer) throws IOException { if (multiLineString.isEmpty()) { writer.write("EMPTY"); } else { int level2 = level; boolean doIndent = indentFirst; writer.write("("); for (int i = 0; i < multiLineString.getNumGeometries(); i++) { if (i > 0) { writer.write(", "); level2 = level + 1; doIndent = true; } appendLineStringText((LineString) multiLineString .getGeometryN(i), level2, doIndent, writer); } writer.write(")"); } } /** * Converts a <code>MultiPolygon</code> to <MultiPolygon Text> * format, then appends it to the writer. * * @param multiPolygon * the <code>MultiPolygon</code> to process * @param writer * the output writer to append to */ private void appendMultiPolygonText(MultiPolygon multiPolygon, int level, Writer writer) throws IOException { if (multiPolygon.isEmpty()) { writer.write("EMPTY"); } else { int level2 = level; boolean doIndent = false; writer.write("("); for (int i = 0; i < multiPolygon.getNumGeometries(); i++) { if (i > 0) { writer.write(", "); level2 = level + 1; doIndent = true; } appendPolygonText((Polygon) multiPolygon.getGeometryN(i), level2, doIndent, writer); } writer.write(")"); } } /** * Converts a <code>GeometryCollection</code> to * <GeometryCollectionText> format, then appends it to the writer. * * @param geometryCollection * the <code>GeometryCollection</code> to process * @param writer * the output writer to append to */ private void appendGeometryCollectionText( GeometryCollection geometryCollection, int level, Writer writer) throws IOException { if (geometryCollection.isEmpty()) { writer.write("EMPTY"); } else { int level2 = level; writer.write("("); for (int i = 0; i < geometryCollection.getNumGeometries(); i++) { if (i > 0) { writer.write(", "); level2 = level + 1; } appendGeometryTaggedText(geometryCollection.getGeometryN(i), level2, writer); } writer.write(")"); } } private void indent(int level, Writer writer) throws IOException { if (!isFormatted || level <= 0) return; writer.write("\n"); writer.write(stringOfChar(' ', INDENT * level)); } }