/** * Copyright (C) 2012-2017 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 * License 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.sos.encode; import java.util.Collections; import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.namespace.QName; import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlObject; import org.apache.xmlbeans.XmlRuntimeException; import org.apache.xmlbeans.impl.values.XmlValueDisconnectedException; import org.n52.sos.exception.ows.NoApplicableCodeException; import org.n52.sos.exception.ows.concrete.DateTimeFormatException; import org.n52.sos.exception.ows.concrete.UnsupportedEncoderInputException; import org.n52.sos.ogc.gml.AbstractFeature; import org.n52.sos.ogc.gml.CodeWithAuthority; import org.n52.sos.ogc.gml.DefaultEncoding; import org.n52.sos.ogc.gml.GenericMetaData; import org.n52.sos.ogc.gml.GmlConstants; import org.n52.sos.ogc.gml.time.Time; import org.n52.sos.ogc.gml.time.Time.TimeIndeterminateValue; import org.n52.sos.ogc.gml.time.TimeInstant; import org.n52.sos.ogc.gml.time.TimePeriod; import org.n52.sos.ogc.gml.time.TimePosition; import org.n52.sos.ogc.om.features.FeatureCollection; import org.n52.sos.ogc.om.features.SfConstants; import org.n52.sos.ogc.om.features.samplingFeatures.SamplingFeature; import org.n52.sos.ogc.om.values.CategoryValue; import org.n52.sos.ogc.om.values.QuantityValue; import org.n52.sos.ogc.ows.OwsExceptionReport; import org.n52.sos.ogc.sos.SosConstants.HelperValues; import org.n52.sos.ogc.sos.SosEnvelope; import org.n52.sos.ogc.swe.SweConstants; import org.n52.sos.service.ServiceConfiguration; import org.n52.sos.util.CodingHelper; import org.n52.sos.util.DateTimeHelper; import org.n52.sos.util.JTSHelper; import org.n52.sos.util.JavaHelper; import org.n52.sos.util.MinMax; import org.n52.sos.util.SosHelper; import org.n52.sos.util.XmlHelper; import org.n52.sos.util.XmlOptionsHelper; import org.n52.sos.w3c.SchemaLocation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Joiner; import com.google.common.collect.Sets; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.geom.util.PolygonExtracter; import net.opengis.gml.AbstractFeatureCollectionType; import net.opengis.gml.AbstractRingPropertyType; import net.opengis.gml.AbstractRingType; import net.opengis.gml.CodeType; import net.opengis.gml.DirectPositionListType; import net.opengis.gml.DirectPositionType; import net.opengis.gml.EnvelopeType; import net.opengis.gml.FeatureCollectionDocument2; import net.opengis.gml.FeaturePropertyType; import net.opengis.gml.LineStringType; import net.opengis.gml.LinearRingType; import net.opengis.gml.MeasureType; import net.opengis.gml.PointType; import net.opengis.gml.PolygonType; import net.opengis.gml.ReferenceType; import net.opengis.gml.TimeIndeterminateValueType; import net.opengis.gml.TimeInstantDocument; import net.opengis.gml.TimeInstantType; import net.opengis.gml.TimePeriodDocument; import net.opengis.gml.TimePeriodType; import net.opengis.gml.TimePositionType; /** * @since 4.0.0 * */ public class GmlEncoderv311 extends AbstractXmlEncoder<Object> { private static final Logger LOGGER = LoggerFactory.getLogger(GmlEncoderv311.class); private static final Set<EncoderKey> ENCODER_KEYS = CodingHelper.encoderKeysForElements(GmlConstants.NS_GML, org.n52.sos.ogc.gml.time.Time.class, com.vividsolutions.jts.geom.Geometry.class, org.n52.sos.ogc.om.values.CategoryValue.class, org.n52.sos.ogc.gml.ReferenceType.class, org.n52.sos.ogc.om.values.QuantityValue.class, org.n52.sos.ogc.gml.CodeWithAuthority.class, org.n52.sos.ogc.gml.CodeType.class, AbstractFeature.class, SosEnvelope.class, GenericMetaData.class); public GmlEncoderv311() { LOGGER.debug("Encoder for the following keys initialized successfully: {}!", Joiner.on(", ") .join(ENCODER_KEYS)); } @Override public Set<EncoderKey> getEncoderKeyType() { return Collections.unmodifiableSet(ENCODER_KEYS); } @Override public void addNamespacePrefixToMap(Map<String, String> nameSpacePrefixMap) { nameSpacePrefixMap.put(GmlConstants.NS_GML, GmlConstants.NS_GML_PREFIX); } @Override public Set<SchemaLocation> getSchemaLocations() { return Sets.newHashSet(GmlConstants.GML_311_SCHEMAL_LOCATION); } @Override public XmlObject encode(Object element, Map<HelperValues, String> additionalValues) throws OwsExceptionReport { XmlObject encodedObject = null; if (element instanceof Time) { encodedObject = createTime((Time) element, additionalValues); } else if (element instanceof Geometry) { encodedObject = createPosition((Geometry) element, additionalValues.get(HelperValues.GMLID)); } else if (element instanceof CategoryValue) { encodedObject = createReferenceTypeForCategroyValue((CategoryValue) element); } else if (element instanceof org.n52.sos.ogc.gml.ReferenceType) { encodedObject = createReferencType((org.n52.sos.ogc.gml.ReferenceType) element); } else if (element instanceof CodeWithAuthority) { encodedObject = createCodeWithAuthorityType((CodeWithAuthority) element); } else if (element instanceof QuantityValue) { encodedObject = createMeasureType((QuantityValue) element); } else if (element instanceof org.n52.sos.ogc.gml.CodeType) { encodedObject = createCodeType((org.n52.sos.ogc.gml.CodeType) element); } else if (element instanceof AbstractFeature) { encodedObject = createFeature((AbstractFeature) element); } else if (element instanceof SosEnvelope) { encodedObject = createEnvelope((SosEnvelope) element); } else if (element instanceof GenericMetaData) { encodedObject = createGenericMetaData((GenericMetaData) element, additionalValues); } else { throw new UnsupportedEncoderInputException(this, element); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Encoded object {} is valid: {}", encodedObject.schemaType().toString(), XmlHelper.validateDocument(encodedObject)); } return encodedObject; } private XmlObject createTime(Time time, Map<HelperValues, String> additionalValues) throws OwsExceptionReport { if (time != null) { if (time instanceof TimeInstant) { if (additionalValues.containsKey(HelperValues.DOCUMENT)) { return createTimeInstantDocument((TimeInstant) time); } else { return createTimeInstantType((TimeInstant) time, null); } } else if (time instanceof TimePeriod) { if (additionalValues.containsKey(HelperValues.DOCUMENT)) { return createTimePeriodDocument((TimePeriod) time); } else { return createTimePeriodType((TimePeriod) time, null); } } else { throw new UnsupportedEncoderInputException(this, time); } } return null; } private XmlObject createTimePeriodDocument(TimePeriod time) throws OwsExceptionReport { TimePeriodDocument timePeriodDoc = TimePeriodDocument.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); createTimePeriodType(time, timePeriodDoc.addNewTimePeriod()); return timePeriodDoc; } /** * Creates a XML TimePeriod from the SOS time object. * * @param timePeriod * SOS time object * @param timePeriodType * @return XML TimePeriod * * * @throws OwsExceptionReport * * if an error occurs. */ private TimePeriodType createTimePeriodType(TimePeriod timePeriod, TimePeriodType timePeriodType) throws OwsExceptionReport { try { if (timePeriodType == null) { timePeriodType = TimePeriodType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); } if (timePeriod.getGmlId() != null && !timePeriod.getGmlId().isEmpty()) { timePeriodType.setId(timePeriod.getGmlId()); } timePeriodType.setBeginPosition(createTimePositionType(timePeriod.getStartTimePosition())); timePeriodType.setEndPosition(createTimePositionType(timePeriod.getEndTimePosition())); return timePeriodType; } catch (XmlRuntimeException x) { throw new NoApplicableCodeException().causedBy(x).withMessage("Error while creating TimePeriod!"); } catch (XmlValueDisconnectedException x) { throw new NoApplicableCodeException().causedBy(x).withMessage("Error while creating TimePeriod!"); } } private XmlObject createTimeInstantDocument(TimeInstant time) throws OwsExceptionReport { TimeInstantDocument timeInstantDoc = TimeInstantDocument.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); createTimeInstantType(time, timeInstantDoc.addNewTimeInstant()); return timeInstantDoc; } /** * Creates a XML TimeInstant from the SOS time object. * * @param timeInstant * SOS time object * @param timeInstantType * @return XML TimeInstant * * * @throws OwsExceptionReport * * if an error occurs. */ private TimeInstantType createTimeInstantType(TimeInstant timeInstant, TimeInstantType timeInstantType) throws OwsExceptionReport { // create time instant if (timeInstantType == null) { timeInstantType = TimeInstantType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); } if (timeInstant.isSetGmlId()) { timeInstantType.setId(timeInstant.getGmlId()); } timeInstantType.setTimePosition(createTimePositionType(timeInstant.getTimePosition())); return timeInstantType; } private TimePositionType createTimePositionType(final TimePosition timePosition) throws DateTimeFormatException { final TimePositionType xbTimePosition = TimePositionType.Factory.newInstance(); if (!timePosition.isSetTime()) { if (timePosition.isSetIndeterminateValue()) { xbTimePosition.setIndeterminatePosition(TimeIndeterminateValueType.Enum.forString(timePosition .getIndeterminateValue().name())); } else { xbTimePosition.setIndeterminatePosition(TimeIndeterminateValueType.Enum .forString(TimeIndeterminateValue.unknown.name())); } } else { final String endString = DateTimeHelper.formatDateTime2String(timePosition.getTime(), timePosition.getTimeFormat()); // concat minutes for timeZone offset, because gml requires // xs:dateTime, which needs minutes in // timezone offset // TODO enable really xbTimePosition.setStringValue(endString); } return xbTimePosition; } private XmlObject createPosition(Geometry geom, String foiId) throws OwsExceptionReport { if (geom instanceof Point) { PointType xbPoint = PointType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); if (foiId != null) { xbPoint.setId("point_" + foiId); } createPointFromJtsGeometry((Point) geom, xbPoint); return xbPoint; } else if (geom instanceof LineString) { LineStringType xbLineString = LineStringType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); if (foiId != null) { xbLineString.setId("lineString_" + foiId); } createLineStringFromJtsGeometry((LineString) geom, xbLineString); return xbLineString; } else if (geom instanceof Polygon) { PolygonType xbPolygon = PolygonType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); if (foiId != null) { xbPolygon.setId("polygon_" + foiId); } createPolygonFromJtsGeometry((Polygon) geom, xbPolygon); return xbPolygon; } else { throw new UnsupportedEncoderInputException(this, geom); } } /** * Creates a XML Point from a SOS Point. * * @param jtsPoint * SOS Point * @param xbPoint * XML Point */ private void createPointFromJtsGeometry(Point jtsPoint, PointType xbPoint) throws OwsExceptionReport { DirectPositionType xbPos = xbPoint.addNewPos(); xbPos.setSrsName(getSrsName(jtsPoint)); xbPos.setStringValue(JTSHelper.getCoordinatesString(jtsPoint)); } /** * Creates a XML LineString from a SOS LineString. * * @param jtsLineString * SOS LineString * @param xbLst * XML LinetSring */ private void createLineStringFromJtsGeometry(LineString jtsLineString, LineStringType xbLst) throws OwsExceptionReport { DirectPositionListType xbPosList = xbLst.addNewPosList(); xbPosList.setSrsName(getSrsName(jtsLineString)); xbPosList.setStringValue(JTSHelper.getCoordinatesString(jtsLineString)); } /** * Creates a XML Polygon from a SOS Polygon. * * @param jtsPolygon * SOS Polygon * @param xbPolType * XML Polygon */ private void createPolygonFromJtsGeometry(Polygon jtsPolygon, PolygonType xbPolType) throws OwsExceptionReport { List<?> jtsPolygons = PolygonExtracter.getPolygons(jtsPolygon); for (int i = 0; i < jtsPolygons.size(); i++) { Polygon pol = (Polygon) jtsPolygons.get(i); AbstractRingPropertyType xbArpt = xbPolType.addNewExterior(); AbstractRingType xbArt = xbArpt.addNewRing(); LinearRingType xbLrt = LinearRingType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); // Exterior ring LineString ring = pol.getExteriorRing(); String coords = JTSHelper.getCoordinatesString(ring); DirectPositionListType xbPosList = xbLrt.addNewPosList(); xbPosList.setSrsName(getSrsName(jtsPolygon)); // switch coordinates xbPosList.setStringValue(coords); xbArt.set(xbLrt); // Rename element name for output XmlCursor cursor = xbArpt.newCursor(); if (cursor.toChild(GmlConstants.QN_ABSTRACT_RING)) { cursor.setName(GmlConstants.QN_LINEAR_RING); } // Interior ring int numberOfInteriorRings = pol.getNumInteriorRing(); for (int ringNumber = 0; ringNumber < numberOfInteriorRings; ringNumber++) { xbArpt = xbPolType.addNewInterior(); xbArt = xbArpt.addNewRing(); xbLrt = LinearRingType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); ring = pol.getInteriorRingN(ringNumber); xbPosList = xbLrt.addNewPosList(); xbPosList.setSrsName(getSrsName(jtsPolygon)); xbPosList.setStringValue(JTSHelper.getCoordinatesString(ring)); xbArt.set(xbLrt); // Rename element name for output cursor = xbArpt.newCursor(); if (cursor.toChild(GmlConstants.QN_ABSTRACT_RING)) { cursor.setName(GmlConstants.QN_LINEAR_RING); } } } } private XmlObject createReferenceTypeForCategroyValue(CategoryValue categoryValue) { ReferenceType xbRef = ReferenceType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); if (categoryValue.getValue() != null && !categoryValue.getValue().isEmpty()) { if (categoryValue.getValue().startsWith("http://")) { xbRef.setHref(categoryValue.getValue()); } else { xbRef.setTitle(categoryValue.getValue()); } } else { xbRef.setNil(); } return xbRef; } private XmlObject createReferencType(org.n52.sos.ogc.gml.ReferenceType sosReferenceType) { if (sosReferenceType.isSetHref()) { ReferenceType referenceType = ReferenceType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); referenceType.setHref(sosReferenceType.getHref()); if (sosReferenceType.isSetTitle()) { referenceType.setTitle(sosReferenceType.getTitle()); } if (sosReferenceType.isSetRole()) { referenceType.setRole(sosReferenceType.getRole()); } return referenceType; } return null; } private XmlObject createCodeWithAuthorityType(CodeWithAuthority sosCodeWithAuthority) { if (sosCodeWithAuthority.isSetValue()) { CodeType codeType = CodeType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); String value = sosCodeWithAuthority.getValue(); codeType.setStringValue(value); codeType.setCodeSpace(sosCodeWithAuthority.getCodeSpace()); return codeType; } return null; } private XmlObject createCodeType(org.n52.sos.ogc.gml.CodeType sosCodeType) { CodeType codeType = CodeType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); codeType.setCodeSpace(sosCodeType.getCodeSpace()); codeType.setStringValue(sosCodeType.getValue()); return codeType; } protected XmlObject createMeasureType(QuantityValue quantityValue) { MeasureType measureType = MeasureType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); if (quantityValue.getUnit() != null) { measureType.setUom(quantityValue.getUnit()); } else { measureType.setUom(""); } if (quantityValue.getValue() != null) { measureType.setDoubleValue(quantityValue.getValue().doubleValue()); } else { measureType.setNil(); } return measureType; } private XmlObject createFeature(AbstractFeature sosAbstractFeature) throws OwsExceptionReport { if (sosAbstractFeature instanceof SamplingFeature) { SamplingFeature sampFeat = (SamplingFeature) sosAbstractFeature; if (sosAbstractFeature.isSetGmlID()) { FeaturePropertyType featureProperty = FeaturePropertyType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); featureProperty.setHref("#" + sosAbstractFeature.getGmlId()); return featureProperty; } else { if (!sampFeat.isSetGeometry()) { FeaturePropertyType featureProperty = FeaturePropertyType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); featureProperty.setHref(sosAbstractFeature.getIdentifierCodeWithAuthority().getValue()); if (sampFeat.isSetName()) { featureProperty.setTitle(sampFeat.getFirstName().getValue()); } return featureProperty; } StringBuilder builder = new StringBuilder(); builder.append("sf_"); builder.append(JavaHelper.generateID(sosAbstractFeature.getIdentifierCodeWithAuthority().getValue())); sosAbstractFeature.setGmlId(builder.toString()); Encoder<XmlObject, SamplingFeature> encoder = CodingHelper.getEncoder(SfConstants.NS_SA, sampFeat); if (encoder != null) { return encoder.encode(sampFeat); } else { FeaturePropertyType featureProperty = FeaturePropertyType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); featureProperty.setHref(sampFeat.getIdentifierCodeWithAuthority().getValue()); if (sampFeat.isSetName()) { featureProperty.setTitle(sampFeat.getFirstName().getValue()); } return featureProperty; } } } else if (sosAbstractFeature instanceof FeatureCollection) { return createFeatureCollection((FeatureCollection) sosAbstractFeature); } throw new UnsupportedEncoderInputException(this, sosAbstractFeature); } private XmlObject createFeatureCollection(FeatureCollection sosFeatureCollection) throws OwsExceptionReport { Map<String, AbstractFeature> members = sosFeatureCollection.getMembers(); XmlObject xmlObject = null; if (sosFeatureCollection.isSetMembers()) { if (members.size() == 1) { for (String member : members.keySet()) { if (members.get(member) instanceof SamplingFeature) { return createFeature((SamplingFeature) members.get(member)); } else { throw new NoApplicableCodeException().withMessage("No encoder found for featuretype"); } } } else { FeatureCollectionDocument2 xbFeatureColllectionDoc = FeatureCollectionDocument2.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); AbstractFeatureCollectionType xbFeatCol = xbFeatureColllectionDoc.addNewFeatureCollection(); StringBuilder builder = new StringBuilder(); builder.append("sfc_"); builder.append(JavaHelper.generateID(Long.toString(System.currentTimeMillis()))); xbFeatCol.setId(builder.toString()); for (String member : members.keySet()) { if (members.get(member) instanceof SamplingFeature) { XmlObject xmlFeature = createFeature((SamplingFeature) members.get(member)); xbFeatCol.addNewFeatureMember().set(xmlFeature); } else { throw new NoApplicableCodeException().withMessage("No encoder found for featuretype"); } } xmlObject = xbFeatureColllectionDoc; } } else { FeatureCollectionDocument2 xbFeatColDoc = FeatureCollectionDocument2.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); xbFeatColDoc.addNewFeatureCollection(); xmlObject = xbFeatColDoc; } XmlCursor cursor = xmlObject.newCursor(); boolean isAFC = cursor.toChild(new QName(GmlConstants.NS_GML, GmlConstants.EN_ABSTRACT_FEATURE_COLLECTION)); if (isAFC) { cursor.setName(new QName(GmlConstants.NS_GML, GmlConstants.EN_FEATURE_COLLECTION)); } cursor.dispose(); return xmlObject; } private XmlObject createEnvelope(SosEnvelope sosEnvelope) { EnvelopeType envelopeType = EnvelopeType.Factory.newInstance(XmlOptionsHelper.getInstance().getXmlOptions()); MinMax<String> minmax = SosHelper.getMinMaxFromEnvelope(sosEnvelope.getEnvelope()); envelopeType.addNewLowerCorner().setStringValue(minmax.getMinimum()); envelopeType.addNewUpperCorner().setStringValue(minmax.getMaximum()); envelopeType.setSrsName(ServiceConfiguration.getInstance().getSrsNamePrefix() + sosEnvelope.getSrid()); return envelopeType; } protected String getSrsName(Geometry geom) { return ServiceConfiguration.getInstance().getSrsNamePrefix() + geom.getSRID(); } private XmlObject createGenericMetaData(GenericMetaData element, Map<HelperValues, String> additionalValues) throws OwsExceptionReport { if (element.getContent() instanceof DefaultEncoding && ((DefaultEncoding)element.getContent()).isSetDefaultElementEncoding()) { Map<HelperValues, String> helperValues = new EnumMap<HelperValues, String>(HelperValues.class); // TODO check helperValues.put(HelperValues.DOCUMENT, "true"); if (SweConstants.NS_SWE_20.equals(((DefaultEncoding) element.getContent()).getDefaultElementEncoding())) { return CodingHelper.encodeObjectToXml( SweConstants.NS_SWE_101, element.getContent(), helperValues); } else { return CodingHelper.encodeObjectToXml( ((DefaultEncoding) element.getContent()).getDefaultElementEncoding(), element.getContent(), helperValues); } } return null; } }