Class GMLWriter

  1 /*
  2  * Copyright (c) 2016 Vivid Solutions.
  3  *
  4  * All rights reserved. This program and the accompanying materials
  5  * are made available under the terms of the Eclipse Public License 2.0
  6  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
  7  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
  8  * and the Eclipse Distribution License is available at
  9  *
 10  * http://www.eclipse.org/org/documents/edl-v10.php.
 11  */
 12 package org.locationtech.jts.io.gml2;
 13  
 14 import java.io.IOException;
 15 import java.io.StringWriter;
 16 import java.io.Writer;
 17  
 18 import org.locationtech.jts.geom.Coordinate;
 19 import org.locationtech.jts.geom.Geometry;
 20 import org.locationtech.jts.geom.GeometryCollection;
 21 import org.locationtech.jts.geom.LineString;
 22 import org.locationtech.jts.geom.LinearRing;
 23 import org.locationtech.jts.geom.MultiLineString;
 24 import org.locationtech.jts.geom.MultiPoint;
 25 import org.locationtech.jts.geom.MultiPolygon;
 26 import org.locationtech.jts.geom.Point;
 27 import org.locationtech.jts.geom.Polygon;
 28 import org.locationtech.jts.util.Assert;
 29  
 30  
 31 /**
 32  * Writes {@link Geometry}s as XML fragments in GML2 format.
 33  * Allows specifying the XML prefix, namespace and srsName
 34  * of the emitted GML.
 35  * Also allows adding custom root elements
 36  * to support GML extensions such as KML. 
 37  * With appropriate settings for prefix (none) and custom root elements
 38  * this class can be used to write out geometry in KML format.
 39  * <p>
 40  * An example of the output that can be generated is:
 41  * 
 42  * <pre>
 43  * <gml:LineString xmlns:gml='http://www.opengis.net/gml' srsName='foo'>
 44  *   <gml:coordinates>
 45  *     6.03,8.17 7.697,6.959 8.333,5.0 7.697,3.041 6.03,1.83 3.97,1.83 2.303,3.041 1.667,5.0 2.303,6.959 3.97,8.17 
 46  *   </gml:coordinates>
 47  * </gml:LineString>
 48  * </pre>
 49  * 
 50  * <p>
 51  * This class does not rely on any external XML libraries. 
 52  *
 53  * @author David Zwiers, Vivid Solutions 
 54  * @author Martin Davis 
 55  */
 56 public class GMLWriter {
 57     private final String INDENT = "  ";
 58  
 59     private int startingIndentIndex = 0;
 60  
 61     private int maxCoordinatesPerLine = 10;
 62  
 63     private boolean emitNamespace = false;
 64  
 65     private boolean isRootTag = false;
 66  
 67     private String prefix = GMLConstants.GML_PREFIX;
 68     private String namespace = GMLConstants.GML_NAMESPACE;
 69     private String srsName = null;
 70     
 71     private String[] customElements = null;
 72     
 73     /**
 74      * Creates a writer which outputs GML with default settings.
 75      * The defaults are:
 76      * <ul>
 77      * <li>the namespace prefix is <tt>gml:</tt>
 78      * <li>no namespace prefix declaration is written
 79      * <li>no <tt>srsName</tt> attribute is written
 80      * </ul>
 81      */
 82     public GMLWriter() {
 83     }
 84  
 85     /**
 86      * Creates a writer which may emit the GML namespace prefix 
 87      * declaration in the geometry root element.
 88      * 
 89      * @param emitNamespace true if the GML namespace prefix declaration should be written
 90      *  in the geometry root element
 91      */
 92     public GMLWriter(boolean emitNamespace) {
 93         this.setNamespace(emitNamespace);
 94     }
 95  
 96     /**
 97      * Specifies the namespace prefix to write on each GML tag. 
 98      * A null or blank prefix may be used to indicate no prefix.
 99      * <p>
100      * The default is to write <tt>gml:</tt> as the namespace prefix.
101      * 
102      * @param prefix the namespace prefix to use (<tt>null</tt> or blank if none)
103      */
104     public void setPrefix(String prefix) {
105         this.prefix = prefix;
106     }
107  
108     /**
109      * Sets the value of the <tt>srsName</tt> attribute 
110      * to be written into the root geometry tag.
111      * If the value is <tt>null</tt> or blank no srsName attribute will be written.
112      * The provided value must be a valid XML attribute value 
113      * - it will not be XML-escaped.
114      * <p>
115      * The default is not to write the <tt>srsName</tt> attribute.
116      * 
117      * @param srsName the srsName attribute value
118      */
119     public void setSrsName(String srsName) {
120         this.srsName = srsName;
121     }
122  
123     /**
124      * Determines whether a GML namespace declaration will be written in the
125      * opening tag of geometries.  Useful in XML-aware environments which 
126      * parse the geometries before use, such as XSLT.
127      * 
128      * @param emitNamespace true if the GML namespace prefix declaration 
129      * should be written in the root geometry element
130      */
131     public void setNamespace(boolean emitNamespace) {
132         this.emitNamespace = emitNamespace;
133     }
134  
135     /**
136      * Specifies a list of custom elements
137      * which are written after the opening tag
138      * of the root element.
139      * The text contained in the string sequence should form valid XML markup.
140      * The specified strings are written one per line immediately after
141      * the root geometry tag line.
142      * <p>
143      * For instance, this is useful for adding KML-specific geometry parameters
144      * such as <tt><extrude></tt>
145      * 
146      * @param customElements a list of the custom element strings, or null if none
147      */
148     public void setCustomElements(String[] customElements) {
149         this.customElements = customElements;
150     }
151  
152     /**
153      * Sets the starting column index for pretty printing
154      * 
155      * @param indent
156      */
157     public void setStartingIndentIndex(int indent) {
158         if (indent < 0)
159             indent = 0;
160         startingIndentIndex = indent;
161     }
162  
163     /**
164      * Sets the number of coordinates printed per line. 
165      * 
166      * @param num
167      */
168     public void setMaxCoordinatesPerLine(int num) {
169         if (num < 1)
170             throw new IndexOutOfBoundsException(
171                     "Invalid coordinate count per line, must be > 0");
172         maxCoordinatesPerLine = num;
173     }
174  
175     /**
176      * Writes a {@link Geometry} in GML2 format to a String.
177      * 
178      * @param geom
179      * @return String GML2 Encoded Geometry
180      */
181     public String write(Geometry geom) 
182     {
183         StringWriter writer = new StringWriter();
184         try {
185             write(geom, writer);
186         }
187     catch (IOException ex) {
188       Assert.shouldNeverReachHere();
189     }
190         return writer.toString();
191     }
192  
193     /**
194      * Writes a {@link Geometry} in GML2 format into a {@link Writer}.
195      * 
196      * @param geom Geometry to encode
197      * @param writer Stream to encode to.
198      * @throws IOException 
199      */
200     public void write(Geometry geom, Writer writer) throws IOException {
201         write(geom, writer, startingIndentIndex);
202     }
203  
204     private void write(Geometry geom, Writer writer, int level)
205             throws IOException 
206             {
207         isRootTag = true;
208         if (geom instanceof Point) {
209             writePoint((Point) geom, writer, level);
210         } else if (geom instanceof LineString) {
211             writeLineString((LineString) geom, writer, level);
212         } else if (geom instanceof Polygon) {
213             writePolygon((Polygon) geom, writer, level);
214         } else if (geom instanceof MultiPoint) {
215             writeMultiPoint((MultiPoint) geom, writer, level);
216         } else if (geom instanceof MultiLineString) {
217             writeMultiLineString((MultiLineString) geom, writer, level);
218         } else if (geom instanceof MultiPolygon) {
219             writeMultiPolygon((MultiPolygon) geom, writer, level);
220         } else if (geom instanceof GeometryCollection) {
221             writeGeometryCollection((GeometryCollection) geom, writer,
222                     startingIndentIndex);
223         } else {
224             throw new IllegalArgumentException("Unhandled geometry type: "
225                     + geom.getGeometryType());
226         }
227         writer.flush();
228     }
229  
230     // <gml:Point><gml:coordinates>1195156.78946687,382069.533723461</gml:coordinates></gml:Point>
231     private void writePoint(Point p, Writer writer, int level) throws IOException {
232         startLine(level, writer);
233         startGeomTag(GMLConstants.GML_POINT, p, writer);
234  
235         write(new Coordinate[] { p.getCoordinate() }, writer, level + 1);
236  
237         startLine(level, writer);
238         endGeomTag(GMLConstants.GML_POINT, writer);
239     }
240  
241     //<gml:LineString><gml:coordinates>1195123.37289257,381985.763974674 1195120.22369473,381964.660533343 1195118.14929823,381942.597718511</gml:coordinates></gml:LineString>
242     private void writeLineString(LineString ls, Writer writer, int level)
243             throws IOException {
244         startLine(level, writer);
245         startGeomTag(GMLConstants.GML_LINESTRING, ls, writer);
246  
247         write(ls.getCoordinates(), writer, level + 1);
248  
249         startLine(level, writer);
250         endGeomTag(GMLConstants.GML_LINESTRING, writer);
251     }
252  
253     //<gml:LinearRing><gml:coordinates>1226890.26761027,1466433.47430292 1226880.59239079,1466427.03208053...></coordinates></gml:LinearRing>
254     private void writeLinearRing(LinearRing lr, Writer writer, int level)
255             throws IOException {
256         startLine(level, writer);
257         startGeomTag(GMLConstants.GML_LINEARRING, lr, writer);
258  
259         write(lr.getCoordinates(), writer, level + 1);
260  
261         startLine(level, writer);
262         endGeomTag(GMLConstants.GML_LINEARRING, writer);
263     }
264  
265     private void writePolygon(Polygon p, Writer writer, int level)
266             throws IOException {
267         startLine(level, writer);
268         startGeomTag(GMLConstants.GML_POLYGON, p, writer);
269  
270         startLine(level + 1, writer);
271         startGeomTag(GMLConstants.GML_OUTER_BOUNDARY_IS, null, writer);
272  
273         writeLinearRing(p.getExteriorRing(), writer, level + 2);
274  
275         startLine(level + 1, writer);
276         endGeomTag(GMLConstants.GML_OUTER_BOUNDARY_IS, writer);
277  
278         for (int t = 0; t < p.getNumInteriorRing(); t++) {
279             startLine(level + 1, writer);
280             startGeomTag(GMLConstants.GML_INNER_BOUNDARY_IS, null, writer);
281  
282             writeLinearRing(p.getInteriorRingN(t), writer, level + 2);
283  
284             startLine(level + 1, writer);
285             endGeomTag(GMLConstants.GML_INNER_BOUNDARY_IS, writer);
286         }
287  
288         startLine(level, writer);
289         endGeomTag(GMLConstants.GML_POLYGON, writer);
290     }
291  
292     private void writeMultiPoint(MultiPoint mp, Writer writer, int level)
293             throws IOException {
294         startLine(level, writer);
295         startGeomTag(GMLConstants.GML_MULTI_POINT, mp, writer);
296  
297         for (int t = 0; t < mp.getNumGeometries(); t++) {
298             startLine(level + 1, writer);
299             startGeomTag(GMLConstants.GML_POINT_MEMBER, null, writer);
300  
301             writePoint((Point) mp.getGeometryN(t), writer, level + 2);
302  
303             startLine(level + 1, writer);
304             endGeomTag(GMLConstants.GML_POINT_MEMBER, writer);
305         }
306         startLine(level, writer);
307         endGeomTag(GMLConstants.GML_MULTI_POINT, writer);
308     }
309  
310     private void writeMultiLineString(MultiLineString mls, Writer writer,
311             int level) throws IOException {
312         startLine(level, writer);
313         startGeomTag(GMLConstants.GML_MULTI_LINESTRING, mls, writer);
314  
315         for (int t = 0; t < mls.getNumGeometries(); t++) {
316             startLine(level + 1, writer);
317             startGeomTag(GMLConstants.GML_LINESTRING_MEMBER, null, writer);
318  
319             writeLineString((LineString) mls.getGeometryN(t), writer, level + 2);
320  
321             startLine(level + 1, writer);
322             endGeomTag(GMLConstants.GML_LINESTRING_MEMBER, writer);
323         }
324         startLine(level, writer);
325         endGeomTag(GMLConstants.GML_MULTI_LINESTRING, writer);
326     }
327  
328     private void writeMultiPolygon(MultiPolygon mp, Writer writer, int level)
329             throws IOException {
330         startLine(level, writer);
331         startGeomTag(GMLConstants.GML_MULTI_POLYGON, mp, writer);
332  
333         for (int t = 0; t < mp.getNumGeometries(); t++) {
334             startLine(level + 1, writer);
335             startGeomTag(GMLConstants.GML_POLYGON_MEMBER, null, writer);
336  
337             writePolygon((Polygon) mp.getGeometryN(t), writer, level + 2);
338  
339             startLine(level + 1, writer);
340             endGeomTag(GMLConstants.GML_POLYGON_MEMBER, writer);
341         }
342         startLine(level, writer);
343         endGeomTag(GMLConstants.GML_MULTI_POLYGON, writer);
344     }
345  
346     private void writeGeometryCollection(GeometryCollection gc, Writer writer,
347             int level) throws IOException {
348         startLine(level, writer);
349         startGeomTag(GMLConstants.GML_MULTI_GEOMETRY, gc, writer);
350  
351         for (int t = 0; t < gc.getNumGeometries(); t++) {
352             startLine(level + 1, writer);
353             startGeomTag(GMLConstants.GML_GEOMETRY_MEMBER, null, writer);
354  
355             write(gc.getGeometryN(t), writer, level + 2);
356  
357             startLine(level + 1, writer);
358             endGeomTag(GMLConstants.GML_GEOMETRY_MEMBER, writer);
359         }
360         startLine(level, writer);
361         endGeomTag(GMLConstants.GML_MULTI_GEOMETRY, writer);
362     }
363  
364     private static final String coordinateSeparator = ",";
365  
366     private static final String tupleSeparator = " ";
367  
368     /**
369      * Takes a list of coordinates and converts it to GML.<br>
370      * 2d and 3d aware.
371      * 
372      * @param coords array of coordinates
373      * @throws IOException 
374      */
375     private void write(Coordinate[] coords, Writer writer, int level)
376             throws IOException {
377         startLine(level, writer);
378         startGeomTag(GMLConstants.GML_COORDINATES, null, writer);
379  
380         int dim = 2;
381  
382         if (coords.length > 0) {
383             if (!(Double.isNaN(coords[0].getZ())))
384                 dim = 3;
385         }
386  
387         boolean isNewLine = true;
388         for (int i = 0; i < coords.length; i++) {
389             if (isNewLine) {
390                 startLine(level + 1, writer);
391                 isNewLine = false;
392             }
393             if (dim == 2) {
394                 writer.write("" + coords[i].x);
395                 writer.write(coordinateSeparator);
396                 writer.write("" + coords[i].y);
397             } else if (dim == 3) {
398                 writer.write("" + coords[i].x);
399                 writer.write(coordinateSeparator);
400                 writer.write("" + coords[i].y);
401                 writer.write(coordinateSeparator);
402                 writer.write("" + coords[i].getZ());
403             }
404             writer.write(tupleSeparator);
405  
406             // break output lines to prevent them from getting too long
407             if ((i + 1) % maxCoordinatesPerLine == 0 && i < coords.length - 1) {
408                 writer.write("\n");
409                 isNewLine = true;
410             }
411         }
412         if (!isNewLine)
413             writer.write("\n");
414  
415         startLine(level, writer);
416         endGeomTag(GMLConstants.GML_COORDINATES, writer);
417     }
418  
419     private void startLine(int level, Writer writer) throws IOException {
420         for (int i = 0; i < level; i++)
421             writer.write(INDENT);
422     }
423  
424     private void startGeomTag(String geometryName, Geometry g, Writer writer)
425             throws IOException {
426         writer.write("<"
427                 + ((prefix == null || "".equals(prefix)) ? "" : prefix + ":"));
428         writer.write(geometryName);
429         writeAttributes(g, writer);
430         writer.write(">\n");
431         writeCustomElements(g, writer);
432         isRootTag = false;
433     }
434  
435     private void writeAttributes(Geometry geom, Writer writer) throws IOException {
436         if (geom == null)
437             return;
438         if (! isRootTag)
439             return;
440         
441         if (emitNamespace) {
442             writer.write(" xmlns"
443                     + ((prefix == null || "".equals(prefix)) ? "" : ":"+prefix )
444                     + "='" + namespace + "'");
445         }
446         if (srsName != null && srsName.length() > 0) {
447             writer.write(" " + GMLConstants.GML_ATTR_SRSNAME + "='" + srsName + "'");
448             // MD - obsoleted
449 //            writer.write(geom.getSRID() + "");
450         }
451     }
452  
453     private void writeCustomElements(Geometry geom, Writer writer) throws IOException {
454         if (geom == null)            return;
455         if (! isRootTag)            return;
456         if (customElements == nullreturn;
457         
458         for (int i = 0; i < customElements.length; i++) {
459             writer.write(customElements[i]);
460             writer.write("\n");
461         }
462     }
463     
464     private void endGeomTag(String geometryName, Writer writer)
465             throws IOException {
466         writer.write("</" + prefix());
467         writer.write(geometryName);
468         writer.write(">\n");
469     }
470     
471     private String prefix()
472     {
473         if (prefix == null || prefix.length() == 0)
474             return "";
475         return prefix + ":";
476     }
477 }
478