/*
* Copyright (c) 2016 Vivid Solutions.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
*
* http://www.eclipse.org/org/documents/edl-v10.php.
*/
package org.locationtech.jts.io.gml2;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.geom.MultiPoint;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.util.Assert;
/**
* Writes {@link Geometry}s as XML fragments in GML2 format.
* Allows specifying the XML prefix, namespace and srsName
* of the emitted GML.
* Also allows adding custom root elements
* to support GML extensions such as KML.
* With appropriate settings for prefix (none) and custom root elements
* this class can be used to write out geometry in KML format.
* <p>
* An example of the output that can be generated is:
*
* <pre>
* <gml:LineString xmlns:gml='http://www.opengis.net/gml' srsName='foo'>
* <gml:coordinates>
* 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
* </gml:coordinates>
* </gml:LineString>
* </pre>
*
* <p>
* This class does not rely on any external XML libraries.
*
* @author David Zwiers, Vivid Solutions
* @author Martin Davis
*/
public class GMLWriter {
private final String INDENT = " ";
private int startingIndentIndex = 0;
private int maxCoordinatesPerLine = 10;
private boolean emitNamespace = false;
private boolean isRootTag = false;
private String prefix = GMLConstants.GML_PREFIX;
private String namespace = GMLConstants.GML_NAMESPACE;
private String srsName = null;
private String[] customElements = null;
/**
* Creates a writer which outputs GML with default settings.
* The defaults are:
* <ul>
* <li>the namespace prefix is <tt>gml:</tt>
* <li>no namespace prefix declaration is written
* <li>no <tt>srsName</tt> attribute is written
* </ul>
*/
public GMLWriter() {
}
/**
* Creates a writer which may emit the GML namespace prefix
* declaration in the geometry root element.
*
* @param emitNamespace true if the GML namespace prefix declaration should be written
* in the geometry root element
*/
public GMLWriter(boolean emitNamespace) {
this.setNamespace(emitNamespace);
}
/**
* Specifies the namespace prefix to write on each GML tag.
* A null or blank prefix may be used to indicate no prefix.
* <p>
* The default is to write <tt>gml:</tt> as the namespace prefix.
*
* @param prefix the namespace prefix to use (<tt>null</tt> or blank if none)
*/
public void setPrefix(String prefix) {
this.prefix = prefix;
}
/**
* Sets the value of the <tt>srsName</tt> attribute
* to be written into the root geometry tag.
* If the value is <tt>null</tt> or blank no srsName attribute will be written.
* The provided value must be a valid XML attribute value
* - it will not be XML-escaped.
* <p>
* The default is not to write the <tt>srsName</tt> attribute.
*
* @param srsName the srsName attribute value
*/
public void setSrsName(String srsName) {
this.srsName = srsName;
}
/**
* Determines whether a GML namespace declaration will be written in the
* opening tag of geometries. Useful in XML-aware environments which
* parse the geometries before use, such as XSLT.
*
* @param emitNamespace true if the GML namespace prefix declaration
* should be written in the root geometry element
*/
public void setNamespace(boolean emitNamespace) {
this.emitNamespace = emitNamespace;
}
/**
* Specifies a list of custom elements
* which are written after the opening tag
* of the root element.
* The text contained in the string sequence should form valid XML markup.
* The specified strings are written one per line immediately after
* the root geometry tag line.
* <p>
* For instance, this is useful for adding KML-specific geometry parameters
* such as <tt><extrude></tt>
*
* @param customElements a list of the custom element strings, or null if none
*/
public void setCustomElements(String[] customElements) {
this.customElements = customElements;
}
/**
* Sets the starting column index for pretty printing
*
* @param indent
*/
public void setStartingIndentIndex(int indent) {
if (indent < 0)
indent = 0;
startingIndentIndex = indent;
}
/**
* Sets the number of coordinates printed per line.
*
* @param num
*/
public void setMaxCoordinatesPerLine(int num) {
if (num < 1)
throw new IndexOutOfBoundsException(
"Invalid coordinate count per line, must be > 0");
maxCoordinatesPerLine = num;
}
/**
* Writes a {@link Geometry} in GML2 format to a String.
*
* @param geom
* @return String GML2 Encoded Geometry
*/
public String write(Geometry geom)
{
StringWriter writer = new StringWriter();
try {
write(geom, writer);
}
catch (IOException ex) {
Assert.shouldNeverReachHere();
}
return writer.toString();
}
/**
* Writes a {@link Geometry} in GML2 format into a {@link Writer}.
*
* @param geom Geometry to encode
* @param writer Stream to encode to.
* @throws IOException
*/
public void write(Geometry geom, Writer writer) throws IOException {
write(geom, writer, startingIndentIndex);
}
private void write(Geometry geom, Writer writer, int level)
throws IOException
{
isRootTag = true;
if (geom instanceof Point) {
writePoint((Point) geom, writer, level);
} else if (geom instanceof LineString) {
writeLineString((LineString) geom, writer, level);
} else if (geom instanceof Polygon) {
writePolygon((Polygon) geom, writer, level);
} else if (geom instanceof MultiPoint) {
writeMultiPoint((MultiPoint) geom, writer, level);
} else if (geom instanceof MultiLineString) {
writeMultiLineString((MultiLineString) geom, writer, level);
} else if (geom instanceof MultiPolygon) {
writeMultiPolygon((MultiPolygon) geom, writer, level);
} else if (geom instanceof GeometryCollection) {
writeGeometryCollection((GeometryCollection) geom, writer,
startingIndentIndex);
} else {
throw new IllegalArgumentException("Unhandled geometry type: "
+ geom.getGeometryType());
}
writer.flush();
}
// <gml:Point><gml:coordinates>1195156.78946687,382069.533723461</gml:coordinates></gml:Point>
private void writePoint(Point p, Writer writer, int level) throws IOException {
startLine(level, writer);
startGeomTag(GMLConstants.GML_POINT, p, writer);
write(new Coordinate[] { p.getCoordinate() }, writer, level + 1);
startLine(level, writer);
endGeomTag(GMLConstants.GML_POINT, writer);
}
//<gml:LineString><gml:coordinates>1195123.37289257,381985.763974674 1195120.22369473,381964.660533343 1195118.14929823,381942.597718511</gml:coordinates></gml:LineString>
private void writeLineString(LineString ls, Writer writer, int level)
throws IOException {
startLine(level, writer);
startGeomTag(GMLConstants.GML_LINESTRING, ls, writer);
write(ls.getCoordinates(), writer, level + 1);
startLine(level, writer);
endGeomTag(GMLConstants.GML_LINESTRING, writer);
}
//<gml:LinearRing><gml:coordinates>1226890.26761027,1466433.47430292 1226880.59239079,1466427.03208053...></coordinates></gml:LinearRing>
private void writeLinearRing(LinearRing lr, Writer writer, int level)
throws IOException {
startLine(level, writer);
startGeomTag(GMLConstants.GML_LINEARRING, lr, writer);
write(lr.getCoordinates(), writer, level + 1);
startLine(level, writer);
endGeomTag(GMLConstants.GML_LINEARRING, writer);
}
private void writePolygon(Polygon p, Writer writer, int level)
throws IOException {
startLine(level, writer);
startGeomTag(GMLConstants.GML_POLYGON, p, writer);
startLine(level + 1, writer);
startGeomTag(GMLConstants.GML_OUTER_BOUNDARY_IS, null, writer);
writeLinearRing((LinearRing) p.getExteriorRing(), writer, level + 2);
startLine(level + 1, writer);
endGeomTag(GMLConstants.GML_OUTER_BOUNDARY_IS, writer);
for (int t = 0; t < p.getNumInteriorRing(); t++) {
startLine(level + 1, writer);
startGeomTag(GMLConstants.GML_INNER_BOUNDARY_IS, null, writer);
writeLinearRing((LinearRing) p.getInteriorRingN(t), writer, level + 2);
startLine(level + 1, writer);
endGeomTag(GMLConstants.GML_INNER_BOUNDARY_IS, writer);
}
startLine(level, writer);
endGeomTag(GMLConstants.GML_POLYGON, writer);
}
private void writeMultiPoint(MultiPoint mp, Writer writer, int level)
throws IOException {
startLine(level, writer);
startGeomTag(GMLConstants.GML_MULTI_POINT, mp, writer);
for (int t = 0; t < mp.getNumGeometries(); t++) {
startLine(level + 1, writer);
startGeomTag(GMLConstants.GML_POINT_MEMBER, null, writer);
writePoint((Point) mp.getGeometryN(t), writer, level + 2);
startLine(level + 1, writer);
endGeomTag(GMLConstants.GML_POINT_MEMBER, writer);
}
startLine(level, writer);
endGeomTag(GMLConstants.GML_MULTI_POINT, writer);
}
private void writeMultiLineString(MultiLineString mls, Writer writer,
int level) throws IOException {
startLine(level, writer);
startGeomTag(GMLConstants.GML_MULTI_LINESTRING, mls, writer);
for (int t = 0; t < mls.getNumGeometries(); t++) {
startLine(level + 1, writer);
startGeomTag(GMLConstants.GML_LINESTRING_MEMBER, null, writer);
writeLineString((LineString) mls.getGeometryN(t), writer, level + 2);
startLine(level + 1, writer);
endGeomTag(GMLConstants.GML_LINESTRING_MEMBER, writer);
}
startLine(level, writer);
endGeomTag(GMLConstants.GML_MULTI_LINESTRING, writer);
}
private void writeMultiPolygon(MultiPolygon mp, Writer writer, int level)
throws IOException {
startLine(level, writer);
startGeomTag(GMLConstants.GML_MULTI_POLYGON, mp, writer);
for (int t = 0; t < mp.getNumGeometries(); t++) {
startLine(level + 1, writer);
startGeomTag(GMLConstants.GML_POLYGON_MEMBER, null, writer);
writePolygon((Polygon) mp.getGeometryN(t), writer, level + 2);
startLine(level + 1, writer);
endGeomTag(GMLConstants.GML_POLYGON_MEMBER, writer);
}
startLine(level, writer);
endGeomTag(GMLConstants.GML_MULTI_POLYGON, writer);
}
private void writeGeometryCollection(GeometryCollection gc, Writer writer,
int level) throws IOException {
startLine(level, writer);
startGeomTag(GMLConstants.GML_MULTI_GEOMETRY, gc, writer);
for (int t = 0; t < gc.getNumGeometries(); t++) {
startLine(level + 1, writer);
startGeomTag(GMLConstants.GML_GEOMETRY_MEMBER, null, writer);
write(gc.getGeometryN(t), writer, level + 2);
startLine(level + 1, writer);
endGeomTag(GMLConstants.GML_GEOMETRY_MEMBER, writer);
}
startLine(level, writer);
endGeomTag(GMLConstants.GML_MULTI_GEOMETRY, writer);
}
private static final String coordinateSeparator = ",";
private static final String tupleSeparator = " ";
/**
* Takes a list of coordinates and converts it to GML.<br>
* 2d and 3d aware.
*
* @param coords array of coordinates
* @throws IOException
*/
private void write(Coordinate[] coords, Writer writer, int level)
throws IOException {
startLine(level, writer);
startGeomTag(GMLConstants.GML_COORDINATES, null, writer);
int dim = 2;
if (coords.length > 0) {
if (!(Double.isNaN(coords[0].z)))
dim = 3;
}
boolean isNewLine = true;
for (int i = 0; i < coords.length; i++) {
if (isNewLine) {
startLine(level + 1, writer);
isNewLine = false;
}
if (dim == 2) {
writer.write("" + coords[i].x);
writer.write(coordinateSeparator);
writer.write("" + coords[i].y);
} else if (dim == 3) {
writer.write("" + coords[i].x);
writer.write(coordinateSeparator);
writer.write("" + coords[i].y);
writer.write(coordinateSeparator);
writer.write("" + coords[i].z);
}
writer.write(tupleSeparator);
// break output lines to prevent them from getting too long
if ((i + 1) % maxCoordinatesPerLine == 0 && i < coords.length - 1) {
writer.write("\n");
isNewLine = true;
}
}
if (!isNewLine)
writer.write("\n");
startLine(level, writer);
endGeomTag(GMLConstants.GML_COORDINATES, writer);
}
private void startLine(int level, Writer writer) throws IOException {
for (int i = 0; i < level; i++)
writer.write(INDENT);
}
private void startGeomTag(String geometryName, Geometry g, Writer writer)
throws IOException {
writer.write("<"
+ ((prefix == null || "".equals(prefix)) ? "" : prefix + ":"));
writer.write(geometryName);
writeAttributes(g, writer);
writer.write(">\n");
writeCustomElements(g, writer);
isRootTag = false;
}
private void writeAttributes(Geometry geom, Writer writer) throws IOException {
if (geom == null)
return;
if (! isRootTag)
return;
if (emitNamespace) {
writer.write(" xmlns"
+ ((prefix == null || "".equals(prefix)) ? "" : ":"+prefix )
+ "='" + namespace + "'");
}
if (srsName != null && srsName.length() > 0) {
writer.write(" " + GMLConstants.GML_ATTR_SRSNAME + "='" + srsName + "'");
// MD - obsoleted
// writer.write(geom.getSRID() + "");
}
}
private void writeCustomElements(Geometry geom, Writer writer) throws IOException {
if (geom == null) return;
if (! isRootTag) return;
if (customElements == null) return;
for (int i = 0; i < customElements.length; i++) {
writer.write(customElements[i]);
writer.write("\n");
}
}
private void endGeomTag(String geometryName, Writer writer)
throws IOException {
writer.write("</" + prefix());
writer.write(geometryName);
writer.write(">\n");
}
private String prefix()
{
if (prefix == null || prefix.length() == 0)
return "";
return prefix + ":";
}
}