/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2014, Geomatys
*
* 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.geotoolkit.data.om.xml;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import org.apache.sis.storage.DataStoreException;
import org.geotoolkit.data.AbstractFeatureStore;
import org.geotoolkit.data.FeatureReader;
import org.geotoolkit.data.FeatureWriter;
import org.geotoolkit.data.internal.GenericNameIndex;
import org.geotoolkit.data.query.DefaultQueryCapabilities;
import org.geotoolkit.data.query.Query;
import org.geotoolkit.data.query.QueryCapabilities;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.nio.IOUtilities;
import org.geotoolkit.util.NamesExt;
import org.geotoolkit.gml.GMLUtilities;
import org.geotoolkit.gml.xml.AbstractGeometry;
import org.geotoolkit.gml.xml.AbstractRing;
import org.geotoolkit.gml.xml.Envelope;
import org.geotoolkit.gml.xml.LineString;
import org.geotoolkit.gml.xml.Point;
import org.geotoolkit.gml.xml.Polygon;
import org.geotoolkit.observation.ObservationFilter;
import org.geotoolkit.observation.ObservationReader;
import org.geotoolkit.observation.ObservationStore;
import org.geotoolkit.observation.ObservationWriter;
import org.geotoolkit.data.om.OMFeatureTypes;
import static org.geotoolkit.data.om.xml.XmlObservationStoreFactory.FILE_PATH;
import org.geotoolkit.observation.xml.*;
import org.geotoolkit.observation.xml.Process;
import org.geotoolkit.sampling.xml.SamplingFeature;
import org.geotoolkit.sos.netcdf.ExtractionResult;
import org.geotoolkit.sos.netcdf.ExtractionResult.ProcedureTree;
import org.geotoolkit.sos.netcdf.GeoSpatialBound;
import org.geotoolkit.sos.xml.SOSMarshallerPool;
import org.geotoolkit.storage.DataFileStore;
import org.geotoolkit.storage.DataStoreFactory;
import org.geotoolkit.storage.DataStores;
import org.geotoolkit.swe.xml.PhenomenonProperty;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
import org.opengis.filter.Filter;
import org.opengis.filter.identity.FeatureId;
import org.opengis.util.GenericName;
import org.opengis.geometry.Geometry;
import org.opengis.observation.AnyFeature;
import org.opengis.observation.Observation;
import org.opengis.observation.ObservationCollection;
import org.opengis.observation.Phenomenon;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.temporal.Instant;
import org.opengis.temporal.Period;
import org.opengis.temporal.TemporalGeometricPrimitive;
import org.opengis.temporal.TemporalObject;
/**
*
* @author Guilhem Legal (Geomatys)
*/
public class XmlObservationStore extends AbstractFeatureStore implements DataFileStore,ObservationStore {
protected final GenericNameIndex<FeatureType> types;
private static final QueryCapabilities capabilities = new DefaultQueryCapabilities(false);
private final Path xmlFile;
public XmlObservationStore(final ParameterValueGroup params) throws IOException {
super(params);
xmlFile = Paths.get((URI) params.parameter(FILE_PATH.getName().toString()).getValue());
types = OMFeatureTypes.getFeatureTypes(IOUtilities.filenameWithoutExtension(xmlFile));
}
public XmlObservationStore(final Path xmlFile) {
super(null);
this.xmlFile = xmlFile;
types = OMFeatureTypes.getFeatureTypes(IOUtilities.filenameWithoutExtension(xmlFile));
}
@Override
public DataStoreFactory getFactory() {
return DataStores.getFactoryById(XmlObservationStoreFactory.NAME);
}
////////////////////////////////////////////////////////////////////////////
// FEATURE STORE ///////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/**
* {@inheritDoc }
*/
@Override
public Set<GenericName> getNames() throws DataStoreException {
return types.getNames();
}
/**
* {@inheritDoc }
*/
@Override
public FeatureType getFeatureType(final String typeName) throws DataStoreException {
typeCheck(typeName);
return types.get(typeName);
}
/**
* {@inheritDoc }
*/
@Override
public QueryCapabilities getQueryCapabilities() {
return capabilities;
}
/**
* {@inheritDoc }
*/
@Override
public void refreshMetaModel() {
}
@Override
public FeatureReader getFeatureReader(final Query query) throws DataStoreException {
final FeatureType sft = getFeatureType(query.getTypeName());
try {
return handleRemaining(new XmlFeatureReader(xmlFile,sft), query);
} catch (IOException | JAXBException ex) {
throw new DataStoreException(ex);
}
}
static Object unmarshallObservationFile(final Path f) throws JAXBException, IOException {
try (InputStream in = Files.newInputStream(f)) {
final Unmarshaller um = SOSMarshallerPool.getInstance().acquireUnmarshaller();
Object obj = um.unmarshal(in);
if (obj instanceof JAXBElement) {
obj = ((JAXBElement) obj).getValue();
}
if (obj != null) {
return obj;
}
throw new JAXBException("the observation file does not contain a valid O&M object");
}
}
/**
* {@inheritDoc }
*/
@Override
public void createFeatureType(final FeatureType featureType) throws DataStoreException {
throw new DataStoreException("Not Supported.");
}
/**
* {@inheritDoc }
*/
@Override
public void updateFeatureType(final FeatureType featureType) throws DataStoreException {
throw new DataStoreException("Not Supported.");
}
/**
* {@inheritDoc }
*/
@Override
public void deleteFeatureType(final String typeName) throws DataStoreException {
throw new DataStoreException("Not Supported.");
}
/**
* {@inheritDoc }
*/
@Override
public List<FeatureId> addFeatures(String groupName, Collection<? extends Feature> newFeatures, Hints hints) throws DataStoreException {
throw new DataStoreException("Not Supported.");
}
/**
* {@inheritDoc }
*/
@Override
public void updateFeatures(final String groupName, final Filter filter, final Map<String, ? extends Object> values) throws DataStoreException {
throw new DataStoreException("Not Supported.");
}
/**
* {@inheritDoc }
*/
@Override
public void removeFeatures(String groupName, Filter filter) throws DataStoreException {
throw new DataStoreException("Not Supported.");
}
/**
* {@inheritDoc }
*/
@Override
public FeatureWriter getFeatureWriter(Query query) throws DataStoreException {
throw new DataStoreException("Not Supported.");
}
////////////////////////////////////////////////////////////////////////////
// OBSERVATION STORE ///////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
@Override
public Set<GenericName> getProcedureNames() {
final Set<GenericName> names = new HashSet<>();
final Object obj = readFile();
if (obj instanceof ObservationCollection) {
final ObservationCollection collection = (ObservationCollection)obj;
for (Observation obs : collection.getMember()) {
final org.geotoolkit.observation.xml.Process process = (Process)obs.getProcedure();
names.add(NamesExt.create(process.getHref()));
}
} else if (obj instanceof Observation) {
final Observation obs = (Observation)obj;
final Process process = (Process)obs.getProcedure();
names.add(NamesExt.create(process.getHref()));
}
return names;
}
@Override
public ExtractionResult getResults() {
return getResults(null);
}
@Override
public ExtractionResult getResults(final String affectedSensorId, final List<String> sensorIDs) {
getLogger().warning("XMLObservation store does not allow to override sensor ID");
return getResults(sensorIDs);
}
@Override
public ExtractionResult getResults(final List<String> sensorIDs) {
final ExtractionResult result = new ExtractionResult();
result.spatialBound.initBoundary();
final Object obj = readFile();
if (obj instanceof ObservationCollection) {
final ObservationCollection collection = (ObservationCollection)obj;
for (Observation obs : collection.getMember()) {
final AbstractObservation o = (AbstractObservation)obs;
final ProcedureTree procedure = new ProcedureTree(o.getProcedure().getHref(), "Component");
if (sensorIDs == null || sensorIDs.contains(procedure.id)) {
if (!result.procedures.contains(procedure)) {
result.procedures.add(procedure);
}
final PhenomenonProperty phenProp = o.getPropertyObservedProperty();
final List<String> fields = XmlObservationUtils.getPhenomenonsFields(phenProp);
for (String field : fields) {
if (!result.fields.contains(field)) {
result.fields.add(field);
}
}
final Phenomenon phen = XmlObservationUtils.getPhenomenons(phenProp);
if (!result.phenomenons.contains(phen)) {
result.phenomenons.add(phen);
}
appendTime(obs.getSamplingTime(), result.spatialBound);
appendTime(obs.getSamplingTime(), procedure.spatialBound);
appendGeometry(obs.getFeatureOfInterest(), result.spatialBound);
appendGeometry(obs.getFeatureOfInterest(), procedure.spatialBound);
result.observations.add(o);
}
}
} else if (obj instanceof AbstractObservation) {
final AbstractObservation obs = (AbstractObservation)obj;
final ProcedureTree procedure = new ProcedureTree(obs.getProcedure().getHref(), "Component");
if (sensorIDs == null || sensorIDs.contains(procedure.id)) {
result.observations .add(obs);
final PhenomenonProperty phenProp = obs.getPropertyObservedProperty();
result.fields.addAll(XmlObservationUtils.getPhenomenonsFields(phenProp));
result.phenomenons.add(XmlObservationUtils.getPhenomenons(phenProp));
result.procedures.add(procedure);
appendTime(obs.getSamplingTime(), result.spatialBound);
appendTime(obs.getSamplingTime(), procedure.spatialBound);
appendGeometry(obs.getFeatureOfInterest(), result.spatialBound);
appendGeometry(obs.getFeatureOfInterest(), procedure.spatialBound);
}
}
return result;
}
@Override
public List<ProcedureTree> getProcedures() throws DataStoreException {
final List<ProcedureTree> result = new ArrayList<>();
final Object obj = readFile();
if (obj instanceof ObservationCollection) {
final ObservationCollection collection = (ObservationCollection)obj;
for (Observation obs : collection.getMember()) {
final AbstractObservation o = (AbstractObservation)obs;
final ProcedureTree procedure = new ProcedureTree(o.getProcedure().getHref(), "Component");
if (!result.contains(procedure)) {
result.add(procedure);
}
final PhenomenonProperty phenProp = o.getPropertyObservedProperty();
final List<String> fields = XmlObservationUtils.getPhenomenonsFields(phenProp);
for (String field : fields) {
if (!procedure.fields.contains(field)) {
procedure.fields.add(field);
}
}
appendTime(obs.getSamplingTime(), procedure.spatialBound);
appendGeometry(obs.getFeatureOfInterest(), procedure.spatialBound);
}
} else if (obj instanceof AbstractObservation) {
final AbstractObservation obs = (AbstractObservation)obj;
final ProcedureTree procedure = new ProcedureTree(obs.getProcedure().getHref(), "Component");
final PhenomenonProperty phenProp = obs.getPropertyObservedProperty();
procedure.fields.addAll(XmlObservationUtils.getPhenomenonsFields(phenProp));
result.add(procedure);
appendTime(obs.getSamplingTime(), procedure.spatialBound);
appendGeometry(obs.getFeatureOfInterest(), procedure.spatialBound);
}
return result;
}
private void appendTime(final TemporalObject time, final GeoSpatialBound spatialBound) {
if (time instanceof Instant) {
final Instant i = (Instant) time;
spatialBound.addDate(i.getDate());
} else if (time instanceof Period) {
final Period p = (Period) time;
spatialBound.addDate(p.getBeginning().getDate());
spatialBound.addDate(p.getEnding().getDate());
}
}
private void appendGeometry(final AnyFeature feature, final GeoSpatialBound spatialBound){
if (feature instanceof SamplingFeature) {
final SamplingFeature sf = (SamplingFeature) feature;
final Geometry geom = sf.getGeometry();
final AbstractGeometry ageom;
if (geom instanceof AbstractGeometry) {
ageom = (AbstractGeometry)geom;
} else if (geom != null) {
ageom = GMLUtilities.getGMLFromISO(geom);
} else {
ageom = null;
}
spatialBound.addGeometry(ageom);
spatialBound.addGeometry(ageom);
extractBoundary(ageom, spatialBound);
extractBoundary(ageom, spatialBound);
}
}
private void extractBoundary(final AbstractGeometry geom, final GeoSpatialBound spatialBound) {
if (geom instanceof Point) {
final Point p = (Point) geom;
if (p.getPos() != null) {
spatialBound.addXCoordinate(p.getPos().getOrdinate(0));
spatialBound.addYCoordinate(p.getPos().getOrdinate(1));
}
} else if (geom instanceof LineString) {
final LineString ls = (LineString) geom;
final Envelope env = ls.getBounds();
if (env != null) {
spatialBound.addXCoordinate(env.getMinimum(0));
spatialBound.addXCoordinate(env.getMaximum(0));
spatialBound.addYCoordinate(env.getMinimum(1));
spatialBound.addYCoordinate(env.getMaximum(1));
}
} else if (geom instanceof Polygon) {
final Polygon p = (Polygon) geom;
AbstractRing ext = p.getExterior().getAbstractRing();
// TODO
}
}
private Object readFile() {
try (InputStream fileStream = Files.newInputStream(xmlFile)){
final Unmarshaller um = SOSMarshallerPool.getInstance().acquireUnmarshaller();
Object obj = um.unmarshal(fileStream);
if (obj instanceof JAXBElement) {
obj = ((JAXBElement)obj).getValue();
}
SOSMarshallerPool.getInstance().recycle(um);
return obj;
} catch (IOException | JAXBException ex) {
getLogger().log(Level.WARNING, "Error while reading file", ex);
}
return null;
}
@Override
public void close() throws DataStoreException {
// do nothing
}
@Override
public Set<String> getPhenomenonNames() {
final Set<String> phenomenons = new HashSet<>();
final Object obj = readFile();
if (obj instanceof ObservationCollection) {
final ObservationCollection collection = (ObservationCollection)obj;
for (Observation obs : collection.getMember()) {
final AbstractObservation o = (AbstractObservation)obs;
final PhenomenonProperty phenProp = o.getPropertyObservedProperty();
phenomenons.addAll(XmlObservationUtils.getPhenomenonsFields(phenProp));
}
} else if (obj instanceof AbstractObservation) {
final AbstractObservation obs = (AbstractObservation)obj;
final PhenomenonProperty phenProp = obs.getPropertyObservedProperty();
phenomenons.addAll(XmlObservationUtils.getPhenomenonsFields(phenProp));
}
return phenomenons;
}
@Override
public TemporalGeometricPrimitive getTemporalBounds() {
final ExtractionResult result = new ExtractionResult();
result.spatialBound.initBoundary();
final Object obj = readFile();
if (obj instanceof ObservationCollection) {
final ObservationCollection collection = (ObservationCollection)obj;
for (Observation obs : collection.getMember()) {
appendTime(obs.getSamplingTime(), result.spatialBound);
}
} else if (obj instanceof AbstractObservation) {
final AbstractObservation obs = (AbstractObservation)obj;
appendTime(obs.getSamplingTime(), result.spatialBound);
}
return result.spatialBound.getTimeObject("2.0.0");
}
/**
* {@inheritDoc }
*/
@Override
public Path[] getDataFiles() throws DataStoreException {
return new Path[]{xmlFile};
}
/**
* {@inheritDoc }
*/
@Override
public ObservationReader getReader() {
final Object obj = readFile();
return new XmlObservationReader(Arrays.asList(obj));
}
/**
* {@inheritDoc }
*/
@Override
public ObservationFilter getFilter() {
throw new UnsupportedOperationException("Filtering is not supported on this observation store.");
}
/**
* {@inheritDoc }
*/
@Override
public ObservationWriter getWriter() {
throw new UnsupportedOperationException("Writing is not supported on this observation store.");
}
/**
* {@inheritDoc }
*/
@Override
public ObservationFilter cloneObservationFilter(ObservationFilter toClone) {
throw new UnsupportedOperationException("Filtering is not supported on this observation store.");
}
}