/**
* 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.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.stream.XMLStreamException;
import net.opengis.gml.x32.MeasureOrNilReasonListType;
import net.opengis.gml.x32.QuantityListDocument;
import net.opengis.om.x20.OMObservationType;
import net.opengis.watermlDr.x20.MeasurementTimeseriesCoverageType;
import net.opengis.watermlDr.x20.MeasurementTimeseriesDomainRangeDocument;
import net.opengis.watermlDr.x20.TimePositionListDocument;
import net.opengis.watermlDr.x20.TimePositionListType;
import org.apache.xmlbeans.XmlObject;
import org.n52.sos.encode.streaming.WmlTDREncoderv20XmlStreamWriter;
import org.n52.sos.exception.ows.NoApplicableCodeException;
import org.n52.sos.exception.ows.concrete.UnsupportedEncoderInputException;
import org.n52.sos.ogc.OGCConstants;
import org.n52.sos.ogc.gml.AbstractFeature;
import org.n52.sos.ogc.gmlcov.GmlCoverageConstants;
import org.n52.sos.ogc.om.AbstractObservationValue;
import org.n52.sos.ogc.om.AbstractPhenomenon;
import org.n52.sos.ogc.om.MultiObservationValues;
import org.n52.sos.ogc.om.ObservationValue;
import org.n52.sos.ogc.om.OmConstants;
import org.n52.sos.ogc.om.OmObservableProperty;
import org.n52.sos.ogc.om.OmObservation;
import org.n52.sos.ogc.om.SingleObservationValue;
import org.n52.sos.ogc.om.TimeValuePair;
import org.n52.sos.ogc.om.values.CountValue;
import org.n52.sos.ogc.om.values.QuantityValue;
import org.n52.sos.ogc.om.values.TVPValue;
import org.n52.sos.ogc.ows.OwsExceptionReport;
import org.n52.sos.ogc.sos.Sos2Constants;
import org.n52.sos.ogc.sos.SosConstants;
import org.n52.sos.ogc.sos.SosConstants.HelperValues;
import org.n52.sos.ogc.swe.SweConstants;
import org.n52.sos.ogc.swe.SweDataRecord;
import org.n52.sos.ogc.swe.SweField;
import org.n52.sos.ogc.swe.simpleType.SweQuantity;
import org.n52.sos.ogc.wml.WaterMLConstants;
import org.n52.sos.response.GetObservationResponse;
import org.n52.sos.service.ServiceConstants.SupportedTypeKey;
import org.n52.sos.util.CodingHelper;
import org.n52.sos.util.CollectionHelper;
import org.n52.sos.util.StringHelper;
import org.n52.sos.w3c.SchemaLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
* Encoder class for WaterML 2.0 TimeseriesDomainRange (TDR)
*
* @author Carsten Hollmann <c.hollmann@52north.org>
* @since 4.0.0
*
*/
public class WmlTDREncoderv20 extends AbstractWmlEncoderv20 {
private static final Logger LOGGER = LoggerFactory.getLogger(WmlTDREncoderv20.class);
// TODO: change to correct conformance class
private static final Set<String> CONFORMANCE_CLASSES = ImmutableSet.of();
private static final Set<EncoderKey> ENCODER_KEYS = createEncoderKeys();
private static final Map<SupportedTypeKey, Set<String>> SUPPORTED_TYPES = Collections.singletonMap(
SupportedTypeKey.ObservationType, Collections.singleton(WaterMLConstants.OBSERVATION_TYPE_MEASURMENT_TDR));;
private static final Map<String, Map<String, Set<String>>> SUPPORTED_RESPONSE_FORMATS = Collections.singletonMap(
SosConstants.SOS,
Collections.singletonMap(Sos2Constants.SERVICEVERSION,
Collections.singleton(WaterMLConstants.NS_WML_20_DR)));
public WmlTDREncoderv20() {
LOGGER.debug("Encoder for the following keys initialized successfully: {}!", Joiner.on(", ")
.join(ENCODER_KEYS));
}
@SuppressWarnings("unchecked")
private static Set<EncoderKey> createEncoderKeys() {
return CollectionHelper.union(getDefaultEncoderKeys(), CodingHelper.encoderKeysForElements(
WaterMLConstants.NS_WML_20_DR, GetObservationResponse.class, OmObservation.class,
AbstractFeature.class, SingleObservationValue.class, MultiObservationValues.class));
}
@Override
public Set<EncoderKey> getEncoderKeyType() {
return Collections.unmodifiableSet(ENCODER_KEYS);
}
@Override
public Map<SupportedTypeKey, Set<String>> getSupportedTypes() {
return Collections.unmodifiableMap(SUPPORTED_TYPES);
}
@Override
public Set<String> getConformanceClasses() {
return Collections.unmodifiableSet(CONFORMANCE_CLASSES);
}
@Override
public void addNamespacePrefixToMap(Map<String, String> nameSpacePrefixMap) {
super.addNamespacePrefixToMap(nameSpacePrefixMap);
nameSpacePrefixMap.put(WaterMLConstants.NS_WML_20_DR, WaterMLConstants.NS_WML_20_DR_PREFIX);
nameSpacePrefixMap.put(GmlCoverageConstants.NS_GML_COV, GmlCoverageConstants.NS_GML_COV_PREFIX);
}
@Override
public Set<String> getSupportedResponseFormats(String service, String version) {
if (SUPPORTED_RESPONSE_FORMATS.get(service) != null
&& SUPPORTED_RESPONSE_FORMATS.get(service).get(version) != null) {
return SUPPORTED_RESPONSE_FORMATS.get(service).get(version);
}
return Collections.emptySet();
}
@Override
public Set<SchemaLocation> getSchemaLocations() {
return Sets.newHashSet(WaterMLConstants.WML_20_SCHEMA_LOCATION, WaterMLConstants.WML_20_DR_SCHEMA_LOCATION,
GmlCoverageConstants.GML_COVERAGE_10_SCHEMA_LOCATION);
}
@Override
public boolean supportsResultStreamingForMergedValues() {
return false;
}
@Override
public XmlObject encode(Object element, Map<HelperValues, String> additionalValues) throws OwsExceptionReport,
UnsupportedEncoderInputException {
XmlObject encodedObject = null;
if (element instanceof ObservationValue) {
encodedObject = encodeResult((ObservationValue<?>) element);
} else {
encodedObject = super.encode(element, additionalValues);
}
return encodedObject;
}
@Override
public void encode(Object objectToEncode, OutputStream outputStream, EncodingValues encodingValues)
throws OwsExceptionReport {
encodingValues.setEncoder(this);
if (objectToEncode instanceof OmObservation) {
try {
new WmlTDREncoderv20XmlStreamWriter().write((OmObservation) objectToEncode, outputStream,
encodingValues);
} catch (XMLStreamException xmlse) {
throw new NoApplicableCodeException().causedBy(xmlse).withMessage(
"Error while writing element to stream!");
}
} else {
super.encode(objectToEncode, outputStream, encodingValues);
}
}
@Override
protected XmlObject createResult(OmObservation sosObservation) throws OwsExceptionReport {
return createMeasurementDomainRange(sosObservation);
}
@Override
protected XmlObject encodeResult(ObservationValue<?> observationValue) throws OwsExceptionReport {
return createMeasurementDomainRange((AbstractObservationValue<?>) observationValue);
}
@Override
protected void addObservationType(OMObservationType xbObservation, String observationType) {
if (StringHelper.isNotEmpty(observationType)) {
if (observationType.equals(OmConstants.OBS_TYPE_MEASUREMENT)
|| observationType.equals(WaterMLConstants.OBSERVATION_TYPE_MEASURMENT_TDR)) {
xbObservation.addNewType().setHref(WaterMLConstants.OBSERVATION_TYPE_MEASURMENT_TDR);
} else if (observationType.equals(OmConstants.OBS_TYPE_CATEGORY_OBSERVATION)
|| observationType.equals(WaterMLConstants.OBSERVATION_TYPE_CATEGORICAL_TDR)) {
xbObservation.addNewType().setHref(WaterMLConstants.OBSERVATION_TYPE_CATEGORICAL_TDR);
}
}
}
/**
* Create a XML MeasurementTimeseriesDomainRange object from SOS observation
* for om:result
*
* @param sosObservation
* SOS observation
* @return XML MeasurementTimeseriesDomainRange object for om:result
* @throws OwsExceptionReport
* If an error occurs
*/
private XmlObject createMeasurementDomainRange(OmObservation sosObservation) throws OwsExceptionReport {
if (!sosObservation.getObservationConstellation().isSetObservationType()
|| (sosObservation.getObservationConstellation().isSetObservationType() && isInvalidObservationType(sosObservation
.getObservationConstellation().getObservationType()))) {
throw new UnsupportedEncoderInputException(this, sosObservation.getObservationConstellation().isSetObservationType());
}
MeasurementTimeseriesDomainRangeDocument xbMearuementTimeseriesDomainRangeDoc =
MeasurementTimeseriesDomainRangeDocument.Factory.newInstance();
MeasurementTimeseriesCoverageType xbMeasurementTimeseriesDomainRange =
xbMearuementTimeseriesDomainRangeDoc.addNewMeasurementTimeseriesDomainRange();
xbMeasurementTimeseriesDomainRange.setId("timeseries_" + sosObservation.getObservationID());
// set time position list
xbMeasurementTimeseriesDomainRange.addNewDomainSet().set(getTimePositionList(sosObservation));
// initialize unit
AbstractPhenomenon observableProperty = sosObservation.getObservationConstellation().getObservableProperty();
String unit = "";
// create quantity list from values
QuantityListDocument quantityListDoc = QuantityListDocument.Factory.newInstance();
MeasureOrNilReasonListType quantityList = quantityListDoc.addNewQuantityList();
if (sosObservation.getValue() instanceof MultiObservationValues) {
MultiObservationValues<?> observationValue = (MultiObservationValues<?>) sosObservation.getValue();
TVPValue tvpValue = (TVPValue) observationValue.getValue();
List<TimeValuePair> timeValuePairs = tvpValue.getValue();
if (Strings.isNullOrEmpty(unit) && CollectionHelper.isNotEmpty(timeValuePairs)
&& timeValuePairs.get(0).getValue().isSetUnit()) {
unit = timeValuePairs.get(0).getValue().getUnit();
}
quantityList.setListValue(getValueList(timeValuePairs));
}
if (Strings.isNullOrEmpty(unit)) {
unit = OGCConstants.UNKNOWN;
}
quantityList.setUom(unit);
// set unit to SosObservableProperty if not set.
if (observableProperty instanceof OmObservableProperty
&& !((OmObservableProperty) observableProperty).isSetUnit()) {
((OmObservableProperty) observableProperty).setUnit(unit);
}
// set up range set
xbMeasurementTimeseriesDomainRange.addNewRangeSet().set(quantityListDoc);
// set up rangeType
xbMeasurementTimeseriesDomainRange.addNewRangeType().set(createDataRecord(sosObservation));
// set om:Result
return xbMearuementTimeseriesDomainRangeDoc;
}
/**
* Create a SOS DataRecord object from SOS observation and encode to
* XmlBeans object
*
* @param sosObservation
* SOS observation
* @return XML DataRecord object
* @throws OwsExceptionReport
* If an error occurs
*/
private XmlObject createDataRecord(OmObservation sosObservation) throws OwsExceptionReport {
AbstractPhenomenon observableProperty = sosObservation.getObservationConstellation().getObservableProperty();
SweDataRecord dataRecord = new SweDataRecord();
dataRecord.setIdentifier("datarecord_" + sosObservation.getObservationID());
SweQuantity quantity = new SweQuantity();
quantity.setDefinition(observableProperty.getIdentifier());
quantity.setDescription(observableProperty.getDescription());
if (observableProperty instanceof OmObservableProperty
&& ((OmObservableProperty) observableProperty).isSetUnit()) {
quantity.setUom(((OmObservableProperty) observableProperty).getUnit());
}
SweField field = new SweField("observed_value", quantity);
dataRecord.addField(field);
Map<HelperValues, String> additionalValues = Maps.newEnumMap(HelperValues.class);
additionalValues.put(HelperValues.FOR_OBSERVATION, null);
return CodingHelper.encodeObjectToXml(SweConstants.NS_SWE_20, dataRecord, additionalValues);
}
/**
* Create a TimePositionList XML object from time values
*
* @param sosObservation
* SOS observation
* @return XML TimePositionList object
* @throws OwsExceptionReport
* If an error occurs
*/
private TimePositionListDocument getTimePositionList(OmObservation sosObservation) throws OwsExceptionReport {
TimePositionListDocument timePositionListDoc = TimePositionListDocument.Factory.newInstance();
TimePositionListType timePositionList = timePositionListDoc.addNewTimePositionList();
timePositionList.setId("timepositionList_" + sosObservation.getObservationID());
if (sosObservation.getValue() instanceof SingleObservationValue<?>) {
timePositionList.setTimePositionList(Lists.newArrayList(getTimeString(sosObservation.getValue().getPhenomenonTime())));
} else if (sosObservation.getValue() instanceof MultiObservationValues<?>) {
timePositionList.setTimePositionList(getTimeArray((MultiObservationValues<?>) sosObservation.getValue()));
}
return timePositionListDoc;
}
/**
* Create a array from time values
*
* @param sosObservationValues
* SOS multi value observation object
* @return List with string representations of time values
* @throws OwsExceptionReport
* If an error occurs
*/
private List<String> getTimeArray(MultiObservationValues<?> sosObservationValues) throws OwsExceptionReport {
TVPValue tvpValue = (TVPValue) sosObservationValues.getValue();
List<TimeValuePair> timeValuePairs = tvpValue.getValue();
List<String> toList = Lists.newArrayListWithCapacity(timeValuePairs.size());
for (TimeValuePair timeValuePair : timeValuePairs) {
toList.add(getTimeString(timeValuePair.getTime()));
}
return toList;
}
/**
* Get a value list from SOS TimeValuePair objects
*
* @param timeValuePairs
* SOS TimeValuePair objects
* @return List with value objects
* @throws OwsExceptionReport
* If an error occurs
*/
private List<Object> getValueList(List<TimeValuePair> timeValuePairs) throws OwsExceptionReport {
ArrayList<Object> values = new ArrayList<Object>(timeValuePairs.size());
for (TimeValuePair timeValuePair : timeValuePairs) {
if (timeValuePair.getValue() != null
&& (timeValuePair.getValue() instanceof CountValue || timeValuePair.getValue() instanceof QuantityValue)) {
values.add(timeValuePair.getValue().getValue());
} else {
values.add("");
}
}
return values;
}
private XmlObject createMeasurementDomainRange(AbstractObservationValue<?> observationValue)
throws OwsExceptionReport {
if (!observationValue.isSetObservationType()
|| (observationValue.isSetObservationType() && isInvalidObservationType(observationValue
.getObservationType()))) {
return null;
}
MeasurementTimeseriesDomainRangeDocument xbMearuementTimeseriesDomainRangeDoc =
MeasurementTimeseriesDomainRangeDocument.Factory.newInstance();
MeasurementTimeseriesCoverageType xbMeasurementTimeseriesDomainRange =
xbMearuementTimeseriesDomainRangeDoc.addNewMeasurementTimeseriesDomainRange();
xbMeasurementTimeseriesDomainRange.setId("timeseries_" + observationValue.getObservationID());
// set time position list
xbMeasurementTimeseriesDomainRange.addNewDomainSet().set(getTimePositionList(observationValue));
// initialize unit
// AbstractPhenomenon observableProperty =
// observationValue.getObservableProperty();
String unit = "";
// create quantity list from values
QuantityListDocument quantityListDoc = QuantityListDocument.Factory.newInstance();
MeasureOrNilReasonListType quantityList = quantityListDoc.addNewQuantityList();
if (observationValue instanceof MultiObservationValues) {
TVPValue tvpValue = (TVPValue) ((MultiObservationValues<?>) observationValue).getValue();
List<TimeValuePair> timeValuePairs = tvpValue.getValue();
if (Strings.isNullOrEmpty(unit) && CollectionHelper.isNotEmpty(timeValuePairs)
&& timeValuePairs.get(0).getValue().isSetUnit()) {
unit = timeValuePairs.get(0).getValue().getUnit();
}
quantityList.setListValue(getValueList(timeValuePairs));
}
if (Strings.isNullOrEmpty(unit)) {
unit = OGCConstants.UNKNOWN;
}
quantityList.setUom(unit);
// set unit to SosObservableProperty if not set.
// if (observableProperty instanceof OmObservableProperty
// && !((OmObservableProperty) observableProperty).isSetUnit()) {
// ((OmObservableProperty) observableProperty).setUnit(unit);
// }
// set up range set
xbMeasurementTimeseriesDomainRange.addNewRangeSet().set(quantityListDoc);
// set up rangeType
xbMeasurementTimeseriesDomainRange.addNewRangeType().set(createDataRecord(observationValue, unit));
// set om:Result
return xbMearuementTimeseriesDomainRangeDoc;
}
private boolean isInvalidObservationType(String observationType) {
return !(OmConstants.OBS_TYPE_COUNT_OBSERVATION.equals(observationType)
|| OmConstants.OBS_TYPE_MEASUREMENT.equals(observationType)
|| OmConstants.OBS_TYPE_SWE_ARRAY_OBSERVATION.equals(observationType));
}
private XmlObject createDataRecord(AbstractObservationValue<?> observationValue, String unit)
throws OwsExceptionReport {
// AbstractPhenomenon observableProperty =
// sosObservation.getObservationConstellation().getObservableProperty();
SweDataRecord dataRecord = new SweDataRecord();
dataRecord.setIdentifier("datarecord_" + observationValue.getObservationID());
SweQuantity quantity = new SweQuantity();
quantity.setDefinition(observationValue.getObservableProperty());
quantity.setUom(unit);
SweField field = new SweField("observed_value", quantity);
dataRecord.addField(field);
Map<HelperValues, String> additionalValues = Maps.newEnumMap(HelperValues.class);
additionalValues.put(HelperValues.FOR_OBSERVATION, null);
return CodingHelper.encodeObjectToXml(SweConstants.NS_SWE_20, dataRecord, additionalValues);
}
private TimePositionListDocument getTimePositionList(AbstractObservationValue<?> observationValue)
throws OwsExceptionReport {
TimePositionListDocument timePositionListDoc = TimePositionListDocument.Factory.newInstance();
TimePositionListType timePositionList = timePositionListDoc.addNewTimePositionList();
timePositionList.setId("timepositionList_" + observationValue.getObservationID());
timePositionList.setTimePositionList(getTimeArray((MultiObservationValues<?>) observationValue));
return timePositionListDoc;
}
}