/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wcs2_0; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import org.geotools.data.CollectionFeatureReader; import org.geotools.data.FeatureReader; import org.geotools.data.Query; import org.geotools.data.QueryCapabilities; import org.geotools.data.store.ContentDataStore; import org.geotools.data.store.ContentEntry; import org.geotools.data.store.ContentFeatureSource; import org.geotools.feature.NameImpl; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.filter.visitor.DefaultFilterVisitor; import org.geotools.geometry.Envelope2D; import org.geotools.geometry.jts.JTSUtils; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.CRS; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.Name; import org.opengis.filter.Filter; import org.opengis.filter.PropertyIsBetween; import org.opengis.filter.PropertyIsEqualTo; import org.opengis.filter.PropertyIsGreaterThanOrEqualTo; import org.opengis.filter.PropertyIsLessThanOrEqualTo; import org.opengis.filter.expression.Literal; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.sort.SortBy; import org.opengis.filter.spatial.BBOX; import org.opengis.filter.spatial.Intersects; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; /** * * A simple datastore containing 2 granules * * @author Jeroen Dries jeroen.dries@vito.be * */ public class MultiDimDataStore extends ContentDataStore { private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geoserver.wcs2_0"); private static final CoordinateReferenceSystem EPSG_4326; static { CoordinateReferenceSystem crs = DefaultGeographicCRS.WGS84; try { crs = CRS.decode("EPSG:4326"); } catch (FactoryException e) { LOGGER.log(Level.WARNING, e.getMessage(), e); } EPSG_4326 = crs; } private static final List<String> BAND_LIST = Arrays.asList("NDVI", "BLUE/TOC", "SWIR/VAA", "NIR/TOC", "RED/TOC", "SWIR/TOC", "VNIR/VAA"); /** * The 'dim_' prefix is mandatory as per the WMS spec, and also required by * geoserver */ private static final String BAND_DIMENSION = "BANDS"; private static final SimpleDateFormat PROBA_DATE_FORMAT = new SimpleDateFormat("dd/MM/yyyy"); static { PROBA_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); } private static final Date THE_DATE = new Date(); private static final ReferencedEnvelope TOTAL_BOUNDS = new ReferencedEnvelope(10.000, 55, 40, 70, EPSG_4326); private static final String FILE_LOCATION_ATTRIBUTE = "fileLocation"; private static final String LABEL_ATTRIBUTE = "label"; private static final String GEOMETRY_ATTRIBUTE = "geometry"; private static final String TIME_ATTRIBUTE = "timestamp"; private static final String TYPENAME = "FlexysCoverage"; private static final SimpleFeatureType FEATURE_TYPE = createSchema(); private SimpleFeature feature1; private SimpleFeature feature2; MultiDimDataStore(String parentLocation) { super(); GeometryFactory geometryBuilder = new GeometryFactory(); SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(FEATURE_TYPE); featureBuilder.add(geometryBuilder.toGeometry(new Envelope(10, 30, 40, 70))); featureBuilder.add(parentLocation + "/2DLatLonCoverage.nc"); featureBuilder.add(THE_DATE); featureBuilder.set(BAND_DIMENSION, "MyBand"); featureBuilder.set(LABEL_ATTRIBUTE, "X" + 0 + "Y" + 0); feature1 = featureBuilder.buildFeature("feature1"); featureBuilder = new SimpleFeatureBuilder(FEATURE_TYPE); featureBuilder.add(geometryBuilder.toGeometry(new Envelope(35, 55, 40, 70))); featureBuilder.add(parentLocation + "/2DLatLonCoverage2.nc"); featureBuilder.add(THE_DATE); featureBuilder.set(BAND_DIMENSION, "MyBand"); featureBuilder.set(LABEL_ATTRIBUTE, "X" + 0 + "Y" + 0); feature2 = featureBuilder.buildFeature("feature2"); } @Override public List<Name> createTypeNames() throws IOException { return Collections.singletonList(new NameImpl(TYPENAME)); } @Override public SimpleFeatureType getSchema(Name typeName) throws IOException { return FEATURE_TYPE; } private static SimpleFeatureType createSchema() { SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder(); builder.setName(TYPENAME); builder.setCRS(EPSG_4326); builder.setDefaultGeometry(GEOMETRY_ATTRIBUTE); builder.setNamespaceURI("VITOEOData"); builder.add(GEOMETRY_ATTRIBUTE, Geometry.class); builder.add(FILE_LOCATION_ATTRIBUTE, String.class); builder.add(TIME_ATTRIBUTE, Date.class); builder.add(BAND_DIMENSION, String.class); builder.add(LABEL_ATTRIBUTE,String.class); return builder.buildFeatureType(); } @Override public ContentFeatureSource createFeatureSource(ContentEntry entry) throws IOException { return new ContentFeatureSource(entry, Query.ALL) { { queryCapabilities = new QueryCapabilities() { @Override public boolean supportsSorting(SortBy[] sortAttributes) { if (sortAttributes != null && sortAttributes.length == 1) { if (sortAttributes[0].getPropertyName().getPropertyName().equals("timestamp")) { // sort by timestamp happens to be what we do by // default // TODO does the PDF support a sort order? return true; } } return super.supportsSorting(sortAttributes); } }; } // the image mosaic reader does not want this bounds to be null @Override public ReferencedEnvelope getBoundsInternal(Query query) throws IOException { return TOTAL_BOUNDS; } @Override protected int getCountInternal(Query query) throws IOException { if (query.getFilter() == Filter.INCLUDE) { //filtering not implemented int count = 0; try (FeatureReader<SimpleFeatureType, SimpleFeature> featureReader = getReaderInternal(query)) { while (featureReader.hasNext()) { featureReader.next(); count++; } } return count; } return -1; // feature by feature scan required to count records } @Override protected FeatureReader<SimpleFeatureType, SimpleFeature> getReaderInternal(Query query) throws IOException { if (query.getPropertyNames() != null && query.getPropertyNames().length == 1) { if (query.getPropertyNames()[0].equals(TIME_ATTRIBUTE)) { // geoserver is determining the time domain List<Date> availableTimes = Collections.singletonList(THE_DATE); List<SimpleFeature> features = new ArrayList<>(availableTimes.size()); int idCounter = 0; for (Date date : availableTimes) { SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(FEATURE_TYPE); featureBuilder.set(TIME_ATTRIBUTE, date); features.add(featureBuilder.buildFeature("dummyID" + idCounter++)); } return wrapAndCache(features); } if (query.getPropertyNames()[0].equals(BAND_DIMENSION)) { List<SimpleFeature> features = new ArrayList<>(BAND_LIST.size()); int idCounter = 0; for (String band : BAND_LIST) { SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(FEATURE_TYPE); featureBuilder.set(BAND_DIMENSION, band); features.add(featureBuilder.buildFeature("dummyID" + idCounter++)); } return wrapAndCache(features); } } try { final Date date = THE_DATE; date.setTime(0); final Date beginDate = THE_DATE; beginDate.setTime(0); final Date endDate = THE_DATE; endDate.setTime(0); final List<String> band = new ArrayList<>(); DefaultFilterVisitor filterVisitor = new DefaultFilterVisitor() { @Override public Object visit(BBOX filter, Object data) { Envelope2D envelope = (Envelope2D) data; filter.getBounds(); envelope.setBounds(filter.getBounds()); return super.visit(filter, data); } @Override public Object visit(Intersects filter, Object data) { Envelope2D envelope = (Envelope2D) data; Geometry polygon= ((Geometry)((Literal)filter.getExpression2()).getValue()); org.opengis.geometry.Geometry polygon2 = JTSUtils.jtsToGo1(polygon, envelope.getCoordinateReferenceSystem()); envelope.setBounds(new Envelope2D(polygon2.getEnvelope())); return super.visit(filter, data); } /** * Used by WCS 2.0 to select a time range */ @Override public Object visit(PropertyIsGreaterThanOrEqualTo filter, Object data) { PropertyName prop; Literal lit; if (filter.getExpression1() instanceof PropertyName && filter.getExpression2() instanceof Literal) { prop = (PropertyName) filter.getExpression1(); lit = (Literal) filter.getExpression2(); } else if (filter.getExpression2() instanceof PropertyName && filter.getExpression1() instanceof Literal) { prop = (PropertyName) filter.getExpression1(); lit = (Literal) filter.getExpression2(); } else { return super.visit(filter, data); } if (prop.getPropertyName().equals(TIME_ATTRIBUTE)) { if(lit.getValue()!=null){ beginDate.setTime(((Date) lit.getValue()).getTime()); } } return super.visit(filter, data); } /** * Used by WCS 2.0 to select a time range */ @Override public Object visit(PropertyIsLessThanOrEqualTo filter, Object data) { PropertyName prop; Literal lit; if (filter.getExpression1() instanceof PropertyName && filter.getExpression2() instanceof Literal) { prop = (PropertyName) filter.getExpression1(); lit = (Literal) filter.getExpression2(); } else if (filter.getExpression2() instanceof PropertyName && filter.getExpression1() instanceof Literal) { prop = (PropertyName) filter.getExpression1(); lit = (Literal) filter.getExpression2(); } else { return super.visit(filter, data); } if (prop.getPropertyName().equals(TIME_ATTRIBUTE)) { if(lit.getValue()!=null){ endDate.setTime(((Date) lit.getValue()).getTime()); } } return super.visit(filter, data); } @Override public Object visit(PropertyIsEqualTo filter, Object data) { PropertyName prop; Literal lit; if (filter.getExpression1() instanceof PropertyName && filter.getExpression2() instanceof Literal) { prop = (PropertyName) filter.getExpression1(); lit = (Literal) filter.getExpression2(); } else if (filter.getExpression2() instanceof PropertyName && filter.getExpression1() instanceof Literal) { prop = (PropertyName) filter.getExpression1(); lit = (Literal) filter.getExpression2(); } else { return super.visit(filter, data); } if (prop.getPropertyName().equals(TIME_ATTRIBUTE)) { date.setTime(((Date) lit.getValue()).getTime()); } if (prop.getPropertyName().equals(BAND_DIMENSION)) { band.add((String) lit.getValue()); } return super.visit(filter, data); } @Override public Object visit(PropertyIsBetween filter, Object data) { PropertyName prop = (PropertyName) filter.getExpression(); if (prop.getPropertyName().equals(TIME_ATTRIBUTE)) { beginDate.setTime(((Date) ((Literal)filter.getLowerBoundary()).getValue()).getTime()); endDate.setTime(((Date) ((Literal)filter.getUpperBoundary()).getValue()).getTime()); } if (prop.getPropertyName().equals(BAND_DIMENSION)) { String bands = (String) ((Literal)filter.getLowerBoundary()).getValue(); String[] singleBands = bands.split(","); band.addAll(Arrays.asList(singleBands)); } return super.visit(filter, data); } }; Envelope2D bbox = new Envelope2D(DefaultGeographicCRS.WGS84,-180,-90,360,180); query.getFilter().accept(filterVisitor, bbox); LOGGER.fine("Mosaic query on bbox: " + bbox); //very rudimentary filtering, for unit test only! if(bbox.getMaxX()<=35.){ return wrapAndCache(Collections.singletonList(feature1)); }else{ return wrapAndCache(Arrays.asList(feature1,feature2)); } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } @Override protected SimpleFeatureType buildFeatureType() throws IOException { return FEATURE_TYPE; } public ContentDataStore getDataStore() { return MultiDimDataStore.this; } public String toString() { return "AbstractDataStore.AbstractFeatureSource(" + TYPENAME + ")"; } }; } private FeatureReader<SimpleFeatureType, SimpleFeature> wrapAndCache(List<SimpleFeature> features) { return new CollectionFeatureReader(features, FEATURE_TYPE); } }