/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geowebcache.georss;
import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
import static org.geowebcache.georss.GeoRSSParsingUtils.consume;
import static org.geowebcache.georss.GeoRSSParsingUtils.nextTag;
import static org.geowebcache.georss.GeoRSSParsingUtils.text;
import java.util.ArrayList;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
/**
* Utility class to parse GML 3.1 geometries out of an {@link XMLStreamReader}
* <p>
* Dislaimer: the code on this class was adapted from LGPL licensed GeoTools WFS module's
* {@code XmlSimpleFeatureParser} class available <a href=
* "http://svn.osgeo.org/geotools/trunk/modules/unsupported/wfs/src/main/java/org/geotools/data/wfs/v1_1_0/parsers/XmlSimpleFeatureParser.java"
* >here</a>
* </p>
*
* @author Gabriel Roldan (TOPP)
* @version $Id$
*/
@SuppressWarnings("nls")
class GML31ParsingUtils {
private final GeometryFactory geomFac;
public static final class GML {
public static final String GML_NS_URI = "http://www.opengis.net/gml";
public static final QName Point = new QName(GML_NS_URI, "Point");
public static final QName LineString = new QName(GML_NS_URI, "LineString");
public static final QName Polygon = new QName(GML_NS_URI, "Polygon");
public static final QName MultiPoint = new QName(GML_NS_URI, "MultiPoint");
public static final QName MultiLineString = new QName(GML_NS_URI, "MultiLineString");
public static final QName MultiSurface = new QName(GML_NS_URI, "MultiSurface");
public static final QName MultiPolygon = new QName(GML_NS_URI, "MultiPolygon");
public static final QName pointMembers = new QName(GML_NS_URI, "pointMembers");
public static final QName pointMember = new QName(GML_NS_URI, "pointMember");
public static final QName lineStringMember = new QName(GML_NS_URI, "lineStringMember");
public static final QName surfaceMembers = new QName(GML_NS_URI, "surfaceMembers");
public static final QName surfaceMember = new QName(GML_NS_URI, "surfaceMember");
public static final QName polygonMember = new QName(GML_NS_URI, "polygonMember");
public static final QName exterior = new QName(GML_NS_URI, "exterior");
public static final QName interior = new QName(GML_NS_URI, "interior");
public static final QName LinearRing = new QName(GML_NS_URI, "LinearRing");
public static final QName pos = new QName(GML_NS_URI, "pos");
public static final QName posList = new QName(GML_NS_URI, "posList");
}
public GML31ParsingUtils() {
this(new GeometryFactory());
}
public GML31ParsingUtils(GeometryFactory gFac) {
this.geomFac = gFac;
}
/**
* <p>
* Precondition: reader cursor positioned on a geometry property (ej, {@code gml:Point}, etc)
* </p>
* <p>
* Postcondition: reader gets positioned at the end tag of the element it started parsing the
* geometry at
* </p>
*
* @return
* @throws XMLStreamException
*/
public Geometry parseGeometry(final XMLStreamReader reader) throws XMLStreamException {
reader.require(START_ELEMENT, GML.GML_NS_URI, null);
final QName startingGeometryTagName = reader.getName();
int dimension = crsDimension(reader, 2);
Geometry geom;
if (GML.Point.equals(startingGeometryTagName)) {
geom = parsePoint(reader, dimension);
} else if (GML.LineString.equals(startingGeometryTagName)) {
geom = parseLineString(reader, dimension);
} else if (GML.Polygon.equals(startingGeometryTagName)) {
geom = parsePolygon(reader, dimension);
} else if (GML.MultiPoint.equals(startingGeometryTagName)) {
geom = parseMultiPoint(reader, dimension);
} else if (GML.MultiLineString.equals(startingGeometryTagName)) {
geom = parseMultiLineString(reader, dimension);
} else if (GML.MultiSurface.equals(startingGeometryTagName)) {
geom = parseMultiSurface(reader, dimension);
} else if (GML.MultiPolygon.equals(startingGeometryTagName)) {
geom = parseMultiPolygon(reader, dimension);
} else {
throw new IllegalStateException("Unrecognized geometry element "
+ startingGeometryTagName);
}
reader.require(END_ELEMENT, startingGeometryTagName.getNamespaceURI(),
startingGeometryTagName.getLocalPart());
return geom;
}
/**
* Parses a MultiPoint.
* <p>
* Precondition: reader positioned at a {@link GML#MultiPoint MultiPoint} start tag
* </p>
* <p>
* Postcondition: reader positioned at the {@link GML#MultiPoint MultiPoint} end tag of the
* starting tag
* </p>
*
* @throws XMLStreamException
*/
private Geometry parseMultiPoint(XMLStreamReader reader, int dimension)
throws XMLStreamException {
Geometry geom;
nextTag(reader);
final QName memberTag = reader.getName();
List<Point> points = new ArrayList<Point>(4);
if (GML.pointMembers.equals(memberTag)) {
while (true) {
nextTag(reader);
if (END_ELEMENT == reader.getEventType()
&& GML.pointMembers.equals(reader.getName())) {
// we're done
break;
}
Point p = parsePoint(reader, dimension);
points.add(p);
}
nextTag(reader);
} else if (GML.pointMember.equals(memberTag)) {
while (true) {
nextTag(reader);
reader.require(START_ELEMENT, GML.GML_NS_URI, GML.Point.getLocalPart());
Point p = parsePoint(reader, dimension);
points.add(p);
nextTag(reader);
reader.require(END_ELEMENT, GML.GML_NS_URI, GML.pointMember.getLocalPart());
nextTag(reader);
if (END_ELEMENT == reader.getEventType() && GML.MultiPoint.equals(reader.getName())) {
// we're done
break;
}
}
}
reader.require(END_ELEMENT, GML.GML_NS_URI, GML.MultiPoint.getLocalPart());
geom = geomFac.createMultiPoint(points.toArray(new Point[points.size()]));
return geom;
}
/**
* Parses a MultiLineString.
* <p>
* Precondition: reader positioned at a {@link GML#MultiLineString MultiLineString} start tag
* </p>
* <p>
* Postcondition: reader positioned at the {@link GML#MultiLineString MultiLineString} end tag
* of the starting tag
* </p>
*
* @throws XMLStreamException
*/
private MultiLineString parseMultiLineString(XMLStreamReader reader, int dimension)
throws XMLStreamException {
MultiLineString geom;
reader.require(START_ELEMENT, GML.GML_NS_URI, GML.MultiLineString.getLocalPart());
List<LineString> lines = new ArrayList<LineString>(2);
while (true) {
nextTag(reader);
if (END_ELEMENT == reader.getEventType()
&& GML.MultiLineString.equals(reader.getName())) {
// we're done
break;
}
reader.require(START_ELEMENT, GML.GML_NS_URI, GML.lineStringMember.getLocalPart());
nextTag(reader);
reader.require(START_ELEMENT, GML.GML_NS_URI, GML.LineString.getLocalPart());
LineString line = parseLineString(reader, dimension);
lines.add(line);
nextTag(reader);
reader.require(END_ELEMENT, GML.GML_NS_URI, GML.lineStringMember.getLocalPart());
}
reader.require(END_ELEMENT, GML.GML_NS_URI, GML.MultiLineString.getLocalPart());
geom = geomFac.createMultiLineString(lines.toArray(new LineString[lines.size()]));
return geom;
}
/**
* Parses a MultiPolygon out of a MultiSurface element (because our geometry model only supports
* MultiPolygon).
* <p>
* Precondition: reader positioned at a {@link GML#MultiSurface MultiSurface} start tag
* </p>
* <p>
* Postcondition: reader positioned at the {@link GML#MultiSurface MultiSurface} end tag of the
* starting tag
* </p>
*
* @param reader
*/
private Geometry parseMultiSurface(XMLStreamReader reader, int dimension)
throws XMLStreamException {
Geometry geom;
nextTag(reader);
final QName memberTag = reader.getName();
List<Polygon> polygons = new ArrayList<Polygon>(2);
if (GML.surfaceMembers.equals(memberTag)) {
while (true) {
nextTag(reader);
if (END_ELEMENT == reader.getEventType()
&& GML.surfaceMembers.equals(reader.getName())) {
// we're done
break;
}
Polygon p = parsePolygon(reader, dimension);
polygons.add(p);
}
nextTag(reader);
} else if (GML.surfaceMember.equals(memberTag)) {
while (true) {
nextTag(reader);
Polygon p = parsePolygon(reader, dimension);
polygons.add(p);
nextTag(reader);
reader.require(END_ELEMENT, GML.GML_NS_URI, GML.surfaceMember.getLocalPart());
nextTag(reader);
if (END_ELEMENT == reader.getEventType()
&& GML.MultiSurface.equals(reader.getName())) {
// we're done
break;
}
}
}
reader.require(END_ELEMENT, GML.GML_NS_URI, GML.MultiSurface.getLocalPart());
geom = geomFac.createMultiPolygon(polygons.toArray(new Polygon[polygons.size()]));
return geom;
}
private Geometry parseMultiPolygon(XMLStreamReader reader, int dimension)
throws XMLStreamException {
reader.require(START_ELEMENT, GML.GML_NS_URI, GML.MultiPolygon.getLocalPart());
Geometry geom;
List<Polygon> polygons = new ArrayList<Polygon>(2);
nextTag(reader);
while (true) {
reader.require(START_ELEMENT, GML.GML_NS_URI, GML.polygonMember.getLocalPart());
nextTag(reader);
reader.require(START_ELEMENT, GML.GML_NS_URI, GML.Polygon.getLocalPart());
Polygon p = parsePolygon(reader, dimension);
polygons.add(p);
nextTag(reader);
reader.require(END_ELEMENT, GML.GML_NS_URI, GML.polygonMember.getLocalPart());
nextTag(reader);
if (END_ELEMENT == reader.getEventType() && GML.MultiPolygon.equals(reader.getName())) {
// we're done
break;
}
}
reader.require(END_ELEMENT, GML.GML_NS_URI, GML.MultiPolygon.getLocalPart());
geom = geomFac.createMultiPolygon(polygons.toArray(new Polygon[polygons.size()]));
return geom;
}
/**
* Parses a polygon.
* <p>
* Precondition: reader positioned at a {@link GML#Polygon Polygon} start tag
* </p>
* <p>
* Postcondition: reader positioned at the {@link GML#Polygon Polygon} end tag of the starting
* tag
* </p>
*
* @param reader
*
* @param dimension
* @return
* @throws XMLStreamException
*/
private Polygon parsePolygon(XMLStreamReader reader, int dimension) throws XMLStreamException {
Polygon geom;
LinearRing shell;
List<LinearRing> holes = null;
nextTag(reader);
reader.require(START_ELEMENT, GML.GML_NS_URI, GML.exterior.getLocalPart());
nextTag(reader);
shell = parseLinearRing(reader, dimension);
nextTag(reader);
reader.require(END_ELEMENT, GML.GML_NS_URI, GML.exterior.getLocalPart());
nextTag(reader);
if (GML.interior.equals(reader.getName())) {
// parse interior rings
holes = new ArrayList<LinearRing>(2);
while (true) {
nextTag(reader);
LinearRing hole = parseLinearRing(reader, dimension);
holes.add(hole);
nextTag(reader);
reader.require(END_ELEMENT, GML.GML_NS_URI, GML.interior.getLocalPart());
nextTag(reader);
if (END_ELEMENT == reader.getEventType()) {
// we're done
reader.require(END_ELEMENT, GML.GML_NS_URI, GML.Polygon.getLocalPart());
break;
}
}
}
reader.require(END_ELEMENT, GML.GML_NS_URI, GML.Polygon.getLocalPart());
LinearRing[] holesArray = null;
if (holes != null) {
holesArray = holes.toArray(new LinearRing[holes.size()]);
}
geom = geomFac.createPolygon(shell, holesArray);
return geom;
}
private LinearRing parseLinearRing(final XMLStreamReader reader, final int dimension)
throws XMLStreamException {
reader.require(START_ELEMENT, GML.GML_NS_URI, GML.LinearRing.getLocalPart());
nextTag(reader);
QName tagName = reader.getName();
Coordinate[] shellCoords;
if (GML.pos.equals(tagName)) {
Coordinate[] point;
List<Coordinate> coords = new ArrayList<Coordinate>();
int eventType;
do {
point = parseCoordListContent(reader, dimension);
coords.add(point[0]);
nextTag(reader);
tagName = reader.getName();
eventType = reader.getEventType();
} while (eventType == START_ELEMENT && GML.pos.equals(tagName));
shellCoords = coords.toArray(new Coordinate[coords.size()]);
} else if (GML.posList.equals(tagName)) {
// reader.require(START_ELEMENT, GML.NAMESPACE,
// GML.posList.getLocalPart());
// crs = crs(reader, crs);
shellCoords = parseCoordListContent(reader, dimension);
nextTag(reader);
} else {
throw new IllegalStateException("Expected posList or pos inside LinearRing: " + tagName);
}
reader.require(END_ELEMENT, GML.GML_NS_URI, GML.LinearRing.getLocalPart());
LinearRing linearRing = geomFac.createLinearRing(shellCoords);
// linearRing.setUserData(crs);
return linearRing;
}
private LineString parseLineString(XMLStreamReader reader, int dimension)
throws XMLStreamException {
LineString geom;
nextTag(reader);
reader.require(START_ELEMENT, GML.GML_NS_URI, GML.posList.getLocalPart());
// crs = crs(reader, crs);
Coordinate[] coords = parseCoordListContent(reader, dimension);
geom = geomFac.createLineString(coords);
// geom.setUserData(crs);
nextTag(reader);
reader.require(END_ELEMENT, GML.GML_NS_URI, GML.LineString.getLocalPart());
return geom;
}
private Point parsePoint(XMLStreamReader reader, int dimension) throws XMLStreamException {
reader.require(START_ELEMENT, GML.GML_NS_URI, GML.Point.getLocalPart());
Point geom;
nextTag(reader);
reader.require(START_ELEMENT, GML.GML_NS_URI, GML.pos.getLocalPart());
// crs = crs(reader, crs);
Coordinate[] coords = parseCoordListContent(reader, dimension);
geom = geomFac.createPoint(coords[0]);
// geom.setUserData(crs);
consume(reader, GML.Point);
reader.require(END_ELEMENT, GML.GML_NS_URI, GML.Point.getLocalPart());
return geom;
}
private int crsDimension(final XMLStreamReader reader, final int defaultValue) {
String srsDimension = reader.getAttributeValue(null, "srsDimension");
if (srsDimension == null) {
return defaultValue;
}
int dimension = Integer.valueOf(srsDimension);
return dimension;
}
private Coordinate[] parseCoordListContent(final XMLStreamReader reader, int dimension)
throws XMLStreamException {
reader.require(START_ELEMENT, null, null);
final QName tagName = reader.getName();
// we might be on a posList tag with srsDimension defined
dimension = crsDimension(reader, dimension);
String rawTextValue = text(reader);
Coordinate[] coords = toCoordList(rawTextValue, dimension);
consume(reader, tagName);
return coords;
}
private Coordinate[] toCoordList(String rawTextValue, final int dimension) {
rawTextValue = rawTextValue.trim();
rawTextValue = rawTextValue.replaceAll("\n", " ");
rawTextValue = rawTextValue.replaceAll("\r", " ");
String[] split = rawTextValue.trim().split(" +");
final int ordinatesLength = split.length;
if (ordinatesLength % dimension != 0) {
throw new IllegalArgumentException("Number of ordinates (" + ordinatesLength
+ ") does not match crs dimension: " + dimension);
}
final int nCoords = ordinatesLength / dimension;
Coordinate[] coords = new Coordinate[nCoords];
Coordinate coord;
int currCoordIdx = 0;
double x, y, z;
for (int i = 0; i < ordinatesLength; i += dimension) {
x = Double.valueOf(split[i]);
y = Double.valueOf(split[i + 1]);
if (dimension > 2) {
z = Double.valueOf(split[i + 2]);
coord = new Coordinate(x, y, z);
} else {
coord = new Coordinate(x, y);
}
coords[currCoordIdx] = coord;
currCoordIdx++;
}
return coords;
}
}