/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2014 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.catalog;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geoserver.catalog.CoverageView.CoverageBand;
import org.geotools.coverage.grid.io.DimensionDescriptor;
import org.geotools.coverage.grid.io.GranuleSource;
import org.geotools.coverage.grid.io.GranuleStore;
import org.geotools.coverage.grid.io.HarvestedSource;
import org.geotools.coverage.grid.io.StructuredGridCoverage2DReader;
import org.geotools.data.DataUtilities;
import org.geotools.data.Query;
import org.geotools.data.Transaction;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.factory.Hints;
import org.geotools.feature.SchemaException;
import org.geotools.feature.collection.AbstractFeatureVisitor;
import org.geotools.gce.imagemosaic.Utils;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.util.DefaultProgressListener;
import org.opengis.feature.Feature;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
/**
* A coverageView reader using a structured coverage readers implementation
*
* @author Daniele Romagnoli - GeoSolutions
*/
public class StructuredCoverageViewReader extends CoverageViewReader implements
StructuredGridCoverage2DReader {
private final static Logger LOGGER = org.geotools.util.logging.Logging
.getLogger(StructuredCoverageViewReader.class);
static class GranuleStoreView implements GranuleStore {
private StructuredGridCoverage2DReader reader;
private CoverageView coverageView;
private String name;
private boolean readOnly;
private SimpleFeatureType schema;
public GranuleStoreView(StructuredGridCoverage2DReader structuredDelegate,
String referenceName, CoverageView coverageView, boolean readOnly)
throws UnsupportedOperationException, IOException {
this.reader = structuredDelegate;
this.coverageView = coverageView;
this.name = referenceName;
this.readOnly = readOnly;
this.schema = buildSchema();
}
private SimpleFeatureType buildSchema() throws IOException {
GranuleSource source = reader.getGranules(name, readOnly);
SimpleFeatureType inputSchema = source.getSchema();
List<AttributeDescriptor> descriptors = inputSchema.getAttributeDescriptors();
StringBuilder builder = new StringBuilder();
for (AttributeDescriptor descriptor : descriptors) {
// Avoid exposing ImageIndex
if (!descriptor.getLocalName().equalsIgnoreCase("imageIndex")) {
Class<?> binding = descriptor.getType().getBinding();
String bindingClass = binding.toString();
if (bindingClass.startsWith("class ")) {
bindingClass = bindingClass.substring(6, bindingClass.length());
}
builder.append(descriptor.getName()).append(":").append(bindingClass)
.append(",");
}
}
String schema = builder.toString();
schema = schema.substring(0, schema.length() - 1);
try {
return DataUtilities.createType(coverageView.getName(), schema);
} catch (SchemaException e) {
throw new IOException("Exception occurred while creating the schemaType", e);
}
}
@Override
public SimpleFeatureCollection getGranules(Query q) throws IOException {
List<CoverageBand> bands = coverageView.getCoverageBands();
SimpleFeatureCollection collection = null;
for (CoverageBand band : bands) {
String coverageName = band.getInputCoverageBands().get(0).getCoverageName();
if (collection == null) {
collection = reader.getGranules(coverageName, readOnly).getGranules(q);
} else {
collection = join(collection, band, coverageName);
}
}
return collection;
}
private SimpleFeatureCollection join(SimpleFeatureCollection inputCollection,
CoverageBand band, final String coverageName) throws IOException {
// TODO Improve this by doing batch join and storing the result into memory
final DefaultProgressListener listener = new DefaultProgressListener();
final ListFeatureCollection collection = new ListFeatureCollection(schema);
// Getting attributes structure to be filled
inputCollection.accepts(new AbstractFeatureVisitor() {
public void visit(Feature feature) {
if (feature instanceof SimpleFeature) {
// get the feature
final SimpleFeature sourceFeature = (SimpleFeature) feature;
Collection<Property> props = sourceFeature.getProperties();
Name propName = null;
Object propValue = null;
// Assigning value to dest feature for matching attributes
Filter filter = null;
for (Property prop : props) {
propName = prop.getName();
if (
!propName.getLocalPart().equalsIgnoreCase("imageIndex")
&& !propName.getLocalPart().equalsIgnoreCase("the_geom")
&& !propName.getLocalPart().equalsIgnoreCase("location")) {
propValue = prop.getValue();
Filter updatedFilter = Utils.FF.equal(Utils.FF.property(propName),
Utils.FF.literal(propValue), true);
if (filter == null) {
filter = updatedFilter;
} else {
filter = FF.and(filter, updatedFilter);
}
}
}
Query query = new Query();
query.setFilter(filter);
SimpleFeatureCollection coverageCollection;
try {
coverageCollection = reader.getGranules(coverageName, readOnly)
.getGranules(query);
coverageCollection.accepts(new AbstractFeatureVisitor() {
public void visit(Feature feature) {
if (feature instanceof SimpleFeature) {
// get the feature
final SimpleFeature destFeature = DataUtilities
.template(schema);
Collection<Property> props = destFeature.getProperties();
Name propName = null;
Object propValue = null;
// Assigning value to dest feature for matching attributes
for (Property prop : props) {
propName = prop.getName();
propValue = ((SimpleFeature) feature)
.getAttribute(propName);
// Matching attributes are set
destFeature.setAttribute(propName, propValue);
}
collection.add(destFeature);
// check if something bad occurred
if (listener.isCanceled() || listener.hasExceptions()) {
if (listener.hasExceptions())
throw new RuntimeException(listener.getExceptions()
.peek());
else
throw new IllegalStateException(
"Feature visitor has been canceled");
}
}
}
}, listener);
} catch (IOException e) {
LOGGER.log(Level.FINER, e.getMessage(), e);
} catch (UnsupportedOperationException e) {
LOGGER.log(Level.FINER, e.getMessage(), e);
}
// check if something bad occurred
if (listener.isCanceled() || listener.hasExceptions()) {
if (listener.hasExceptions())
throw new RuntimeException(listener.getExceptions().peek());
else
throw new IllegalStateException("Feature visitor has been canceled");
}
}
}
}, listener);
return collection;
}
@Override
public int getCount(Query q) throws IOException {
return getGranules(q).size();
}
@Override
public ReferencedEnvelope getBounds(Query q) throws IOException {
return getGranules(q).getBounds();
}
@Override
public SimpleFeatureType getSchema() throws IOException {
return schema;
}
@Override
public void dispose() throws IOException {
// TODO: check if we need to dispose it or not
// Does nothing, the catalog should be disposed by the user
}
@Override
public void addGranules(SimpleFeatureCollection granules) {
// We deal with the one from the underlying reader
throw new UnsupportedOperationException();
}
@Override
public int removeGranules(Filter filter) {
List<CoverageBand> bands = coverageView.getCoverageBands();
int removed = 0;
for (CoverageBand band : bands) {
String coverageName = band.getInputCoverageBands().get(0).getCoverageName();
GranuleStore granuleStore;
try {
granuleStore = (GranuleStore) reader.getGranules(coverageName, false);
// TODO: We may revisit the #removed granules computation to take into
// account cases where we remove different number of records across different
// input coverages
removed = granuleStore.removeGranules(filter);
} catch (UnsupportedOperationException e) {
LOGGER.log(Level.FINER, e.getMessage(), e);
} catch (IOException e) {
LOGGER.log(Level.FINER, e.getMessage(), e);
}
}
return removed;
}
@Override
public void updateGranules(String[] attributeNames, Object[] attributeValues, Filter filter) {
throw new UnsupportedOperationException();
}
@Override
public Transaction getTransaction() {
return null;
}
@Override
public void setTransaction(Transaction transaction) {
}
}
private StructuredGridCoverage2DReader structuredDelegate;
public StructuredCoverageViewReader(StructuredGridCoverage2DReader delegate,
CoverageView coverageView, CoverageInfo coverageInfo, Hints hints) {
super(delegate, coverageView, coverageInfo, hints);
structuredDelegate = delegate;
}
@Override
public GranuleSource getGranules(String coverageName, boolean readOnly) throws IOException,
UnsupportedOperationException {
return new GranuleStoreView(structuredDelegate, referenceName, coverageView, readOnly);
}
@Override
public boolean isReadOnly() {
return structuredDelegate.isReadOnly();
}
@Override
public void createCoverage(String coverageName, SimpleFeatureType schema) throws IOException,
UnsupportedOperationException {
throw new UnsupportedOperationException("Operation unavailable for Coverage Views");
}
@Override
public boolean removeCoverage(String coverageName) throws IOException,
UnsupportedOperationException {
return removeCoverage(referenceName, false);
}
@Override
public boolean removeCoverage(String coverageName, boolean delete) throws IOException,
UnsupportedOperationException {
throw new UnsupportedOperationException("Operation unavailable for Coverage Views");
}
@Override
public void delete(boolean deleteData) throws IOException {
structuredDelegate.delete(deleteData);
}
@Override
public List<HarvestedSource> harvest(String defaultTargetCoverage, Object source, Hints hints)
throws IOException, UnsupportedOperationException {
return structuredDelegate.harvest(defaultTargetCoverage, source, hints);
}
@Override
public List<DimensionDescriptor> getDimensionDescriptors(String coverageName)
throws IOException {
return structuredDelegate.getDimensionDescriptors(referenceName);
}
}