/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2007-2008, Open Source Geospatial Foundation (OSGeo) * * 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.geotools.gce.imagemosaic.catalog; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.TimeZone; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.spi.ImageReaderSpi; import org.apache.commons.io.FilenameUtils; import org.geotools.data.DataStore; import org.geotools.data.DataStoreFactorySpi; import org.geotools.data.DataUtilities; import org.geotools.data.FeatureWriter; import org.geotools.data.Query; import org.geotools.data.QueryCapabilities; import org.geotools.data.Transaction; import org.geotools.data.postgis.PostgisNGDataStoreFactory; import org.geotools.data.postgis.PostgisNGJNDIDataStoreFactory; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.data.simple.SimpleFeatureStore; import org.geotools.data.store.ContentFeatureSource; import org.geotools.factory.CommonFactoryFinder; import org.geotools.factory.GeoTools; import org.geotools.feature.FeatureIterator; import org.geotools.feature.SchemaException; import org.geotools.feature.collection.AbstractFeatureVisitor; import org.geotools.feature.visitor.FeatureCalc; import org.geotools.filter.visitor.DefaultFilterVisitor; import org.geotools.gce.imagemosaic.GranuleDescriptor; import org.geotools.gce.imagemosaic.ImageMosaicReader; import org.geotools.gce.imagemosaic.PathType; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.util.DefaultProgressListener; import org.geotools.util.Utilities; import org.opengis.feature.Feature; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.FeatureType; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory2; import org.opengis.filter.spatial.BBOX; import org.opengis.geometry.BoundingBox; /** * This class simply builds an SRTREE spatial index in memory for fast indexed * geometric queries. * * <p> * Since the {@link ImageMosaicReader} heavily uses spatial queries to find out * which are the involved tiles during mosaic creation, it is better to do some * caching and keep the index in memory as much as possible, hence we came up * with this index. * * @author Simone Giannecchini, S.A.S. * @author Stefan Alfons Krueger (alfonx), Wikisquare.de : Support for jar:file:foo.jar/bar.properties URLs * @since 2.5 * * @source $URL: http://svn.osgeo.org/geotools/trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/RasterManager.java $ */ class GTDataStoreGranuleCatalog extends AbstractGranuleCatalog { /** Logger. */ final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(GTDataStoreGranuleCatalog.class); /** * UTC timezone to serve as reference */ static final TimeZone UTC_TZ = TimeZone.getTimeZone("UTC"); final static FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( GeoTools.getDefaultHints() ); /** * Extracts a bbox from a filter in case there is at least one. * * I am simply looking for the BBOX filter but I am sure we could * use other filters as well. I will leave this as a todo for the moment. * * @author Simone Giannecchini, GeoSolutions SAS. * @todo TODO use other spatial filters as well */ @SuppressWarnings("deprecation") static class BBOXFilterExtractor extends DefaultFilterVisitor{ public ReferencedEnvelope getBBox() { return bbox; } private ReferencedEnvelope bbox; @Override public Object visit(BBOX filter, Object data) { final ReferencedEnvelope bbox= new ReferencedEnvelope( filter.getMinX(), filter.getMinY(), filter.getMaxX(), filter.getMaxY(), null); if(this.bbox!=null) this.bbox=(ReferencedEnvelope) this.bbox.intersection(bbox); else this.bbox=bbox; return super.visit(filter, data); } } private DataStore tileIndexStore; private String typeName; private String geometryPropertyName; private ReferencedEnvelope bounds; private DataStoreFactorySpi spi; private PathType pathType; private String locationAttribute; private ImageReaderSpi suggestedSPI; private String parentLocation; private boolean heterogeneous; public GTDataStoreGranuleCatalog( final Map<String, Serializable> params, final boolean create, final DataStoreFactorySpi spi) { Utilities.ensureNonNull("params",params); Utilities.ensureNonNull("spi",spi); this.spi=spi; try{ this.pathType=(PathType) params.get("PathType"); this.locationAttribute=(String)params.get("LocationAttribute"); final String temp=(String)params.get("SuggestedSPI"); this.suggestedSPI=temp!=null?(ImageReaderSpi) Class.forName(temp).newInstance():null; this.parentLocation=(String)params.get("ParentLocation"); Object heterogen = params.get("Heterogeneous"); if (heterogen != null){ this.heterogeneous = ((Boolean) heterogen).booleanValue(); } // creating a store, this might imply creating it for an existing underlying store or // creating a brand new one if(!create) tileIndexStore =spi.createDataStore(params); else { // this works only with the shapefile datastore, not with the others // therefore I try to catch the error to try and use themethdo without *New* try{ tileIndexStore = spi.createNewDataStore(params); }catch (UnsupportedOperationException e) { tileIndexStore = spi.createDataStore(params); } } // is this a new store? If so we do not set any properties if(create) return; // if this is not a new store let's extract basic properties from it if(spi instanceof PostgisNGJNDIDataStoreFactory||spi instanceof PostgisNGDataStoreFactory){ String typeName = FilenameUtils.getBaseName(FilenameUtils.getPathNoEndSeparator(this.parentLocation)); //if (typeName != null){ // typeName = typeName.toLowerCase(); //} extractBasicProperties(typeName); } else { extractBasicProperties(null); } } catch (Throwable e) { try { if(tileIndexStore!=null) tileIndexStore.dispose(); } catch (Throwable e1) { if (LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE, e1.getLocalizedMessage(), e1); } finally{ tileIndexStore=null; } throw new IllegalArgumentException(e); } } /** * If the underlying store has been disposed we throw an {@link IllegalStateException}. * <p> * We need to arrive here with at least a read lock! * @throws IllegalStateException in case the underlying store has been disposed. */ private void checkStore()throws IllegalStateException{ if (tileIndexStore == null) { throw new IllegalStateException("The index store has been disposed already."); } } private void extractBasicProperties(String typeName) throws IOException{ final String[] typeNames = tileIndexStore.getTypeNames(); if (typeNames==null||typeNames.length <= 0) throw new IllegalArgumentException( "BBOXFilterExtractor::extractBasicProperties(): Problems when opening the index," + " no typenames for the schema are defined"); if (typeName == null) { typeName = typeNames[0]; if (LOGGER.isLoggable(Level.WARNING)) LOGGER.warning("BBOXFilterExtractor::extractBasicProperties(): passed typename is null, using: " + typeName); } // loading all the features into memory to build an in-memory index. for (String type : typeNames) { if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine("BBOXFilterExtractor::extractBasicProperties(): Looking for type \'" + typeName + "\' in DataStore:getTypeNames(). Testing: \'" + type + "\'."); if (type.equalsIgnoreCase(typeName)) { if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine("BBOXFilterExtractor::extractBasicProperties(): SUCCESS -> type \'" + typeName + "\' is equalsIgnoreCase() to \'" + type + "\'."); this.typeName = type; break; } } final SimpleFeatureSource featureSource = tileIndexStore .getFeatureSource(this.typeName); if (featureSource != null) bounds = featureSource.getBounds(); else throw new IOException( "BBOXFilterExtractor::extractBasicProperties(): unable to get a featureSource for the qualified name" + this.typeName); final FeatureType schema = featureSource.getSchema(); if (schema != null) { geometryPropertyName = schema.getGeometryDescriptor().getLocalName(); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine("BBOXFilterExtractor::extractBasicProperties(): geometryPropertyName is set to \'" + geometryPropertyName + "\'."); } else { throw new IOException( "BBOXFilterExtractor::extractBasicProperties(): unable to get a schema from the featureSource"); } } private final ReadWriteLock rwLock= new ReentrantReadWriteLock(true); /* (non-Javadoc) * @see org.geotools.gce.imagemosaic.FeatureIndex#findFeatures(com.vividsolutions.jts.geom.Envelope) */ public List<GranuleDescriptor> getGranules(final BoundingBox envelope) throws IOException { Utilities.ensureNonNull("envelope",envelope); final Query q = new Query(typeName); Filter filter = ff.bbox( ff.property( geometryPropertyName ), ReferencedEnvelope.reference(envelope) ); q.setFilter(filter); return getGranules(q); } /* (non-Javadoc) * @see org.geotools.gce.imagemosaic.FeatureIndex#findFeatures(com.vividsolutions.jts.geom.Envelope, com.vividsolutions.jts.index.ItemVisitor) */ public void getGranules(final BoundingBox envelope, final GranuleCatalogVisitor visitor) throws IOException { Utilities.ensureNonNull("envelope",envelope); final Query q = new Query(typeName); Filter filter = ff.bbox( ff.property( geometryPropertyName ), ReferencedEnvelope.reference(envelope) ); q.setFilter(filter); getGranules(q,visitor); } public void dispose() { final Lock l=rwLock.writeLock(); try{ l.lock(); try { if(tileIndexStore!=null) tileIndexStore.dispose(); } catch (Throwable e) { if (LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE, e.getLocalizedMessage(), e); } finally{ tileIndexStore=null; } }finally{ l.unlock(); } } public int removeGranules(final Query query) { Utilities.ensureNonNull("query",query); final Lock lock=rwLock.writeLock(); try{ lock.lock(); // check if the index has been cleared checkStore(); SimpleFeatureStore fs=null; try{ // create a writer that appends this features fs = (SimpleFeatureStore) tileIndexStore.getFeatureSource(typeName); final int retVal=fs.getCount(query); fs.removeFeatures(query.getFilter()); //update bounds bounds=tileIndexStore.getFeatureSource(typeName).getBounds(); return retVal; } catch (Throwable e) { if(LOGGER.isLoggable(Level.SEVERE)) LOGGER.log(Level.SEVERE,e.getLocalizedMessage(),e); return -1; } // do your thing }finally{ lock.unlock(); } } public void addGranule(final SimpleFeature granule, final Transaction transaction) throws IOException { addGranules(Collections.singleton(granule),transaction); } public void addGranules(final Collection<SimpleFeature> granules, final Transaction transaction) throws IOException { Utilities.ensureNonNull("granuleMetadata",granules); final Lock lock=rwLock.writeLock(); try{ lock.lock(); // check if the index has been cleared checkStore(); FeatureWriter<SimpleFeatureType, SimpleFeature> fw =null; try{ // create a writer that appends this features fw = tileIndexStore.getFeatureWriterAppend(typeName,transaction); //add them all for(SimpleFeature f:granules){ // create a new feature final SimpleFeature feature = fw.next(); // get attributes and copy them over for(int i=f.getAttributeCount()-1;i>=0;i--){ Object attribute = f.getAttribute(i); // special case for postgis if(spi instanceof PostgisNGJNDIDataStoreFactory||spi instanceof PostgisNGDataStoreFactory) { final AttributeDescriptor descriptor = tileIndexStore.getSchema(typeName).getDescriptor(i); if(descriptor.getType().getBinding().equals(String.class)) { // escape the string correctly attribute=((String) attribute).replace("\\", "\\\\"); } if(descriptor.getType().getBinding().equals(Date.class)) { // escape the date correctly Calendar cal = Calendar.getInstance(UTC_TZ); cal.setTime(((Date) attribute)); attribute=cal.getTime(); } } feature.setAttribute(i, attribute); } //write down fw.write(); } } catch (Throwable e) { if(LOGGER.isLoggable(Level.SEVERE)) LOGGER.log(Level.SEVERE,e.getLocalizedMessage(),e); }finally{ if(fw!=null) fw.close(); } // do your thing //update bounds bounds=tileIndexStore.getFeatureSource(typeName).getBounds(Query.ALL); }finally{ lock.unlock(); } } public void getGranules(final Query q, final GranuleCatalogVisitor visitor) throws IOException { Utilities.ensureNonNull("q",q); final Lock lock=rwLock.readLock(); try{ lock.lock(); checkStore(); // // Load tiles informations, especially the bounds, which will be // reused // final SimpleFeatureSource featureSource = tileIndexStore.getFeatureSource(this.typeName); if (featureSource == null) throw new NullPointerException( "The provided SimpleFeatureSource is null, it's impossible to create an index!"); final SimpleFeatureCollection features = featureSource.getFeatures( q ); if (features == null) throw new NullPointerException( "The provided SimpleFeatureCollection is null, it's impossible to create an index!"); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine("Index Loaded"); //load the feature from the underlying datastore as needed final SimpleFeatureIterator it = features.features(); try{ if (!it.hasNext()) { if(LOGGER.isLoggable(Level.FINE)) LOGGER.fine("The provided SimpleFeatureCollection or empty, it's impossible to create an index!"); return ; } }finally{ it.close(); } final DefaultProgressListener listener= new DefaultProgressListener(); features.accepts( new AbstractFeatureVisitor(){ public void visit( Feature feature ) { if(feature instanceof SimpleFeature) { final SimpleFeature sf= (SimpleFeature) feature; // create the granule descriptor final GranuleDescriptor granule= new GranuleDescriptor( sf, suggestedSPI, pathType, locationAttribute, parentLocation, heterogeneous); visitor.visit(granule, null); // 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 for query "+q+" has been canceled"); } } } }, listener); } catch (Throwable e) { final IOException ioe= new IOException(); ioe.initCause(e); throw ioe; } finally{ lock.unlock(); } } public List<GranuleDescriptor> getGranules(final Query q) throws IOException { Utilities.ensureNonNull("q",q); FeatureIterator<SimpleFeature> it=null; final Lock lock=rwLock.readLock(); try{ lock.lock(); checkStore(); // // Load tiles informations, especially the bounds, which will be // reused // final SimpleFeatureSource featureSource = tileIndexStore.getFeatureSource(this.typeName); if (featureSource == null) throw new NullPointerException( "The provided SimpleFeatureSource is null, it's impossible to create an index!"); final SimpleFeatureCollection features = featureSource.getFeatures( q ); if (features == null) throw new NullPointerException( "The provided SimpleFeatureCollection is null, it's impossible to create an index!"); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine("Index Loaded"); //load the feature from the underlying datastore as needed it = features.features(); if (!it.hasNext()) { if(LOGGER.isLoggable(Level.FINE)) LOGGER.fine("The provided SimpleFeatureCollection or empty, it's impossible to create an index!"); return Collections.emptyList(); } // now build the index // TODO make it configurable as far the index is involved final ArrayList<GranuleDescriptor> retVal= new ArrayList<GranuleDescriptor>(features.size()); while (it.hasNext()) { // get the feature final SimpleFeature sf = it.next(); try { // create the granule descriptor final GranuleDescriptor granule = new GranuleDescriptor( sf, suggestedSPI, pathType, locationAttribute, parentLocation, heterogeneous); retVal.add(granule); } catch (Throwable t) { if(LOGGER.isLoggable(Level.SEVERE)) LOGGER.log(Level.SEVERE,"Skipping granule " + sf.toString(), t); } } return retVal; } catch (Throwable e) { throw new IllegalArgumentException(e); } finally{ lock.unlock(); if(it!=null) // closing he iterator to free some resources. it.close(); } } public Collection<GranuleDescriptor> getGranules()throws IOException { return getGranules(getBounds()); } public BoundingBox getBounds() { final Lock lock=rwLock.readLock(); try{ lock.lock(); checkStore(); return bounds; }finally{ lock.unlock(); } } public void createType(String namespace, String typeName, String typeSpec) throws IOException, SchemaException { Utilities.ensureNonNull("typeName",typeName); Utilities.ensureNonNull("typeSpec",typeSpec); final Lock lock=rwLock.writeLock(); try{ lock.lock(); checkStore(); final SimpleFeatureType featureType= DataUtilities.createType(namespace, typeName, typeSpec); tileIndexStore.createSchema(featureType); extractBasicProperties(featureType.getTypeName()); }finally{ lock.unlock(); } } public void createType(SimpleFeatureType featureType) throws IOException { Utilities.ensureNonNull("featureType",featureType); final Lock lock=rwLock.writeLock(); try{ lock.lock(); checkStore(); tileIndexStore.createSchema(featureType); extractBasicProperties(featureType.getTypeName()); }finally{ lock.unlock(); } } public void createType(String identification, String typeSpec) throws SchemaException, IOException { Utilities.ensureNonNull("typeSpec",typeSpec); Utilities.ensureNonNull("identification",identification); final Lock lock=rwLock.writeLock(); try{ lock.lock(); checkStore(); final SimpleFeatureType featureType= DataUtilities.createType(identification, typeSpec); tileIndexStore.createSchema(featureType); extractBasicProperties(featureType.getTypeName()); }finally{ lock.unlock(); } } public SimpleFeatureType getType() throws IOException { final Lock lock=rwLock.readLock(); try{ lock.lock(); checkStore(); return tileIndexStore.getSchema(typeName); }finally{ lock.unlock(); } } public void computeAggregateFunction(Query query, FeatureCalc function) throws IOException { final Lock lock=rwLock.readLock(); try{ lock.lock(); checkStore(); SimpleFeatureSource fs = tileIndexStore.getFeatureSource(query.getTypeName()); if(fs instanceof ContentFeatureSource) ((ContentFeatureSource)fs).accepts(query, function, null); else { final SimpleFeatureCollection collection = fs.getFeatures(query); collection.accepts(function, null); } }finally{ lock.unlock(); } } public QueryCapabilities getQueryCapabilities() { final Lock lock=rwLock.readLock(); try{ lock.lock(); checkStore(); return tileIndexStore.getFeatureSource(typeName).getQueryCapabilities(); } catch (IOException e) { if(LOGGER.isLoggable(Level.INFO)) LOGGER.log(Level.INFO,"Unable to collect QueryCapabilities",e); return null; }finally{ lock.unlock(); } } }