/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2007-2015, 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.awt.geom.Rectangle2D; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Properties; 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 org.geotools.data.DataStoreFactorySpi; import org.geotools.data.Query; import org.geotools.data.QueryCapabilities; 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.FeatureCollection; import org.geotools.feature.SchemaException; import org.geotools.feature.visitor.FeatureCalc; import org.geotools.gce.imagemosaic.GranuleDescriptor; import org.geotools.gce.imagemosaic.ImageMosaicReader; import org.geotools.gce.imagemosaic.Utils; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.resources.coverage.FeatureUtilities; import org.geotools.util.Utilities; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.filter.Filter; import org.opengis.geometry.BoundingBox; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.index.ItemVisitor; import com.vividsolutions.jts.index.strtree.STRtree; /** * 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 * @version 10.0 * * @source $URL$ */ @SuppressWarnings("unused") class STRTreeGranuleCatalog extends GranuleCatalog { /** Logger. */ final static Logger LOGGER = org.geotools.util.logging.Logging .getLogger(STRTreeGranuleCatalog.class); private static class JTSIndexVisitorAdapter implements ItemVisitor { private GranuleCatalogVisitor adaptee; private Filter filter; private int maxGranules = -1; private int granuleIndex = 0; /** * @param indexLocation */ public JTSIndexVisitorAdapter(final GranuleCatalogVisitor adaptee) { this(adaptee, (Query) null); } public JTSIndexVisitorAdapter(final GranuleCatalogVisitor adaptee, Query q) { this.adaptee = adaptee; this.filter = q == null ? Query.ALL.getFilter() : q.getFilter(); this.maxGranules = q.getMaxFeatures(); } /** * @param indexLocation */ public JTSIndexVisitorAdapter(final GranuleCatalogVisitor adaptee, Filter filter) { this.adaptee = adaptee; this.filter = filter == null ? Query.ALL.getFilter() : filter; } /* * (non-Javadoc) * * @see com.vividsolutions.jts.index.ItemVisitor#visitItem(java.lang.Object) */ public void visitItem(Object o) { if (maxGranules > 0 && granuleIndex > maxGranules) { return; // Skip } if(adaptee.isVisitComplete()) { return; // Skipt } if (o instanceof GranuleDescriptor) { final GranuleDescriptor g = (GranuleDescriptor) o; final SimpleFeature originator = g.getOriginator(); if (originator != null && filter.evaluate(originator)) { adaptee.visit(g, null); granuleIndex++; } return; } throw new IllegalArgumentException("Unable to visit provided item" + o); } } private GranuleCatalog wrappedCatalogue; private String typeName; public STRTreeGranuleCatalog(final Properties params, DataStoreFactorySpi spi, final Hints hints) { super(hints); Utilities.ensureNonNull("params", params); this.wrappedCatalogue = new GTDataStoreGranuleCatalog(params, false, spi, hints); this.typeName = (String) params.get("TypeName"); if (typeName == null) { ((GTDataStoreGranuleCatalog) wrappedCatalogue).typeNames.iterator().next(); } } /** The {@link STRtree} index. */ private STRtree index; private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true); /** * Constructs a {@link STRTreeGranuleCatalog} out of a {@link FeatureCollection}. * * @param readLock * * @param features * @throws IOException */ private void checkIndex(Lock readLock) throws IOException { final Lock writeLock = rwLock.writeLock(); try { // upgrade the read lock to write lock readLock.unlock(); writeLock.lock(); // do your thing if (index == null) { if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine("No index exits and we create a new one."); createIndex(); } else if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine("Index does not need to be created..."); } finally { // get read lock again readLock.lock(); // leave write lock writeLock.unlock(); } } /** * This method shall only be called when the <code>indexLocation</code> is of protocol <code>file:</code> */ private void createIndex() { Iterator<GranuleDescriptor> it = null; final Collection<GranuleDescriptor> features = new ArrayList<GranuleDescriptor>(); // // Load tiles informations, especially the bounds, which will be // reused // try { wrappedCatalogue.getGranuleDescriptors(new Query(typeName), new GranuleCatalogVisitor() { @Override public void visit(GranuleDescriptor granule, SimpleFeature o) { features.add(granule); } }); 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 shapefile and create JTS index it = features.iterator(); // now build the index // TODO make it configurable as far the index is involved STRtree tree = new STRtree(); long size = 0; while (it.hasNext()) { final GranuleDescriptor granule = it.next(); final ReferencedEnvelope env = ReferencedEnvelope .reference(granule.getGranuleBBOX()); final Geometry g = FeatureUtilities.getPolygon(new Rectangle2D.Double(env.getMinX(), env.getMinY(), env.getWidth(), env.getHeight()), 0); tree.insert(g.getEnvelopeInternal(), granule); } // force index construction --> STRTrees are built on first call to // query tree.build(); // save the soft reference index = tree; } catch (Throwable e) { throw new IllegalArgumentException(e); } } /* * (non-Javadoc) * * @see org.geotools.gce.imagemosaic.FeatureIndex#findFeatures(com.vividsolutions.jts.geom.Envelope) */ @SuppressWarnings("unchecked") public List<GranuleDescriptor> getGranules(final BoundingBox envelope) throws IOException { Utilities.ensureNonNull("envelope", envelope); final Lock lock = rwLock.readLock(); try { lock.lock(); checkStore(); checkIndex(lock); return index.query(ReferencedEnvelope.reference(envelope)); } finally { lock.unlock(); } } /* * (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); Utilities.ensureNonNull("visitor", visitor); final Lock lock = rwLock.readLock(); try { lock.lock(); checkStore(); checkIndex(lock); index.query(ReferencedEnvelope.reference(envelope), new JTSIndexVisitorAdapter(visitor)); } finally { lock.unlock(); } } public void dispose() { final Lock l = rwLock.writeLock(); try { l.lock(); // original index if (wrappedCatalogue != null) { try { wrappedCatalogue.dispose(); } catch (Exception e) { if (LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE, e.getLocalizedMessage(), e); } } if (multiScaleROIProvider != null) { multiScaleROIProvider.dispose(); } } finally { index = null; multiScaleROIProvider = null; l.unlock(); } } @SuppressWarnings("unchecked") public SimpleFeatureCollection getGranules(Query q) throws IOException { q = mergeHints(q); Utilities.ensureNonNull("q", q); final Lock lock = rwLock.readLock(); try { lock.lock(); checkStore(); // get filter and check bbox final Filter filter = q.getFilter(); // try to combine the index bbox with the one that may come from the query. ReferencedEnvelope requestedBBox = extractAndCombineBBox(filter); // load what we need to load checkIndex(lock); final List<GranuleDescriptor> features = index.query(requestedBBox); final ListFeatureCollection retVal = new ListFeatureCollection( wrappedCatalogue.getType(typeName)); final int maxGranules = q.getMaxFeatures(); int numGranules = 0; for (GranuleDescriptor g : features) { // check how many tiles we are returning if (maxGranules > 0 && numGranules >= maxGranules) break; final SimpleFeature originator = g.getOriginator(); if (originator != null && filter.evaluate(originator)) retVal.add(originator); } return retVal; } finally { lock.unlock(); } } private ReferencedEnvelope extractAndCombineBBox(Filter filter) { final Utils.BBOXFilterExtractor bboxExtractor = new Utils.BBOXFilterExtractor(); filter.accept(bboxExtractor, null); ReferencedEnvelope requestedBBox = bboxExtractor.getBBox(); BoundingBox bbox = wrappedCatalogue.getBounds(typeName); // add eventual bbox from the underlying index to constrain search if (requestedBBox != null) { // intersection final Envelope intersection = requestedBBox .intersection(ReferencedEnvelope.reference(bbox)); // create intersection and return it requestedBBox = new ReferencedEnvelope(intersection, bbox.getCoordinateReferenceSystem()); } else { return ReferencedEnvelope.reference(bbox); } return requestedBBox; } public List<GranuleDescriptor> getGranules() throws IOException { return getGranules(this.getBounds(typeName)); } public void getGranuleDescriptors(Query q, GranuleCatalogVisitor visitor) throws IOException { Utilities.ensureNonNull("q", q); final Lock lock = rwLock.readLock(); try { lock.lock(); checkStore(); // get filter and check bbox final Filter filter = q.getFilter(); ReferencedEnvelope requestedBBox = extractAndCombineBBox(filter); // get filter and check bbox checkIndex(lock); index.query(requestedBBox, new JTSIndexVisitorAdapter(visitor, q)); } finally { lock.unlock(); } } public BoundingBox getBounds(String typeName) { final Lock lock = rwLock.readLock(); try { lock.lock(); checkStore(); return wrappedCatalogue.getBounds(typeName); } finally { lock.unlock(); } } /** * @throws IllegalStateException */ private void checkStore() throws IllegalStateException { if (wrappedCatalogue == null) throw new IllegalStateException("The underlying store has already been disposed!"); } @Override public SimpleFeatureType getType(final String typeName) throws IOException { final Lock lock = rwLock.readLock(); try { lock.lock(); checkStore(); return this.wrappedCatalogue.getType(typeName); } finally { lock.unlock(); } } @Override public String[] getTypeNames() { return typeName != null ? new String[] { typeName } : null; } public void computeAggregateFunction(Query query, FeatureCalc function) throws IOException { query = mergeHints(query); final Lock lock = rwLock.readLock(); try { lock.lock(); checkStore(); wrappedCatalogue.computeAggregateFunction(query, function); } finally { lock.unlock(); } } public QueryCapabilities getQueryCapabilities() { final Lock lock = rwLock.readLock(); try { lock.lock(); checkStore(); return wrappedCatalogue.getQueryCapabilities(typeName); } finally { lock.unlock(); } } @Override public int getGranulesCount(Query q) throws IOException { return wrappedCatalogue.getGranulesCount(mergeHints(q)); } @Override public void addGranule(String typeName, SimpleFeature granule, Transaction transaction) throws IOException { throw new UnsupportedOperationException("Unsupported operation"); } @Override public void addGranules(String typeName, Collection<SimpleFeature> granules, Transaction transaction) throws IOException { throw new UnsupportedOperationException("Unsupported operation"); } @Override public void createType(String namespace, String typeName, String typeSpec) throws IOException, SchemaException { throw new UnsupportedOperationException("Unsupported operation"); } @Override public void createType(SimpleFeatureType featureType) throws IOException { throw new UnsupportedOperationException("Unsupported operation"); } @Override public void createType(String identification, String typeSpec) throws SchemaException, IOException { throw new UnsupportedOperationException("Unsupported operation"); } @Override public QueryCapabilities getQueryCapabilities(String typeName) { throw new UnsupportedOperationException("Unsupported operation"); } @Override public int removeGranules(Query query) { throw new UnsupportedOperationException("Unsupported operation"); } @Override public void removeType(String typeName) throws IOException { final Lock lock = rwLock.readLock(); try { lock.lock(); checkStore(); this.wrappedCatalogue.removeType(typeName); } finally { lock.unlock(); } } @Override public void drop() throws IOException { final Lock lock = rwLock.writeLock(); try { lock.lock(); checkStore(); this.wrappedCatalogue.drop(); } catch (Exception e) { throw new IOException(e); } finally { lock.unlock(); } } }