/**
* Copyright (C) 2008 - 2014 52°North Initiative for Geospatial Open Source
* Software GmbH
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* If the program is linked with libraries which are licensed under one of
* the following licenses, the combination of the program with the linked
* library is not considered a "derivative work" of the program:
*
* - Apache License, version 2.0
* - Apache Software License, version 1.0
* - GNU Lesser General Public License, version 3
* - Mozilla Public License, versions 1.0, 1.1 and 2.0
* - Common Development and Distribution License (CDDL), version 1.0
*
* Therefore the distribution of the program linked with libraries licensed
* under the aforementioned licenses, is permitted by the copyright holders
* if the distribution is compliant with both the GNU General Public
* icense version 2 and the aforementioned licenses.
*
* This program 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 General
* Public License for more details.
*/
package org.n52.ses.io.parser;
import java.util.List;
import net.opengis.gml.x32.AbstractRingPropertyType;
import net.opengis.gml.x32.AbstractRingType;
import net.opengis.gml.x32.CoordinatesType;
import net.opengis.gml.x32.DirectPositionListType;
import net.opengis.gml.x32.DirectPositionType;
import net.opengis.gml.x32.EnvelopeDocument;
import net.opengis.gml.x32.EnvelopeType;
import net.opengis.gml.x32.GeodesicStringDocument;
import net.opengis.gml.x32.GeodesicStringType;
import net.opengis.gml.x32.LineStringDocument;
import net.opengis.gml.x32.LineStringType;
import net.opengis.gml.x32.LinearRingType;
import net.opengis.gml.x32.PointDocument;
import net.opengis.gml.x32.PointType;
import net.opengis.gml.x32.PolygonDocument;
import net.opengis.gml.x32.PolygonType;
import net.opengis.gml.x32.PosDocument;
import net.opengis.gml.x32.TimeInstantDocument;
import net.opengis.gml.x32.TimeInstantType;
import net.opengis.gml.x32.TimePeriodDocument;
import net.opengis.gml.x32.TimePeriodType;
import org.apache.muse.util.xml.XmlUtils;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.joda.time.DateTime;
import org.n52.oxf.conversion.gml32.geometry.GeometryWithInterpolation;
import org.n52.oxf.conversion.gml32.xmlbeans.jts.GMLGeometryFactory;
import org.n52.ses.api.exception.GMLParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
/**
* Class for GML 3.2 parsing.
*
* @author Matthes Rieke <m.rieke@uni-muenster.de>
*
*/
public class GML32Parser {
/**
* namespace for GML 3.2
*/
public static final String GML32_NAMESPACE = "http://www.opengis.net/gml/3.2";
private static final Logger logger = LoggerFactory
.getLogger(GML32Parser.class);
/**
* parses a Geometry Object from an EnvelopeType.
*
* @param et The xml beans {@link EnvelopeType}
* @param crs the reference system if known, "" else
* @return java topology suite {@link Geometry}
* @throws ParseException if the geometry could not be parsed.
*/
private static Geometry getGeometryFromEnvelope(EnvelopeType et, String crs) throws ParseException {
//get reference system if not yet defined
String system = crs;
if (et.isSetSrsName() && !system.equals("")) {
system = et.getSrsName();
}
//get upper corner and split at " "
Element elem = (Element) et.getUpperCorner().getDomNode();
String ucString = XmlUtils.toString(elem.getFirstChild()).trim();
String[] uc = ucString.split(" ");
int i = 0;
for (String string : uc) {
string = string.trim();
uc[i] = string;
i++;
}
Coordinate upper = build2DCoordinate(uc[0], uc[1], system);
//get lower corner and split at " "
elem = (Element) et.getLowerCorner().getDomNode();
String lcString = XmlUtils.toString(elem.getFirstChild()).trim();
String[] lc = lcString.split(" ");
i = 0;
for (String string : lc) {
string = string.trim();
lc[i] = string;
i++;
}
Coordinate lower = build2DCoordinate(lc[0], lc[1], system);
String wktString = "POLYGON((" + lower.x + " "+ lower.y + ", "+ lower.x +" "+ upper.y +", "+ upper.x +" "+ upper.y +
", "+ upper.x +" "+ lower.y +", "+ lower.x +" "+ lower.y + "))";
WKTReader wktReader = new WKTReader();
return wktReader.read(wktString);
}
/**
* parses a geometry from a point
*
* @param pt xbeans {@link PointType}
* @param crs the reference system if known, "" else
* @return java topology suite {@link Geometry}
*/
private static final Geometry getGeometryFromPoint(PointType pt, String crs) {
GeometryFactory gf = new GeometryFactory();
Element elem = (Element) pt.getDomNode();
String posString = XmlUtils.toString(elem.getFirstChild()).trim();
if (posString.length() == 0) {
posString = pt.getPos().getStringValue().trim();
}
String[] cs = posString.split(" ");
//get reference system if not yet defined
String system = crs;
if (pt.isSetSrsName() && !system.equals("")) {
system = pt.getSrsName();
}
if (cs != null && cs.length > 1) {
Coordinate c = build2DCoordinate(cs[0], cs[1], system);
return gf.createPoint(c);
}
return null;
}
/**
* Builds a 2D coordinate from two parts of a position string
* and a reference system identifier.
*
* @param first the first part of the position string
* @param second the second part of the position string
* @param crs the reference system identifier ("" if unknown)
*
* @return a {@link Coordinate} at the given position
*/
private static Coordinate build2DCoordinate (String first, String second, String crs) {
return new Coordinate(Double.parseDouble(first), Double.parseDouble(second));
}
/**
* Builds a 2D coordinate from two parts of a position string
* and a reference system identifier.
*
* @param first the first part of the position string
* @param second the second part of the position string
* @param crs the reference system identifier ("" if unknown)
*
* @return a {@link Coordinate} at the given position
*/
private static Coordinate build2DCoordinate (double first, double second, String crs) {
double x;
double y;
if (crs.equals("")) {
/*
* not set
* assume x, y
*/
x = first;
y = second;
}
else if (crs.endsWith("4979")) {
/*
* EPSG 4979 (WGS 83 3D)
* Axis order: lat, lon, h
*/
x = second;
y = first;
//H ignored
}
else if (crs.endsWith("4326")) {
/*
* EPSG 4326 (WGS 84 2D)
* Axis order: lat, lon
*/
x = second;
y = first;
}
else {
/*
* unknown
* assume x, y
*/
x = first;
y = second;
}
return new Coordinate(x, y);
}
/**
* parses a geometry from a line string
*
* @param lst xbeans {@link LineStringType}
* @param crs the reference system if known, "" else
* @return java topology suite {@link Geometry}
* @throws GMLParseException if parsing is not supported
*/
private static final Geometry getGeometryFromLineString(LineStringType lst, String crs) throws GMLParseException {
//get reference system if not yet defined
String system = crs.trim();
if (lst.isSetSrsName() && system.isEmpty()) {
system = lst.getSrsName();
}
GeometryFactory gf = new GeometryFactory();
/*
* try posList
*/
if (lst.isSetPosList()) {
return createLineStringFromPosList(lst.getPosList(), system, gf);
}
/*
* try coordinates
*/
if (lst.isSetCoordinates()) {
CoordinatesType coords = lst.getCoordinates();
String coordSep = coords.getCs();
String decSep = coords.getDecimal();
String tokSep = coords.getTs();
String coordinates = coords.getStringValue().replaceAll("\\s+", " ");
String[] coordArray = coordinates.split(tokSep);
Coordinate[] coordinateSequence = new Coordinate[coordArray.length];
int i = 0;
for (String c : coordArray) {
c = c.trim();
String[] values = c.split(coordSep);
if (values.length != 2) {
//something wrong with the list
return null;
}
//do the right decimal separator
double value0 = 0;
double value1 = 0;
if (!decSep.equals(".")) {
String[] valArray = values[0].split(decSep);
if (valArray.length == 2) {
value0 = Double.parseDouble(valArray[0] +"."+ valArray[1]);
} else if (valArray.length == 1) {
value0 = Double.parseDouble(valArray[0]);
}
valArray = values[1].split(decSep);
if (valArray.length == 2) {
value1 = Double.parseDouble(valArray[0] +"."+ valArray[1]);
} else if (valArray.length == 1) {
value1 = Double.parseDouble(valArray[0]);
}
}
else {
value0 = Double.parseDouble(values[0]);
value1 = Double.parseDouble(values[1]);
}
coordinateSequence[i] = build2DCoordinate(value0, value1, system);
i++;
}
return gf.createLineString(coordinateSequence);
}
return null;
}
private static Geometry createLineStringFromPosList(DirectPositionListType posList,
String system, GeometryFactory gf) throws GMLParseException {
List<?> dataList = posList.getListValue();
if ((dataList.size() % 2) != 0) {
throw new GMLParseException("Odd element count. Either wrong number of doubles" +
" inside the ring or coords are in 3D which is not supported.");
}
Coordinate[] coords = new Coordinate[dataList.size() / 2];
for (int i = 0; i < dataList.size(); i=i+2) {
coords[i/2] = build2DCoordinate((Double) dataList.get(i), (Double) dataList.get(i+1), system);
}
return gf.createLineString(coords);
}
/**
* Main method for parsing a geometry from an xml-fragment.
* This method delegates to the private concrete parsing methods.
*
* @param geomElement the geometry xml object
* @return a {@link Geometry} as a JTS representation.
* @throws ParseException if the geometry could not be parsed.
* @throws GMLParseException if something could not be parsed or is not supported.
*/
public static Geometry parseGeometry(XmlObject geomElement) throws ParseException, GMLParseException {
if (geomElement instanceof EnvelopeType) {
return getGeometryFromEnvelope((EnvelopeType) geomElement, "");
}
else if (geomElement instanceof PointType) {
return getGeometryFromPoint((PointType) geomElement, "");
}
else if (geomElement instanceof DirectPositionType) {
DirectPositionType dirPos = (DirectPositionType) geomElement;
String srs;
if (dirPos.isSetSrsName()) {
srs = dirPos.getSrsName();
} else {
srs = "";
}
return getGeoemtryFromPos(dirPos, srs);
}
else if (geomElement instanceof LineStringType) {
return getGeometryFromLineString((LineStringType) geomElement, "");
}
else if (geomElement instanceof LinearRingType) {
return getGeometryFromLinearRing((LinearRingType) geomElement, "");
}
else if (geomElement instanceof GeodesicStringDocument) {
return getGeometryFromGeodesicString(((GeodesicStringDocument) geomElement).getGeodesicString());
}
else if (geomElement instanceof GeodesicStringType) {
return getGeometryFromGeodesicString((GeodesicStringType) geomElement);
}
else if (geomElement instanceof PolygonType) {
return getGeometryFromPolygon((PolygonType) geomElement, "");
}
else if (geomElement instanceof EnvelopeDocument) {
return getGeometryFromEnvelope(((EnvelopeDocument) geomElement).getEnvelope(), "");
}
else if (geomElement instanceof PosDocument) {
return getGeoemtryFromPos(((PosDocument) geomElement).getPos(), "");
}
else if (geomElement instanceof PolygonDocument) {
return parseGeometry(((PolygonDocument) geomElement).getPolygon());
}
else if (geomElement instanceof PointDocument) {
return parseGeometry(((PointDocument) geomElement).getPoint());
}
else if (geomElement instanceof LineStringDocument) {
return parseGeometry(((LineStringDocument) geomElement).getLineString());
}
logger.info("could not parse gml type, returning null");
return null;
}
private static Geometry getGeometryFromGeodesicString(GeodesicStringType geodesicString) {
if (geodesicString.isSetPosList()) {
GeometryWithInterpolation geom = GMLGeometryFactory.createGreatCirlce(geodesicString, null);
GMLGeometryFactory.checkAndApplyInterpolation(geom);
return geom.getGeometry();
}
throw new UnsupportedOperationException("Only posList is currently supported for GeodesicString.");
}
private static Geometry getGeoemtryFromPos(DirectPositionType pos, String crs) {
//get reference system if not yet defined
String system = crs;
if (pos.isSetSrsName() && !system.equals("")) {
system = pos.getSrsName();
}
List<?> list = pos.getListValue();
if (list.size() < 2) {
//not enough coordinates
return null;
}
GeometryFactory gf = new GeometryFactory();
return gf.createPoint(build2DCoordinate(list.get(0).toString(), list.get(1).toString(), crs));
}
/**
* Main method for parsing a geometry from an xml-fragment.
* If XmlAnyType object this tries to parse it to a geometry
* type.
* This method delegates to the private concrete parsing methods.
*
* @param geomElement the geometry xml object
* @param anyType if this is an XmlAnyType object - tries parsing it.
* @return a {@link Geometry} as a JTS representation.
* @throws ParseException if the geometry could not be parsed.
* @throws GMLParseException if something could not be parsed or is not supported.
*/
public static Geometry parseGeometry(XmlObject geomElement, boolean anyType) throws ParseException, GMLParseException {
if (anyType) {
XmlObject xobj = null;
try {
xobj = XmlObject.Factory.parse(geomElement.toString());
} catch (XmlException e) {
// nothing, go on
}
if (xobj != null) {
return parseGeometry(xobj);
}
throw new ParseException("Could not parse geometry - unkown type.");
}
return parseGeometry(geomElement);
}
/**
* parses a geometry from a polygon
*
* @param pt the xbeans polygon type
* @param crs the reference system if known, "" else
* @return the {@link Geometry}
* @throws GMLParseException
*/
private static Geometry getGeometryFromPolygon(PolygonType pt, String crs) throws GMLParseException {
//get reference system if not yet defined
String system = crs;
if (pt.isSetSrsName() && !system.equals("")) {
system = pt.getSrsName();
}
GeometryFactory gf = new GeometryFactory();
AbstractRingPropertyType[] inter = pt.getInteriorArray();
if (inter.length > 0) {
// throw new GMLParseException("Currently interior rings in polygons" +
// " are not supported.");
logger.info("interior ring is not parsed!");
}
/*
* parse exterior ring
*/
if (pt.isSetExterior()) {
AbstractRingPropertyType exter = pt.getExterior();
AbstractRingType ring = exter.getAbstractRing();
if (ring instanceof LinearRingType) {
Geometry ringGeom = getGeometryFromLinearRing((LinearRingType) ring, system);
return gf.createPolygon((LinearRing) ringGeom, null);
}
throw new GMLParseException("Only LinearRing supported " +
"at the current developement state.");
}
return null;
}
/**
* parses a geometry from a linear ring
*
* @param lrt xbeans linear ring type
* @param crs the reference system if known, "" else
* @return a {@link Geometry}
* @throws GMLParseException
*/
private static Geometry getGeometryFromLinearRing(LinearRingType lrt, String crs) throws GMLParseException {
if (lrt.isSetPosList()) {
//parse posList
GeometryFactory gf = new GeometryFactory();
DirectPositionListType posList = lrt.getPosList();
//get reference system if not yet defined
String system = crs;
if (posList.isSetSrsName() && !system.equals("")) {
system = posList.getSrsName();
}
List<?> dataList = posList.getListValue();
if ((dataList.size() % 2) != 0) {
throw new GMLParseException("Odd element count. Either wrong number of doubles" +
" inside the ring or coords are in 3D which is not supported.");
}
Coordinate[] coords = new Coordinate[(dataList.size() /2) + 1];
for (int i = 0; i < dataList.size(); i=i+2) {
coords[i/2] = build2DCoordinate((Double) dataList.get(i), (Double) dataList.get(i+1), system);
}
coords[coords.length - 1] = coords[0];
return gf.createLinearRing(coords);
}
else if (lrt.isSetCoordinates()) {
//parse coordinates
return getGeometryFromCoordinates(lrt.getCoordinates(), crs);
}
else if (lrt.sizeOfPosArray() > 0){
//parse PosArray
GeometryFactory gf = new GeometryFactory();
DirectPositionType[] posArray = lrt.getPosArray();
Coordinate[] coords = new Coordinate[posArray.length];
for (int i = 0; i < posArray.length; i++){
List<?> xy = posArray[i].getListValue();
if (xy.size() < 2) // coordinates missing
return null;
coords[i] = build2DCoordinate(xy.get(0).toString(), xy.get(1).toString(), crs);
}
return gf.createLinearRing(coords);
}
throw new GMLParseException("Only posList and posArray currently supported.");
}
/**
* parses a geometry from a coordinates set
*
* @param ct the coordinates set as xbeans coordinates type
* @param crs the reference system if known, "" else
* @return a {@link Geometry}
* @throws GMLParseException
*/
private static Geometry getGeometryFromCoordinates(CoordinatesType ct, String crs) throws GMLParseException {
GeometryFactory gf = new GeometryFactory();
//read values
String charSeparator = ct.getCs();
String tupleSeparator = ct.getTs();
String content = ct.getStringValue();
//separate tuples
String[] coords = content.split(tupleSeparator);
Coordinate[] coordinates = new Coordinate[coords.length + 1];
//build coordinates
String[] coord;
for (int i = 0; i < coords.length; i++) {
coord = coords[i].split(charSeparator);
coordinates[i] = build2DCoordinate(coord[0], coord[1], crs);
}
//close circle
coordinates[coordinates.length - 1] = coordinates[0];
//build linear ring (polygon)
try {
return gf.createLinearRing(coordinates);
}
catch (Throwable t) {
throw new GMLParseException(t.getMessage());
}
}
/**
* Parses the time from a GML32 time object.
*
* @param time the xmlobject which holds the time
* @return {@link DateTime} array, null if nothing could be found
*/
public static DateTime[] parseTime(XmlObject time) {
DateTime begin = null;
DateTime end = null;
/*
* wrapped in a document
*/
if (time instanceof TimePeriodDocument) {
return parseTime(((TimePeriodDocument) time).getTimePeriod());
}
else if (time instanceof TimeInstantDocument) {
return parseTime(((TimeInstantDocument) time).getTimeInstant());
}
/*
* just the types
*/
else if (time instanceof TimePeriodType) {
/*
* TimePeriod
*/
TimePeriodType tpt = (TimePeriodType) time;
if (tpt.isSetBeginPosition()) {
begin = new DateTime(tpt.getBeginPosition().getStringValue());
}
else if (tpt.isSetBegin()) {
begin = new DateTime(tpt.getBegin().getTimeInstant().getTimePosition().getStringValue());
}
if (tpt.isSetEndPosition()) {
end = new DateTime(tpt.getEndPosition().getStringValue());
}
else if (tpt.isSetEnd()) {
begin = new DateTime(tpt.getEnd().getTimeInstant().getTimePosition().getStringValue());
}
}
else if (time instanceof TimeInstantType) {
/*
* TimeInstanct
*/
TimeInstantType tit = (TimeInstantType) time;
begin = new DateTime(tit.getTimePosition().getStringValue());
}
/*
* check what should be returned.
* 1-elem array if just begin is found
*/
if (begin != null) {
if (end == null) {
return new DateTime[] {begin};
}
return new DateTime[] {begin, end};
}
return null;
}
}