001: package com.vividsolutions.jump.io;
002:
003: import com.vividsolutions.jts.geom.*;
004:
005: import com.vividsolutions.jts.util.*;
006: import java.io.*;
007: import java.text.DecimalFormat;
008: import java.text.DecimalFormatSymbols;
009:
010: /**
011: * Outputs the textual representation of a {@link Geometry}.
012: * <p>
013: * The <code>WKTWriter</code> outputs coordinates rounded to the precision
014: * model. No more than the maximum number of necessary decimal places will be
015: * output.
016: * <p>
017: * The Well-known Text format is defined in the <A
018: * HREF="http://www.opengis.org/techno/specs.htm">OpenGIS Simple Features
019: * Specification for SQL </A>.
020: * <p>
021: * A non-standard "LINEARRING" tag is used for LinearRings. The WKT spec does
022: * not define a special tag for LinearRings. The standard tag to use is
023: * "LINESTRING".
024: *
025: * @version 1.4
026: */
027: // Writes z-coordinates if they are not NaN. Will be moved into JTS in
028: // the future. [Jon Aquino 2004-10-25]
029: public class FUTURE_JTS_WKTWriter {
030:
031: private static int INDENT = 2;
032:
033: /**
034: * Creates the <code>DecimalFormat</code> used to write
035: * <code>double</code> s with a sufficient number of decimal places.
036: *
037: * @param precisionModel
038: * the <code>PrecisionModel</code> used to determine the number
039: * of decimal places to write.
040: * @return a <code>DecimalFormat</code> that write <code>double</code> s
041: * without scientific notation.
042: */
043: private static DecimalFormat createFormatter(
044: PrecisionModel precisionModel) {
045: // the default number of decimal places is 16, which is sufficient
046: // to accomodate the maximum precision of a double.
047: int decimalPlaces = precisionModel
048: .getMaximumSignificantDigits();
049: // specify decimal separator explicitly to avoid problems in other
050: // locales
051: DecimalFormatSymbols symbols = new DecimalFormatSymbols();
052: symbols.setDecimalSeparator('.');
053: return new DecimalFormat("#" + (decimalPlaces > 0 ? "." : "")
054: + stringOfChar('#', decimalPlaces), symbols);
055: }
056:
057: /**
058: * Returns a <code>String</code> of repeated characters.
059: *
060: * @param ch
061: * the character to repeat
062: * @param count
063: * the number of times to repeat the character
064: * @return a <code>String</code> of characters
065: */
066: public static String stringOfChar(char ch, int count) {
067: StringBuffer buf = new StringBuffer();
068: for (int i = 0; i < count; i++) {
069: buf.append(ch);
070: }
071: return buf.toString();
072: }
073:
074: private DecimalFormat formatter;
075:
076: private boolean isFormatted = false;
077:
078: private int level = 0;
079:
080: public FUTURE_JTS_WKTWriter() {
081: }
082:
083: /**
084: * Converts a <code>Geometry</code> to its Well-known Text representation.
085: *
086: * @param geometry
087: * a <code>Geometry</code> to process
088: * @return a <Geometry Tagged Text> string (see the OpenGIS Simple Features
089: * Specification)
090: */
091: public String write(Geometry geometry) {
092: Writer sw = new StringWriter();
093: try {
094: writeFormatted(geometry, false, sw);
095: } catch (IOException ex) {
096: Assert.shouldNeverReachHere();
097: }
098: return sw.toString();
099: }
100:
101: /**
102: * Converts a <code>Geometry</code> to its Well-known Text representation.
103: *
104: * @param geometry
105: * a <code>Geometry</code> to process
106: * @return a <Geometry Tagged Text> string (see the OpenGIS Simple Features
107: * Specification)
108: */
109: public void write(Geometry geometry, Writer writer)
110: throws IOException {
111: writeFormatted(geometry, false, writer);
112: }
113:
114: /**
115: * Same as <code>write</code>, but with newlines and spaces to make the
116: * well-known text more readable.
117: *
118: * @param geometry
119: * a <code>Geometry</code> to process
120: * @return a <Geometry Tagged Text> string (see the OpenGIS Simple Features
121: * Specification), with newlines and spaces
122: */
123: public String writeFormatted(Geometry geometry) {
124: Writer sw = new StringWriter();
125: try {
126: writeFormatted(geometry, true, sw);
127: } catch (IOException ex) {
128: Assert.shouldNeverReachHere();
129: }
130: return sw.toString();
131: }
132:
133: /**
134: * Same as <code>write</code>, but with newlines and spaces to make the
135: * well-known text more readable.
136: *
137: * @param geometry
138: * a <code>Geometry</code> to process
139: * @return a <Geometry Tagged Text> string (see the OpenGIS Simple Features
140: * Specification), with newlines and spaces
141: */
142: public void writeFormatted(Geometry geometry, Writer writer)
143: throws IOException {
144: writeFormatted(geometry, true, writer);
145: }
146:
147: /**
148: * Converts a <code>Geometry</code> to its Well-known Text representation.
149: *
150: * @param geometry
151: * a <code>Geometry</code> to process
152: * @return a <Geometry Tagged Text> string (see the OpenGIS Simple Features
153: * Specification)
154: */
155: private void writeFormatted(Geometry geometry, boolean isFormatted,
156: Writer writer) throws IOException {
157: this .isFormatted = isFormatted;
158: formatter = createFormatter(geometry.getPrecisionModel());
159: appendGeometryTaggedText(geometry, 0, writer);
160: }
161:
162: /**
163: * Converts a <code>Geometry</code> to <Geometry Tagged Text>
164: * format, then appends it to the writer.
165: *
166: * @param geometry
167: * the <code>Geometry</code> to process
168: * @param writer
169: * the output writer to append to
170: */
171: private void appendGeometryTaggedText(Geometry geometry, int level,
172: Writer writer) throws IOException {
173: indent(level, writer);
174:
175: if (geometry instanceof Point) {
176: Point point = (Point) geometry;
177: appendPointTaggedText(point.getCoordinate(), level, writer,
178: point.getPrecisionModel());
179: } else if (geometry instanceof LinearRing) {
180: appendLinearRingTaggedText((LinearRing) geometry, level,
181: writer);
182: } else if (geometry instanceof LineString) {
183: appendLineStringTaggedText((LineString) geometry, level,
184: writer);
185: } else if (geometry instanceof Polygon) {
186: appendPolygonTaggedText((Polygon) geometry, level, writer);
187: } else if (geometry instanceof MultiPoint) {
188: appendMultiPointTaggedText((MultiPoint) geometry, level,
189: writer);
190: } else if (geometry instanceof MultiLineString) {
191: appendMultiLineStringTaggedText((MultiLineString) geometry,
192: level, writer);
193: } else if (geometry instanceof MultiPolygon) {
194: appendMultiPolygonTaggedText((MultiPolygon) geometry,
195: level, writer);
196: } else if (geometry instanceof GeometryCollection) {
197: appendGeometryCollectionTaggedText(
198: (GeometryCollection) geometry, level, writer);
199: } else {
200: Assert
201: .shouldNeverReachHere("Unsupported Geometry implementation:"
202: + geometry.getClass());
203: }
204: }
205:
206: /**
207: * Converts a <code>Coordinate</code> to <Point Tagged Text> format,
208: * then appends it to the writer.
209: *
210: * @param coordinate
211: * the <code>Coordinate</code> to process
212: * @param writer
213: * the output writer to append to
214: * @param precisionModel
215: * the <code>PrecisionModel</code> to use to convert from a
216: * precise coordinate to an external coordinate
217: */
218: private void appendPointTaggedText(Coordinate coordinate,
219: int level, Writer writer, PrecisionModel precisionModel)
220: throws IOException {
221: writer.write("POINT ");
222: appendPointText(coordinate, level, writer, precisionModel);
223: }
224:
225: /**
226: * Converts a <code>LineString</code> to <LineString Tagged Text>
227: * format, then appends it to the writer.
228: *
229: * @param lineString
230: * the <code>LineString</code> to process
231: * @param writer
232: * the output writer to append to
233: */
234: private void appendLineStringTaggedText(LineString lineString,
235: int level, Writer writer) throws IOException {
236: writer.write("LINESTRING ");
237: appendLineStringText(lineString, level, false, writer);
238: }
239:
240: /**
241: * Converts a <code>LinearRing</code> to <LinearRing Tagged Text>
242: * format, then appends it to the writer.
243: *
244: * @param linearRing
245: * the <code>LinearRing</code> to process
246: * @param writer
247: * the output writer to append to
248: */
249: private void appendLinearRingTaggedText(LinearRing linearRing,
250: int level, Writer writer) throws IOException {
251: writer.write("LINEARRING ");
252: appendLineStringText(linearRing, level, false, writer);
253: }
254:
255: /**
256: * Converts a <code>Polygon</code> to <Polygon Tagged Text> format,
257: * then appends it to the writer.
258: *
259: * @param polygon
260: * the <code>Polygon</code> to process
261: * @param writer
262: * the output writer to append to
263: */
264: private void appendPolygonTaggedText(Polygon polygon, int level,
265: Writer writer) throws IOException {
266: writer.write("POLYGON ");
267: appendPolygonText(polygon, level, false, writer);
268: }
269:
270: /**
271: * Converts a <code>MultiPoint</code> to <MultiPoint Tagged Text>
272: * format, then appends it to the writer.
273: *
274: * @param multipoint
275: * the <code>MultiPoint</code> to process
276: * @param writer
277: * the output writer to append to
278: */
279: private void appendMultiPointTaggedText(MultiPoint multipoint,
280: int level, Writer writer) throws IOException {
281: writer.write("MULTIPOINT ");
282: appendMultiPointText(multipoint, level, writer);
283: }
284:
285: /**
286: * Converts a <code>MultiLineString</code> to <MultiLineString Tagged
287: * Text> format, then appends it to the writer.
288: *
289: * @param multiLineString
290: * the <code>MultiLineString</code> to process
291: * @param writer
292: * the output writer to append to
293: */
294: private void appendMultiLineStringTaggedText(
295: MultiLineString multiLineString, int level, Writer writer)
296: throws IOException {
297: writer.write("MULTILINESTRING ");
298: appendMultiLineStringText(multiLineString, level, false, writer);
299: }
300:
301: /**
302: * Converts a <code>MultiPolygon</code> to <MultiPolygon Tagged
303: * Text> format, then appends it to the writer.
304: *
305: * @param multiPolygon
306: * the <code>MultiPolygon</code> to process
307: * @param writer
308: * the output writer to append to
309: */
310: private void appendMultiPolygonTaggedText(
311: MultiPolygon multiPolygon, int level, Writer writer)
312: throws IOException {
313: writer.write("MULTIPOLYGON ");
314: appendMultiPolygonText(multiPolygon, level, writer);
315: }
316:
317: /**
318: * Converts a <code>GeometryCollection</code> to <GeometryCollection
319: * Tagged Text> format, then appends it to the writer.
320: *
321: * @param geometryCollection
322: * the <code>GeometryCollection</code> to process
323: * @param writer
324: * the output writer to append to
325: */
326: private void appendGeometryCollectionTaggedText(
327: GeometryCollection geometryCollection, int level,
328: Writer writer) throws IOException {
329: writer.write("GEOMETRYCOLLECTION ");
330: appendGeometryCollectionText(geometryCollection, level, writer);
331: }
332:
333: /**
334: * Converts a <code>Coordinate</code> to <Point Text> format, then
335: * appends it to the writer.
336: *
337: * @param coordinate
338: * the <code>Coordinate</code> to process
339: * @param writer
340: * the output writer to append to
341: * @param precisionModel
342: * the <code>PrecisionModel</code> to use to convert from a
343: * precise coordinate to an external coordinate
344: */
345: private void appendPointText(Coordinate coordinate, int level,
346: Writer writer, PrecisionModel precisionModel)
347: throws IOException {
348: if (coordinate == null) {
349: writer.write("EMPTY");
350: } else {
351: writer.write("(");
352: appendCoordinate(coordinate, writer, precisionModel);
353: writer.write(")");
354: }
355: }
356:
357: /**
358: * Converts a <code>Coordinate</code> to <Point> format, then
359: * appends it to the writer.
360: *
361: * @param coordinate
362: * the <code>Coordinate</code> to process
363: * @param writer
364: * the output writer to append to
365: * @param precisionModel
366: * the <code>PrecisionModel</code> to use to convert from a
367: * precise coordinate to an external coordinate
368: */
369: private void appendCoordinate(Coordinate coordinate, Writer writer,
370: PrecisionModel precisionModel) throws IOException {
371: writer.write(writeNumber(coordinate.x)
372: + " "
373: + writeNumber(coordinate.y)
374: + (Double.isNaN(coordinate.z) ? "" : " "
375: + writeNumber(coordinate.z)));
376: }
377:
378: /**
379: * Converts a <code>double</code> to a <code>String</code>, not in
380: * scientific notation.
381: *
382: * @param d
383: * the <code>double</code> to convert
384: * @return the <code>double</code> as a <code>String</code>, not in
385: * scientific notation
386: */
387: private String writeNumber(double d) {
388: return formatter.format(d);
389: }
390:
391: /**
392: * Converts a <code>LineString</code> to <LineString Text> format,
393: * then appends it to the writer.
394: *
395: * @param lineString
396: * the <code>LineString</code> to process
397: * @param writer
398: * the output writer to append to
399: */
400: private void appendLineStringText(LineString lineString, int level,
401: boolean doIndent, Writer writer) throws IOException {
402: if (lineString.isEmpty()) {
403: writer.write("EMPTY");
404: } else {
405: if (doIndent)
406: indent(level, writer);
407: writer.write("(");
408: for (int i = 0; i < lineString.getNumPoints(); i++) {
409: if (i > 0) {
410: writer.write(", ");
411: if (i % 10 == 0)
412: indent(level + 2, writer);
413: }
414: appendCoordinate(lineString.getCoordinateN(i), writer,
415: lineString.getPrecisionModel());
416: }
417: writer.write(")");
418: }
419: }
420:
421: /**
422: * Converts a <code>Polygon</code> to <Polygon Text> format, then
423: * appends it to the writer.
424: *
425: * @param polygon
426: * the <code>Polygon</code> to process
427: * @param writer
428: * the output writer to append to
429: */
430: private void appendPolygonText(Polygon polygon, int level,
431: boolean indentFirst, Writer writer) throws IOException {
432: if (polygon.isEmpty()) {
433: writer.write("EMPTY");
434: } else {
435: if (indentFirst)
436: indent(level, writer);
437: writer.write("(");
438: appendLineStringText(polygon.getExteriorRing(), level,
439: false, writer);
440: for (int i = 0; i < polygon.getNumInteriorRing(); i++) {
441: writer.write(", ");
442: appendLineStringText(polygon.getInteriorRingN(i),
443: level + 1, true, writer);
444: }
445: writer.write(")");
446: }
447: }
448:
449: /**
450: * Converts a <code>MultiPoint</code> to <MultiPoint Text> format,
451: * then appends it to the writer.
452: *
453: * @param multiPoint
454: * the <code>MultiPoint</code> to process
455: * @param writer
456: * the output writer to append to
457: */
458: private void appendMultiPointText(MultiPoint multiPoint, int level,
459: Writer writer) throws IOException {
460: if (multiPoint.isEmpty()) {
461: writer.write("EMPTY");
462: } else {
463: writer.write("(");
464: for (int i = 0; i < multiPoint.getNumGeometries(); i++) {
465: if (i > 0) {
466: writer.write(", ");
467: }
468: appendCoordinate(((Point) multiPoint.getGeometryN(i))
469: .getCoordinate(), writer, multiPoint
470: .getPrecisionModel());
471: }
472: writer.write(")");
473: }
474: }
475:
476: /**
477: * Converts a <code>MultiLineString</code> to <MultiLineString Text>
478: * format, then appends it to the writer.
479: *
480: * @param multiLineString
481: * the <code>MultiLineString</code> to process
482: * @param writer
483: * the output writer to append to
484: */
485: private void appendMultiLineStringText(
486: MultiLineString multiLineString, int level,
487: boolean indentFirst, Writer writer) throws IOException {
488: if (multiLineString.isEmpty()) {
489: writer.write("EMPTY");
490: } else {
491: int level2 = level;
492: boolean doIndent = indentFirst;
493: writer.write("(");
494: for (int i = 0; i < multiLineString.getNumGeometries(); i++) {
495: if (i > 0) {
496: writer.write(", ");
497: level2 = level + 1;
498: doIndent = true;
499: }
500: appendLineStringText((LineString) multiLineString
501: .getGeometryN(i), level2, doIndent, writer);
502: }
503: writer.write(")");
504: }
505: }
506:
507: /**
508: * Converts a <code>MultiPolygon</code> to <MultiPolygon Text>
509: * format, then appends it to the writer.
510: *
511: * @param multiPolygon
512: * the <code>MultiPolygon</code> to process
513: * @param writer
514: * the output writer to append to
515: */
516: private void appendMultiPolygonText(MultiPolygon multiPolygon,
517: int level, Writer writer) throws IOException {
518: if (multiPolygon.isEmpty()) {
519: writer.write("EMPTY");
520: } else {
521: int level2 = level;
522: boolean doIndent = false;
523: writer.write("(");
524: for (int i = 0; i < multiPolygon.getNumGeometries(); i++) {
525: if (i > 0) {
526: writer.write(", ");
527: level2 = level + 1;
528: doIndent = true;
529: }
530: appendPolygonText((Polygon) multiPolygon
531: .getGeometryN(i), level2, doIndent, writer);
532: }
533: writer.write(")");
534: }
535: }
536:
537: /**
538: * Converts a <code>GeometryCollection</code> to
539: * <GeometryCollectionText> format, then appends it to the writer.
540: *
541: * @param geometryCollection
542: * the <code>GeometryCollection</code> to process
543: * @param writer
544: * the output writer to append to
545: */
546: private void appendGeometryCollectionText(
547: GeometryCollection geometryCollection, int level,
548: Writer writer) throws IOException {
549: if (geometryCollection.isEmpty()) {
550: writer.write("EMPTY");
551: } else {
552: int level2 = level;
553: writer.write("(");
554: for (int i = 0; i < geometryCollection.getNumGeometries(); i++) {
555: if (i > 0) {
556: writer.write(", ");
557: level2 = level + 1;
558: }
559: appendGeometryTaggedText(geometryCollection
560: .getGeometryN(i), level2, writer);
561: }
562: writer.write(")");
563: }
564: }
565:
566: private void indent(int level, Writer writer) throws IOException {
567: if (!isFormatted || level <= 0)
568: return;
569: writer.write("\n");
570: writer.write(stringOfChar(' ', INDENT * level));
571: }
572:
573: }
|