package nl.ipo.cds.etl.featurecollection;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import nl.ipo.cds.domain.AttributeType;
import nl.ipo.cds.domain.FeatureType;
import nl.ipo.cds.domain.FeatureTypeAttribute;
import nl.ipo.cds.etl.GenericFeature;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.deegree.commons.tom.TypedObjectNode;
import org.deegree.commons.tom.datetime.Date;
import org.deegree.commons.tom.datetime.DateTime;
import org.deegree.commons.tom.genericxml.GenericXMLElement;
import org.deegree.commons.tom.gml.property.Property;
import org.deegree.commons.tom.ows.CodeType;
import org.deegree.commons.tom.primitive.PrimitiveValue;
import org.deegree.commons.uom.Measure;
import org.deegree.commons.xml.XMLParsingException;
import org.deegree.cs.coordinatesystems.ICRS;
import org.deegree.cs.exceptions.UnknownCRSException;
import org.deegree.feature.Feature;
import org.deegree.feature.types.AppSchema;
import org.deegree.geometry.Envelope;
import org.deegree.geometry.Geometry;
import org.deegree.gml.GMLInputFactory;
import org.deegree.gml.GMLStreamReader;
import org.deegree.gml.GMLVersion;
public class FeatureCollectionReader {
private static final Log logger = LogFactory.getLog(FeatureCollectionReader.class);
private final GMLVersion gmlVersion;
private final AppSchema applicationSchema;
private final String GML_NS = "http://www.opengis.net/gml";
private abstract class AbstractFeatureCollection implements FeatureCollection {
final Envelope boundedBy;
final FeatureType featureType;
public AbstractFeatureCollection(Envelope boundedBy, final FeatureType featureType) {
if (featureType == null) {
throw new NullPointerException ("featureType cannot be null");
}
this.boundedBy = boundedBy;
this.featureType = featureType;
}
@Override
public Envelope getBoundedBy() {
return boundedBy;
}
@Override
public FeatureType getFeatureType () {
return featureType;
}
}
private class DefaultFeatureCollection extends AbstractFeatureCollection {
boolean parsing = false, endOfStream = false;
final XMLStreamReader streamReader;
final boolean featureMembers;
public DefaultFeatureCollection(XMLStreamReader streamReader, Envelope boundedBy, boolean featureMembers, final FeatureType featureType) {
super(boundedBy, featureType);
this.streamReader = streamReader;
this.featureMembers = featureMembers;
}
@Override
public Iterator<GenericFeature> iterator() {
if(parsing) {
throw new IllegalStateException("Parsing already started");
}
parsing = true;
final ICRS defaultCRS;
if(boundedBy != null) {
defaultCRS = boundedBy.getCoordinateSystem();
} else {
defaultCRS = null;
}
try {
return new Iterator<GenericFeature>() {
{
logger.debug("interator constructed");
if(featureMembers) {
nextTag();
} else {
nextFeatureMember();
}
}
private void nextTag() throws XMLStreamException {
streamReader.next();
while(streamReader.hasNext() && !streamReader.isStartElement()) {
streamReader.next();
}
}
private void nextFeatureMember() throws XMLStreamException {
logger.debug("fetching next feature");
if(featureMembers) {
while(streamReader.hasNext()) {
if(streamReader.isStartElement()) {
logger.debug("next feature within featureMembers fetched");
break;
}
if(streamReader.isEndElement()
&& WFSResponseReader.GML_NAMESPACES.contains (streamReader.getNamespaceURI())
&& streamReader.getLocalName().equals("featureMembers")) {
logger.debug("end of stream");
endOfStream = true;
break;
}
streamReader.next();
}
} else {
while(streamReader.hasNext()) {
if(streamReader.isStartElement()) {
QName currentName = streamReader.getName();
if(WFSResponseReader.GML_NAMESPACES.contains (currentName.getNamespaceURI())
&& currentName.getLocalPart().equals("featureMember")) {
nextTag();
logger.debug("next feature fetched");
return;
}
}
streamReader.next();
}
logger.debug("end of stream");
endOfStream = true;
}
}
@Override
public boolean hasNext() {
return !endOfStream;
}
@Override
public GenericFeature next() {
try {
final GMLStreamReader gmlStreamReader = GMLInputFactory.createGMLStreamReader (gmlVersion, streamReader);
gmlStreamReader.setApplicationSchema (applicationSchema);
if (defaultCRS != null) {
gmlStreamReader.setDefaultCRS (defaultCRS);
}
logger.debug("unmarshal feature");
final Feature feature = gmlStreamReader.readFeature ();
nextFeatureMember ();
return createGenericFeature (feature, featureType);
} catch(Exception e) {
logger.debug("error: " + e.getMessage());
throw new RuntimeException("Couldn't read feature member", e);
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
} catch(Exception e) {
logger.debug("error: " + e.getMessage());
throw new RuntimeException("Couldn't parse feature collection", e);
}
}
}
private class EmptyFeatureCollection extends AbstractFeatureCollection {
public EmptyFeatureCollection(Envelope boundedBy, final FeatureType featureType) {
super(boundedBy, featureType);
}
@Override
public Iterator<GenericFeature> iterator() {
return Collections.<GenericFeature>emptyList().iterator();
}
}
public FeatureCollectionReader(final AppSchema applicationSchema) {
if (applicationSchema == null) {
throw new NullPointerException ("applicationSchema is null");
}
if (applicationSchema.getGMLSchema () == null || applicationSchema.getGMLSchema ().getVersion () == null) {
throw new IllegalArgumentException ("Provided applicationSchema has no GML version");
}
this.gmlVersion = applicationSchema.getGMLSchema ().getVersion ();
this.applicationSchema = applicationSchema;
}
FeatureCollection parseCollection(final XMLStreamReader streamReader, final FeatureType featureType) throws XMLStreamException, XMLParsingException, UnknownCRSException {
Envelope boundedBy = null;
if(streamReader.isStartElement()) {
QName rootName = streamReader.getName();
if((rootName.getNamespaceURI().equals(WFSResponseReader.WFS_NS) || WFSResponseReader.GML_NAMESPACES.contains (rootName.getNamespaceURI ()))
&& rootName.getLocalPart().equals("FeatureCollection")) {
while(streamReader.hasNext()) {
if(streamReader.isStartElement()) {
if(WFSResponseReader.GML_NAMESPACES.contains (streamReader.getNamespaceURI ())) {
String localName = streamReader.getLocalName();
if(localName.equals("featureMember")) {
return new DefaultFeatureCollection(streamReader, boundedBy, false, featureType);
} else if(localName.equals("featureMembers")) {
return new DefaultFeatureCollection(streamReader, boundedBy, true, featureType);
} else if(localName.equals("boundedBy")) {
streamReader.nextTag();
GMLStreamReader gmlStreamReader = GMLInputFactory.createGMLStreamReader(gmlVersion, streamReader);
boundedBy = (Envelope)gmlStreamReader.readGeometryOrEnvelope();
}
}
} else if(streamReader.isEndElement() && streamReader.getName().equals(rootName)) {
return new EmptyFeatureCollection(boundedBy, featureType);
}
streamReader.next();
}
}
throw new RuntimeException("XML stream is not a feature collection");
} else {
throw new RuntimeException("Empty XML stream");
}
}
public static GenericFeature createGenericFeature (final Feature feature, final FeatureType featureType) {
if (feature == null) {
throw new NullPointerException ("feature is null");
}
final Map<String, Object> values = new HashMap<String, Object> ();
for (final Property property: feature.getProperties ()) {
final TypedObjectNode node = property.getValue ();
final String name = property.getName ().getLocalPart ();
// Lookup the attribute in the feature type:
final FeatureTypeAttribute attribute = getFeatureTypeAttribute (featureType, name);
if (node instanceof Geometry) {
values.put (property.getName ().getLocalPart (), (Geometry)node);
} else if (node instanceof PrimitiveValue) {
final PrimitiveValue value = (PrimitiveValue)node;
values.put (property.getName ().getLocalPart (), convertPrimitiveValue (value, attribute));
} else if (node instanceof CodeType || node instanceof Measure) {
values.put (property.getName ().getLocalPart (), node);
} else if (node instanceof GenericXMLElement) {
final GenericXMLElement elem = (GenericXMLElement)node;
// Parse code types and measures:
final String value = elem.getValue ().getAsText ();
final String codeSpace = getAttributeValue (elem, "codeSpace");
values.put (property.getName ().getLocalPart (), new CodeType (value, codeSpace));
}
}
return new GenericFeature (feature.getId (), values);
}
private static FeatureTypeAttribute getFeatureTypeAttribute (final FeatureType featureType, final String propertyName) {
for (final FeatureTypeAttribute attr: featureType.getAttributes ()) {
if (attr.getName ().getLocalPart ().equals (propertyName)) {
return attr;
}
}
return null;
}
private static String getAttributeValue (final GenericXMLElement element, final String localName) {
final Map<QName, PrimitiveValue> attributes = element.getAttributes ();
for (final Map.Entry<QName, PrimitiveValue> entry: attributes.entrySet ()) {
if (entry.getKey ().getLocalPart ().equals (localName)) {
return entry.getValue ().getAsText ();
}
}
return null;
}
protected static Object convertPrimitiveValue (final PrimitiveValue value, final FeatureTypeAttribute attribute) {
final Object val = value.getValue ();
if (val instanceof DateTime) {
return new Timestamp (((DateTime)val).getTimeInMilliseconds ());
} else if (val instanceof Date) {
return new java.sql.Date (((Date)val).getTimeInMilliseconds ());
}
// Convert BigDecimal into either Double, Float or BigDecimal depending on the type of the feature type attribute.
// deegree returns each of these as BigDecimal.
if (val instanceof BigDecimal && attribute != null) {
if (AttributeType.DOUBLE.equals (attribute.getType())) {
return ((BigDecimal)val).doubleValue ();
} else if (AttributeType.FLOAT.equals (attribute.getType ())) {
return ((BigDecimal)val).floatValue ();
} else {
return (BigDecimal)val;
}
}
return val;
}
}