/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2013 - 2016, 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; import java.awt.image.ColorModel; import java.awt.image.IndexColorModel; import java.awt.image.SampleModel; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import javax.imageio.spi.ImageReaderSpi; import javax.media.jai.ImageLayout; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.geotools.coverage.grid.io.GranuleSource; import org.geotools.coverage.grid.io.GranuleStore; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.coverage.grid.io.StructuredGridCoverage2DReader; import org.geotools.coverage.grid.io.footprint.MultiLevelROIProvider; import org.geotools.data.DataSourceException; import org.geotools.data.DataStoreFactorySpi; import org.geotools.data.DataUtilities; import org.geotools.data.DefaultTransaction; import org.geotools.data.collection.ListFeatureCollection; import org.geotools.data.shapefile.ShapefileDataStoreFactory; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.factory.Hints; import org.geotools.factory.Hints.Key; import org.geotools.feature.collection.AbstractFeatureVisitor; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.gce.imagemosaic.Utils.Prop; import org.geotools.gce.imagemosaic.acceptors.DefaultGranuleAcceptorFactory; import org.geotools.gce.imagemosaic.acceptors.GranuleAcceptor; import org.geotools.gce.imagemosaic.acceptors.GranuleAcceptorFactorySPI; import org.geotools.gce.imagemosaic.acceptors.GranuleAcceptorFactorySPIFinder; import org.geotools.gce.imagemosaic.catalog.CatalogConfigurationBean; import org.geotools.gce.imagemosaic.catalog.GranuleCatalog; import org.geotools.gce.imagemosaic.catalog.GranuleCatalogFactory; import org.geotools.gce.imagemosaic.catalog.MultiLevelROIProviderMosaicFactory; import org.geotools.gce.imagemosaic.catalog.index.Indexer; import org.geotools.gce.imagemosaic.catalog.index.Indexer.Collectors; import org.geotools.gce.imagemosaic.catalog.index.Indexer.Collectors.Collector; import org.geotools.gce.imagemosaic.catalog.index.Indexer.Coverages.Coverage; import org.geotools.gce.imagemosaic.catalog.index.IndexerUtils; import org.geotools.gce.imagemosaic.catalog.index.ParametersType; import org.geotools.gce.imagemosaic.catalog.index.SchemaType; import org.geotools.gce.imagemosaic.catalog.index.SchemasType; import org.geotools.gce.imagemosaic.catalogbuilder.CatalogBuilderConfiguration; import org.geotools.gce.imagemosaic.catalogbuilder.MosaicBeanBuilder; import org.geotools.gce.imagemosaic.granulecollector.SubmosaicProducerFactory; import org.geotools.gce.imagemosaic.granulecollector.SubmosaicProducerFactoryFinder; import org.geotools.gce.imagemosaic.granulehandler.DefaultGranuleHandler; import org.geotools.gce.imagemosaic.granulehandler.GranuleHandler; import org.geotools.gce.imagemosaic.granulehandler.GranuleHandlerFactoryFinder; import org.geotools.gce.imagemosaic.granulehandler.GranuleHandlerFactorySPI; import org.geotools.gce.imagemosaic.granulehandler.GranuleHandlingException; import org.geotools.gce.imagemosaic.namecollector.DefaultCoverageNameCollectorSPI; import org.geotools.gce.imagemosaic.properties.CRSExtractor; import org.geotools.gce.imagemosaic.properties.DefaultPropertiesCollectorSPI; import org.geotools.gce.imagemosaic.properties.PropertiesCollector; import org.geotools.gce.imagemosaic.properties.PropertiesCollectorFinder; import org.geotools.gce.imagemosaic.properties.PropertiesCollectorSPI; import org.geotools.geometry.GeneralEnvelope; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.jdbc.JDBCDataStore; import org.geotools.referencing.CRS; import org.geotools.resources.coverage.CoverageUtilities; import org.geotools.util.DefaultProgressListener; import org.geotools.util.Utilities; 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.GeometryDescriptor; import org.opengis.feature.type.Name; import org.opengis.filter.Filter; import org.opengis.referencing.FactoryException; import org.opengis.referencing.NoSuchAuthorityCodeException; import org.opengis.referencing.ReferenceIdentifier; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import com.vividsolutions.jts.geom.Polygon; /** * This class is in responsible for creating and managing the catalog and the configuration of the mosaic * * @author Carlo Cancellieri - GeoSolutions SAS * */ public class ImageMosaicConfigHandler { /** Default Logger * */ final static Logger LOGGER = org.geotools.util.logging.Logging .getLogger(ImageMosaicConfigHandler.class); /* * Used to check if we can use memory mapped buffers safely. In case the OS cannot be detected, we act as if it was Windows and do not use memory * mapped buffers */ private final static Boolean USE_MEMORY_MAPPED_BUFFERS = !System .getProperty("os.name", "Windows").contains("Windows"); private List<PropertiesCollector> propertiesCollectors = null; private Map<String, MosaicConfigurationBean> configurations = new HashMap<>(); /** * Proper way to stop a thread is not by calling Thread.stop() but by using a shared variable that can be checked in order to notify a terminating * condition. */ private volatile boolean stop = false; protected GranuleCatalog catalog; private CatalogBuilderConfiguration runConfiguration; private ImageReaderSpi cachedReaderSPI; private ReferencedEnvelope imposedBBox; private ImageMosaicReader parentReader; private File indexerFile; private File parent; private ImageMosaicEventHandlers eventHandler; private boolean useExistingSchema; private List<GranuleAcceptor> granuleAcceptors = new ArrayList<>(); private GranuleHandler granuleHandler = new DefaultGranuleHandler(); private CoverageNameHandler coverageNameHandler = new CoverageNameHandler(new DefaultCoverageNameCollectorSPI()); /** * Default constructor * * @throws IllegalArgumentException */ public ImageMosaicConfigHandler(final CatalogBuilderConfiguration configuration, final ImageMosaicEventHandlers eventHandler) { Utilities.ensureNonNull("runConfiguration", configuration); Utilities.ensureNonNull("eventHandler", eventHandler); this.eventHandler = eventHandler; Indexer defaultIndexer = configuration.getIndexer(); ParametersType params = null; String rootMosaicDir = null; if (defaultIndexer != null) { params = defaultIndexer.getParameters(); rootMosaicDir = IndexerUtils.getParam(params, Prop.ROOT_MOSAIC_DIR); } Utilities.ensureNonNull("root location", rootMosaicDir); // look for and indexer.properties file parent = new File(rootMosaicDir); Indexer indexer = IndexerUtils.initializeIndexer(params, parent); if (indexer != null) { indexerFile = indexer.getIndexerFile(); } Hints hints = configuration.getHints(); String ancillaryFile = null; String datastoreFile = null; if (indexer != null) { // Overwrite default indexer only when indexer is available configuration.setIndexer(indexer); String auxiliaryFileParam = IndexerUtils.getParameter(Utils.Prop.AUXILIARY_FILE, indexer); if (auxiliaryFileParam != null) { ancillaryFile = auxiliaryFileParam; } String datastoreFileParam = IndexerUtils .getParameter(Utils.Prop.AUXILIARY_DATASTORE_FILE, indexer); if (datastoreFileParam != null) { datastoreFile = datastoreFileParam; } if (datastoreFileParam != null || auxiliaryFileParam != null) { setReader(hints, false); } if (IndexerUtils.getParameterAsBoolean(Utils.Prop.USE_EXISTING_SCHEMA, indexer)) { this.useExistingSchema = true; } } initializeGranuleAcceptors(indexer); initializeGranuleHandler(indexer); initializeCoverageNameHandler(indexer); updateConfigurationHints(configuration, hints, ancillaryFile, datastoreFile, IndexerUtils.getParam(params, Prop.ROOT_MOSAIC_DIR)); // check config configuration.check(); this.runConfiguration = new CatalogBuilderConfiguration(configuration); } /** * Initialize the list of granule collectors from the indexer. * * @param indexer the indexer configuration */ private void initializeGranuleAcceptors(Indexer indexer) { // initialized granuleAcceptors with empty list // do we wnat/need something different if (indexer != null) { String granuleAcceptorsString = IndexerUtils.getParameter(Prop.GRANULE_ACCEPTORS, indexer); if (granuleAcceptorsString != null && granuleAcceptorsString.length() > 0) { // parsing indicated granule acceptors Map<String, GranuleAcceptorFactorySPI> granuleAcceptorsMap = GranuleAcceptorFactorySPIFinder .getGranuleAcceptorFactorySPI(); Arrays.stream(granuleAcceptorsString.split(",")).forEach((factoryImpl) -> { if (granuleAcceptorsMap.containsKey(factoryImpl)) { granuleAcceptors.addAll(granuleAcceptorsMap.get(factoryImpl).create()); } }); } } // if we didn't find any granule acceptors in the index configuration, use the default if (granuleAcceptors.isEmpty()) { granuleAcceptors.addAll(new DefaultGranuleAcceptorFactory().create()); } } private void initializeGranuleHandler(Indexer indexer) { // we initialized at construction time with the default handler // ok, do we need/want something different? if (indexer != null) { String granuleHandlerString = IndexerUtils.getParameter(Prop.GEOMETRY_HANDLER, indexer); if (granuleHandlerString != null && granuleHandlerString.length() > 0) { GranuleHandlerFactorySPI factory = GranuleHandlerFactoryFinder .getGranuleHandlersSPI().get(granuleHandlerString); if (factory != null) { granuleHandler = factory.create(); } } } } private void initializeCoverageNameHandler(Indexer indexer) { // we initialized at construction time with the default handler // ok, do we need/want something different? if (indexer != null) { String coverageNameCollectorString = IndexerUtils.getParameter(Prop.COVERAGE_NAME_COLLECTOR_SPI, indexer); if (coverageNameCollectorString != null && coverageNameCollectorString.length() > 0) { //Override default handling machinery coverageNameHandler = new CoverageNameHandler(coverageNameCollectorString); } } } /** * Create or load a GranuleCatalog on top of the provided configuration * * @param runConfiguration configuration to be used * @param create if true create a new catalog, otherwise it is loaded * @return a new GranuleCatalog built from the configuration * @throws IOException */ private GranuleCatalog createCatalog(CatalogBuilderConfiguration runConfiguration, boolean create) throws IOException { // // create the index // // do we have a datastore.properties file? final File parent = new File(runConfiguration.getParameter(Prop.ROOT_MOSAIC_DIR)); GranuleCatalog catalog; // Consider checking that from the indexer if any final File datastoreProperties = new File(parent, "datastore.properties"); // GranuleCatalog catalog = null; if (Utils.checkFileReadable(datastoreProperties)) { // read the properties file Properties properties = createGranuleCatalogProperties(datastoreProperties); // pass the typename from the indexer, if one is available String indexerTypeName = runConfiguration.getParameter(Prop.TYPENAME); if (indexerTypeName != null && properties.getProperty(Prop.TYPENAME) == null) { properties.put(Prop.TYPENAME, indexerTypeName); } catalog = createGranuleCatalogFromDatastore(parent, properties, create, Boolean.parseBoolean(runConfiguration.getParameter(Prop.WRAP_STORE)), runConfiguration.getHints()); } else { // we do not have a datastore properties file therefore we continue with a shapefile datastore final URL file = new File(parent, runConfiguration.getParameter(Prop.INDEX_NAME) + ".shp").toURI().toURL(); final Properties params = new Properties(); params.put(ShapefileDataStoreFactory.URLP.key, file); if (file.getProtocol().equalsIgnoreCase("file")) { params.put(ShapefileDataStoreFactory.CREATE_SPATIAL_INDEX.key, Boolean.TRUE); } params.put(ShapefileDataStoreFactory.MEMORY_MAPPED.key, USE_MEMORY_MAPPED_BUFFERS); params.put(ShapefileDataStoreFactory.DBFTIMEZONE.key, TimeZone.getTimeZone("UTC")); params.put(Prop.LOCATION_ATTRIBUTE, runConfiguration.getParameter(Prop.LOCATION_ATTRIBUTE)); catalog = GranuleCatalogFactory.createGranuleCatalog(params, false, create, Utils.SHAPE_SPI, runConfiguration.getHints()); MultiLevelROIProvider roi = MultiLevelROIProviderMosaicFactory .createFootprintProvider(parent); catalog.setMultiScaleROIProvider(roi); } return catalog; } static Properties createGranuleCatalogProperties(File datastoreProperties) throws IOException { Properties properties = CoverageUtilities .loadPropertiesFromURL(DataUtilities.fileToURL(datastoreProperties)); if (properties == null) { throw new IOException( "Unable to load properties from:" + datastoreProperties.getAbsolutePath()); } return properties; } static GranuleCatalog createGranuleCatalogFromDatastore(File parent, File datastoreProperties, boolean create, Hints hints) throws IOException { return createGranuleCatalogFromDatastore(parent, datastoreProperties, create, false, hints); } /** * Create a granule catalog from a datastore properties file */ private static GranuleCatalog createGranuleCatalogFromDatastore(File parent, File datastoreProperties, boolean create, boolean wraps, Hints hints) throws IOException { Utilities.ensureNonNull("datastoreProperties", datastoreProperties); Properties properties = createGranuleCatalogProperties(datastoreProperties); return createGranuleCatalogFromDatastore(parent, properties, create, wraps, hints); } private static GranuleCatalog createGranuleCatalogFromDatastore(File parent, Properties properties, boolean create, boolean wraps, Hints hints) throws IOException { GranuleCatalog catalog = null; // SPI final String SPIClass = properties.getProperty("SPI"); try { // create a datastore as instructed final DataStoreFactorySpi spi = (DataStoreFactorySpi) Class.forName(SPIClass) .newInstance(); // set ParentLocation parameter since for embedded database like H2 we must change the database // to incorporate the path where to write the db properties.put("ParentLocation", DataUtilities.fileToURL(parent).toExternalForm()); if (wraps) { properties.put(Prop.WRAP_STORE, wraps); } catalog = GranuleCatalogFactory.createGranuleCatalog(properties, false, create, spi, hints); MultiLevelROIProvider rois = MultiLevelROIProviderMosaicFactory .createFootprintProvider(parent); catalog.setMultiScaleROIProvider(rois); } catch (Exception e) { final IOException ioe = new IOException(); throw (IOException) ioe.initCause(e); } return catalog; } /** * Create a {@link SimpleFeatureType} from the specified configuration. * * @param configurationBean * @param actualCRS CRS of the mosaic * @return the schema for the mosaic */ private SimpleFeatureType createSchema(CatalogBuilderConfiguration runConfiguration, String name, CoordinateReferenceSystem actualCRS) { SimpleFeatureType indexSchema = null; SchemaType schema = null; String schemaAttributes = null; Indexer indexer = runConfiguration.getIndexer(); if (indexer != null) { SchemasType schemas = indexer.getSchemas(); Coverage coverage = IndexerUtils.getCoverage(indexer, name); if (coverage != null) { schema = IndexerUtils.getSchema(indexer, coverage); } if (schema != null) { schemaAttributes = schema.getAttributes(); } else if (schemas != null) { List<SchemaType> schemaList = schemas.getSchema(); // CHECK THAT if (!schemaList.isEmpty()) { schemaAttributes = schemaList.get(0).getAttributes(); } } } if (schemaAttributes == null) { schemaAttributes = runConfiguration.getSchema(name); } if (schemaAttributes != null) { schemaAttributes = schemaAttributes.trim(); // get the schema try { indexSchema = DataUtilities.createType(name, schemaAttributes); // override the crs in case the provided one was wrong or absent indexSchema = DataUtilities.createSubType(indexSchema, DataUtilities.attributeNames(indexSchema), actualCRS); if (actualCRS != null) { Set<ReferenceIdentifier> identifiers = actualCRS.getIdentifiers(); if (identifiers == null || identifiers.isEmpty()) { Integer code = CRS.lookupEpsgCode(actualCRS, true); int nativeSrid = code == null ? 0 : code; GeometryDescriptor geometryDescriptor = indexSchema.getGeometryDescriptor(); if (geometryDescriptor != null) { Map<Object, Object> userData = geometryDescriptor.getUserData(); userData.put(JDBCDataStore.JDBC_NATIVE_SRID, nativeSrid); } } } } catch (Throwable e) { LOGGER.log(Level.FINE, e.getLocalizedMessage(), e); indexSchema = null; } } if (indexSchema == null) { // Proceed with default Schema final SimpleFeatureTypeBuilder featureBuilder = new SimpleFeatureTypeBuilder(); featureBuilder.setName(name); featureBuilder.setNamespaceURI("http://www.geo-solutions.it/"); featureBuilder.add(runConfiguration.getParameter(Prop.LOCATION_ATTRIBUTE).trim(), String.class); featureBuilder.add("the_geom", Polygon.class, actualCRS); featureBuilder.setDefaultGeometry("the_geom"); String timeAttribute = runConfiguration.getTimeAttribute(); addAttributes(timeAttribute, featureBuilder, Date.class); indexSchema = featureBuilder.buildFeatureType(); } return indexSchema; } /** * Add splitted attributes to the featureBuilder * * @param attribute * @param featureBuilder * @param classType TODO: Remove that once reworking on the dimension stuff */ private static void addAttributes(String attribute, SimpleFeatureTypeBuilder featureBuilder, Class classType) { if (attribute != null) { if (!attribute.contains(Utils.RANGE_SPLITTER_CHAR)) { featureBuilder.add(attribute, classType); } else { String[] ranges = attribute.split(Utils.RANGE_SPLITTER_CHAR); if (ranges.length != 2) { throw new IllegalArgumentException( "All ranges attribute need to be composed of a maximum of 2 elements:\n" + "As an instance (min;max) or (low;high) or (begin;end) , ..."); } else { featureBuilder.add(ranges[0], classType); featureBuilder.add(ranges[1], classType); } } } } /** * Get a {@link GranuleSource} related to a specific coverageName from an inputReader and put the related granules into a {@link GranuleStore} * related to the same coverageName of the mosaicReader. * * @param coverageName the name of the coverage to be managed * @param fileBeingProcessed the reference input file * @param inputReader the reader source of granules * @param mosaicReader the reader where to store source granules * @param configuration the configuration * @param envelope envelope of the granule being added * @param transaction transaction in progress * @param propertiesCollectors list of properties collectors to use * @throws IOException */ private void updateCatalog(final String coverageName, final File fileBeingProcessed, final GridCoverage2DReader inputReader, final ImageMosaicReader mosaicReader, final CatalogBuilderConfiguration configuration, final GeneralEnvelope envelope, final DefaultTransaction transaction, final List<PropertiesCollector> propertiesCollectors) throws IOException, GranuleHandlingException { // Retrieving the store and the destination schema final GranuleStore store = (GranuleStore) mosaicReader.getGranules(coverageName, false); if (store == null) { throw new IllegalArgumentException( "No valid granule store has been found for: " + coverageName); } final SimpleFeatureType indexSchema = store.getSchema(); final SimpleFeature feature = new ShapefileCompatibleFeature( DataUtilities.template(indexSchema)); store.setTransaction(transaction); final ListFeatureCollection collection = new ListFeatureCollection(indexSchema); final String fileLocation = prepareLocation(configuration, fileBeingProcessed); final String locationAttribute = configuration.getParameter(Prop.LOCATION_ATTRIBUTE); MosaicConfigurationBean mosaicConfiguration = this.getConfigurations().get(coverageName); GranuleHandler geometryHandler = this.getGeometryHandler(); // getting input granules if (inputReader instanceof StructuredGridCoverage2DReader) { // // Case A: input reader is a StructuredGridCoverage2DReader. We can get granules from a source // handleStructuredGridCoverage( ((StructuredGridCoverage2DReader) inputReader).getGranules(coverageName, true), fileBeingProcessed, inputReader, propertiesCollectors, indexSchema, feature, collection, fileLocation, locationAttribute, mosaicConfiguration); } else { // // Case B: old style reader, proceed with classic way, using properties collectors // geometryHandler.handleGranule(fileBeingProcessed, inputReader, feature, indexSchema, null, null, mosaicConfiguration); feature.setAttribute(locationAttribute, fileLocation); updateAttributesFromCollectors(feature, fileBeingProcessed, inputReader, propertiesCollectors); collection.add(feature); } // drop all the granules associated to the same Filter filter = Utils.FF.equal(Utils.FF.property(locationAttribute), Utils.FF.literal(fileLocation), !isCaseSensitiveFileSystem(fileBeingProcessed)); store.removeGranules(filter); // Add the granules collection to the store store.addGranules(collection); } private void handleStructuredGridCoverage(GranuleSource granules, final File fileBeingProcessed, final GridCoverage2DReader inputReader, final List<PropertiesCollector> propertiesCollectors, final SimpleFeatureType indexSchema, SimpleFeature feature, final ListFeatureCollection collection, final String fileLocation, final String locationAttribute, final MosaicConfigurationBean mosaicConfiguration) throws IOException { // Getting granule source and its input granules final GranuleSource source = granules; final SimpleFeatureCollection originCollection = source.getGranules(null); final DefaultProgressListener listener = new DefaultProgressListener(); // Getting attributes structure to be filled final Collection<Property> destProps = feature.getProperties(); final Set<Name> destAttributes = new HashSet<>(); for (Property prop : destProps) { destAttributes.add(prop.getName()); } // Collecting granules final GranuleHandler geometryHandler = this.granuleHandler; originCollection.accepts(new AbstractFeatureVisitor() { public void visit(Feature feature) { if (feature instanceof SimpleFeature) { // get the feature final SimpleFeature sourceFeature = (SimpleFeature) feature; final SimpleFeature destFeature = DataUtilities.template(indexSchema); Collection<Property> props = sourceFeature.getProperties(); Name propName = null; Object propValue = null; // Assigning value to dest feature for matching attributes for (Property prop : props) { Name geometryName = sourceFeature.getFeatureType().getGeometryDescriptor() .getName(); if (prop.getName().equals(geometryName)) { try { geometryHandler.handleGranule(fileBeingProcessed, (StructuredGridCoverage2DReader) inputReader, destFeature, destFeature.getFeatureType(), sourceFeature, sourceFeature.getFeatureType(), mosaicConfiguration); } catch (GranuleHandlingException e) { throw new RuntimeException( "Error handling structured coverage granule", e); } } else { propName = prop.getName(); propValue = prop.getValue(); // Matching attributes are set if (destAttributes.contains(propName)) { destFeature.setAttribute(propName, propValue); } } } // Set location destFeature.setAttribute(locationAttribute, fileLocation); // delegate remaining attributes set to properties collector updateAttributesFromCollectors(destFeature, fileBeingProcessed, inputReader, propertiesCollectors); 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); } private GranuleHandler getGeometryHandler() { return this.granuleHandler; } /** * Checks if the file system is case sensitive or not using File.exists (the only method that also works on OSX too according to * http://stackoverflow.com/questions/1288102/how-do-i-detect-whether-the-file-system-is-case-sensitive ) * * @param fileBeingProcessed * @return */ private static boolean isCaseSensitiveFileSystem(File fileBeingProcessed) { File loCase = new File(fileBeingProcessed.getParentFile(), fileBeingProcessed.getName().toLowerCase()); File upCase = new File(fileBeingProcessed.getParentFile(), fileBeingProcessed.getName().toUpperCase()); return loCase.exists() && upCase.exists(); } /** * Update feature attributes through properties collector * * @param feature * @param fileBeingProcessed * @param inputReader * @param propertiesCollectors */ private static void updateAttributesFromCollectors(final SimpleFeature feature, final File fileBeingProcessed, final GridCoverage2DReader inputReader, final List<PropertiesCollector> propertiesCollectors) { // collect and dump properties if (propertiesCollectors != null && propertiesCollectors.size() > 0) for (PropertiesCollector pc : propertiesCollectors) { pc.collect(fileBeingProcessed).collect(inputReader).setProperties(feature); pc.reset(); } } /** * Prepare the location on top of the configuration and file to be processed. * * @param runConfiguration * @param fileBeingProcessed * @return * @throws IOException */ private static String prepareLocation(CatalogBuilderConfiguration runConfiguration, final File fileBeingProcessed) throws IOException { // absolute if (Boolean.valueOf(runConfiguration.getParameter(Prop.ABSOLUTE_PATH))) { return fileBeingProcessed.getAbsolutePath(); } // relative String targetPath = fileBeingProcessed.getCanonicalPath(); String basePath = runConfiguration.getParameter(Prop.ROOT_MOSAIC_DIR); String relative = getRelativePath(targetPath, basePath, File.separator); // TODO: Remove this replace after fixing the quote escaping return relative; } /** * Get the relative path from one file to another, specifying the directory separator. If one of the provided resources does not exist, it is * assumed to be a file unless it ends with '/' or '\'. * * @param targetPath targetPath is calculated to this file * @param basePath basePath is calculated from this file * @param pathSeparator directory separator. The platform default is not assumed so that we can test Unix behaviour when running on Windows (for * example) * @return */ private static String getRelativePath(String targetPath, String basePath, String pathSeparator) { // Normalize the paths String normalizedTargetPath = FilenameUtils.normalizeNoEndSeparator(targetPath); String normalizedBasePath = FilenameUtils.normalizeNoEndSeparator(basePath); // Undo the changes to the separators made by normalization if (pathSeparator.equals("/")) { normalizedTargetPath = FilenameUtils.separatorsToUnix(normalizedTargetPath); normalizedBasePath = FilenameUtils.separatorsToUnix(normalizedBasePath); } else if (pathSeparator.equals("\\")) { normalizedTargetPath = FilenameUtils.separatorsToWindows(normalizedTargetPath); normalizedBasePath = FilenameUtils.separatorsToWindows(normalizedBasePath); } else { throw new IllegalArgumentException( "Unrecognised dir separator '" + pathSeparator + "'"); } String[] base = normalizedBasePath.split(Pattern.quote(pathSeparator)); String[] target = normalizedTargetPath.split(Pattern.quote(pathSeparator)); // First get all the common elements. Store them as a string, // and also count how many of them there are. StringBuilder common = new StringBuilder(); int commonIndex = 0; while (commonIndex < target.length && commonIndex < base.length && target[commonIndex].equals(base[commonIndex])) { common.append(target[commonIndex] + pathSeparator); commonIndex++; } if (commonIndex == 0) { // No single common path element. This most // likely indicates differing drive letters, like C: and D:. // These paths cannot be relativized. throw new RuntimeException("No common path element found for '" + normalizedTargetPath + "' and '" + normalizedBasePath + "'"); } // The number of directories we have to backtrack depends on whether the base is a file or a dir // For example, the relative path from // // /foo/bar/baz/gg/ff to /foo/bar/baz // // ".." if ff is a file // "../.." if ff is a directory // // The following is a heuristic to figure out if the base refers to a file or dir. It's not perfect, because // the resource referred to by this path may not actually exist, but it's the best I can do boolean baseIsFile = true; File baseResource = new File(normalizedBasePath); if (baseResource.exists()) { baseIsFile = baseResource.isFile(); } else if (basePath.endsWith(pathSeparator)) { baseIsFile = false; } StringBuilder relative = new StringBuilder(); if (base.length != commonIndex) { int numDirsUp = baseIsFile ? base.length - commonIndex - 1 : base.length - commonIndex; for (int i = 0; i < numDirsUp; i++) { relative.append(".." + pathSeparator); } } relative.append(normalizedTargetPath.substring(common.length())); return relative.toString(); } /** * Make sure a proper type name is specified in the catalogBean, it will be used to create the {@link GranuleCatalog} * * @param sourceURL * @param configuration * @throws IOException */ private static void checkTypeName(URL sourceURL, MosaicConfigurationBean configuration) throws IOException { CatalogConfigurationBean catalogBean = configuration.getCatalogConfigurationBean(); if (catalogBean.getTypeName() == null) { if (sourceURL.getPath().endsWith("shp")) { // In case we didn't find a typeName and we are dealing with a shape index, // we set the typeName as the shape name final File file = DataUtilities.urlToFile(sourceURL); catalogBean.setTypeName(FilenameUtils.getBaseName(file.getCanonicalPath())); } else { // use the default "mosaic" name catalogBean.setTypeName("mosaic"); } } } /** * Create a {@link GranuleCatalog} on top of the provided Configuration * * @param sourceURL * @param configuration * @param hints * @return * @throws IOException */ static GranuleCatalog createCatalog(final URL sourceURL, final MosaicConfigurationBean configuration, Hints hints) throws IOException { CatalogConfigurationBean catalogBean = configuration.getCatalogConfigurationBean(); // Check the typeName checkTypeName(sourceURL, configuration); if (hints != null && hints.containsKey(Hints.MOSAIC_LOCATION_ATTRIBUTE)) { final String hintLocation = (String) hints.get(Hints.MOSAIC_LOCATION_ATTRIBUTE); if (!catalogBean.getLocationAttribute().equalsIgnoreCase(hintLocation)) { throw new DataSourceException("wrong location attribute"); } } // Create the catalog GranuleCatalog catalog = GranuleCatalogFactory.createGranuleCatalog(sourceURL, catalogBean, null, hints); File parent = DataUtilities.urlToFile(sourceURL).getParentFile(); MultiLevelROIProvider rois = MultiLevelROIProviderMosaicFactory .createFootprintProvider(parent); catalog.setMultiScaleROIProvider(rois); return catalog; } private void setReader(Hints hints, final boolean updateHints) { if (hints != null && hints.containsKey(Utils.MOSAIC_READER)) { Object reader = hints.get(Utils.MOSAIC_READER); if (reader instanceof ImageMosaicReader) { if (getParentReader() == null) { setParentReader((ImageMosaicReader) reader); } if (updateHints) { Hints readerHints = getParentReader().getHints(); readerHints.add(hints); } } } } private void updateConfigurationHints(final CatalogBuilderConfiguration configuration, Hints hints, final String ancillaryFile, final String datastoreFile, final String rootMosaicDir) { final boolean isAbsolutePath = Boolean .parseBoolean(configuration.getParameter(Prop.ABSOLUTE_PATH)); hints = updateHints(ancillaryFile, isAbsolutePath, rootMosaicDir, configuration, hints, Utils.AUXILIARY_FILES_PATH); hints = updateHints(datastoreFile, isAbsolutePath, rootMosaicDir, configuration, hints, Utils.AUXILIARY_DATASTORE_PATH); setReader(hints, true); } private Hints updateHints(String filePath, boolean isAbsolutePath, String rootMosaicDir, CatalogBuilderConfiguration configuration, Hints hints, Key key) { String updatedFilePath = null; if (filePath != null) { if (isAbsolutePath && !filePath.startsWith(rootMosaicDir)) { updatedFilePath = rootMosaicDir + File.separatorChar + filePath; } else { updatedFilePath = filePath; } if (hints != null) { hints.put(key, updatedFilePath); } else { hints = new Hints(key, updatedFilePath); configuration.setHints(hints); } if (!isAbsolutePath) { hints.put(Utils.PARENT_DIR, rootMosaicDir); } } return hints; } /** * Perform proper clean up. * * <p> * Make sure to call this method when you are not running the {@link ImageMosaicConfigHandler} or bad things can happen. If it is running, please * stop it first. */ public void reset() { eventHandler.removeAllProcessingEventListeners(); // clear stop stop = false; // fileIndex = 0; runConfiguration = null; } public boolean getStop() { return stop; } public void stop() { stop = true; } void indexingPreamble() throws IOException { this.catalog = buildCatalog(); // // IMPOSED ENVELOPE // String bbox = runConfiguration.getParameter(Prop.ENVELOPE2D); try { this.imposedBBox = Utils.parseEnvelope(bbox); } catch (Exception e) { this.imposedBBox = null; if (LOGGER.isLoggable(Level.WARNING)) LOGGER.log(Level.WARNING, "Unable to parse imposed bbox", e); } // // load property collectors // loadPropertyCollectors(); } protected GranuleCatalog buildCatalog() throws IOException { GranuleCatalog catalog = createCatalog(runConfiguration, !useExistingSchema); getParentReader().granuleCatalog = catalog; return catalog; } /** * Load properties collectors from the configuration */ private void loadPropertyCollectors() { // load property collectors Indexer indexer = runConfiguration.getIndexer(); Collectors collectors = indexer.getCollectors(); // check whether this indexer allows heterogeneous CRS, then we know we need the CRS collector Boolean heterogeneousCRS = Boolean .valueOf(IndexerUtils.getParameter(Prop.HETEROGENEOUS_CRS, indexer)); if (collectors == null) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("No properties collector have been found"); } if (heterogeneousCRS) { this.propertiesCollectors = Collections.singletonList(new CRSExtractor()); } return; } List<Collector> collectorList = collectors.getCollector(); // load the SPI set final Set<PropertiesCollectorSPI> pcSPIs = PropertiesCollectorFinder .getPropertiesCollectorSPI(); // parse the string final List<PropertiesCollector> pcs = new ArrayList<>(); boolean hasCRSCollector = false; for (Collector collector : collectorList) { PropertiesCollectorSPI selectedSPI = null; final String spiName = collector.getSpi(); for (PropertiesCollectorSPI spi : pcSPIs) { if (spi.isAvailable() && spi.getName().equalsIgnoreCase(spiName)) { selectedSPI = spi; break; } } if (selectedSPI == null) { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.info( "Unable to find a PropertyCollector for this definition: " + spiName); } continue; } // property names String collectorValue = collector.getValue(); String config = null; if (collectorValue != null) { if (!collectorValue.startsWith(DefaultPropertiesCollectorSPI.REGEX_PREFIX)) { config = DefaultPropertiesCollectorSPI.REGEX_PREFIX + collector.getValue(); } else { config = collector.getValue(); } } // create the PropertiesCollector final PropertiesCollector pc = selectedSPI.create(config, Arrays.asList(collector.getMapped())); if (pc != null) { hasCRSCollector |= pc instanceof CRSExtractor; pcs.add(pc); } else { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.info("Unable to create PropertyCollector"); } } } if(heterogeneousCRS && !hasCRSCollector) { pcs.add(new CRSExtractor()); } this.propertiesCollectors = pcs; } void indexingPostamble(final boolean success) throws IOException { // close shapefile elements if (success) { Indexer indexer = runConfiguration.getIndexer(); boolean supportsEmpty = false; if (indexer != null) { supportsEmpty = IndexerUtils.getParameterAsBoolean(Prop.CAN_BE_EMPTY, indexer); } // complete initialization of mosaic configuration boolean haveConfigs = configurations != null && !configurations.isEmpty(); if (haveConfigs || supportsEmpty) { // We did found some MosaicConfigurations Set<String> keys = configurations.keySet(); int keySize = keys.size(); if (haveConfigs || !supportsEmpty) { final boolean useName = keySize > 1; for (String key : keys) { MosaicConfigurationBean mosaicConfiguration = configurations.get(key); RasterManager manager = parentReader.getRasterManager(key); manager.initialize(supportsEmpty); // create sample image if the needed elements are available createSampleImage(mosaicConfiguration, useName); eventHandler.fireEvent(Level.INFO, "Creating final properties file ", 99.9); createPropertiesFiles(mosaicConfiguration); } } final String base = FilenameUtils.getName(parent.getAbsolutePath()); // we create a root properties file if we have more than one coverage, or if the // one coverage does not have the default name if (supportsEmpty || keySize > 1 || (keySize > 0 && !base.equals(keys.iterator().next()))) { File mosaicFile = null; File originFile = null; if (indexerFile.getAbsolutePath().endsWith("xml")) { mosaicFile = new File(indexerFile.getAbsolutePath() .replace(IndexerUtils.INDEXER_XML, (base + ".xml"))); originFile = indexerFile; } else if (indexerFile.getAbsolutePath().endsWith("properties")) { mosaicFile = new File(indexerFile.getAbsolutePath() .replace(IndexerUtils.INDEXER_PROPERTIES, (base + ".properties"))); originFile = indexerFile; } else { final String source = runConfiguration.getParameter(Prop.ROOT_MOSAIC_DIR) + File.separatorChar + configurations.get(keys.iterator().next()).getName() + ".properties"; mosaicFile = new File(indexerFile.getAbsolutePath() .replace(IndexerUtils.INDEXER_PROPERTIES, (base + ".properties"))); originFile = new File(source); } if (!mosaicFile.exists()) { FileUtils.copyFile(originFile, mosaicFile); } } // processing information eventHandler.fireEvent(Level.FINE, "Done!!!", 100); } else { // processing information eventHandler.fireEvent(Level.FINE, "Nothing to process!!!", 100); } } else { // processing information eventHandler.fireEvent(Level.FINE, "Canceled!!!", 100); } } /** * Store a sample image frmo which we can derive the default SM and CM */ private void createSampleImage(final MosaicConfigurationBean mosaicConfiguration, final boolean useName) { // create a sample image to store SM and CM Utilities.ensureNonNull("mosaicConfiguration", mosaicConfiguration); String filePath = null; if (mosaicConfiguration.getSampleModel() != null && mosaicConfiguration.getColorModel() != null) { // sample image file // TODO: Consider revisit this using different name/folder final String baseName = runConfiguration.getParameter(Prop.ROOT_MOSAIC_DIR) + "/"; filePath = baseName + (useName ? mosaicConfiguration.getName() : "") + Utils.SAMPLE_IMAGE_NAME; try { Utils.storeSampleImage(new File(filePath), mosaicConfiguration.getSampleModel(), mosaicConfiguration.getColorModel()); } catch (IOException e) { eventHandler.fireEvent(Level.SEVERE, e.getLocalizedMessage(), 0); } } } /** * Creates the final properties file. */ private void createPropertiesFiles(MosaicConfigurationBean mosaicConfiguration) { // // FINAL STEP // // CREATING GENERAL INFO FILE // CatalogConfigurationBean catalogConfigurationBean = mosaicConfiguration .getCatalogConfigurationBean(); // envelope final Properties properties = new Properties(); properties.setProperty(Utils.Prop.ABSOLUTE_PATH, Boolean.toString(catalogConfigurationBean.isAbsolutePath())); properties.setProperty(Utils.Prop.LOCATION_ATTRIBUTE, catalogConfigurationBean.getLocationAttribute()); // Time final String timeAttribute = mosaicConfiguration.getTimeAttribute(); if (timeAttribute != null) { properties.setProperty(Utils.Prop.TIME_ATTRIBUTE, mosaicConfiguration.getTimeAttribute()); } // Elevation final String elevationAttribute = mosaicConfiguration.getElevationAttribute(); if (elevationAttribute != null) { properties.setProperty(Utils.Prop.ELEVATION_ATTRIBUTE, mosaicConfiguration.getElevationAttribute()); } // CRS final String crsAttribute = mosaicConfiguration.getCRSAttribute(); if (crsAttribute != null) { properties.setProperty(Utils.Prop.CRS_ATTRIBUTE, mosaicConfiguration.getCRSAttribute()); } // Additional domains final String additionalDomainAttribute = mosaicConfiguration .getAdditionalDomainAttributes(); if (additionalDomainAttribute != null) { properties.setProperty(Utils.Prop.ADDITIONAL_DOMAIN_ATTRIBUTES, mosaicConfiguration.getAdditionalDomainAttributes()); } final int numberOfLevels = mosaicConfiguration.getLevelsNum(); final double[][] resolutionLevels = mosaicConfiguration.getLevels(); properties.setProperty(Utils.Prop.LEVELS_NUM, Integer.toString(numberOfLevels)); final StringBuilder levels = new StringBuilder(); for (int k = 0; k < numberOfLevels; k++) { levels.append(Double.toString(resolutionLevels[k][0])).append(",") .append(Double.toString(resolutionLevels[k][1])); if (k < numberOfLevels - 1) { levels.append(" "); } } properties.setProperty(Utils.Prop.LEVELS, levels.toString()); properties.setProperty(Utils.Prop.NAME, mosaicConfiguration.getName()); String typeName = mosaicConfiguration.getCatalogConfigurationBean().getTypeName(); if (typeName == null) { typeName = mosaicConfiguration.getName(); } properties.setProperty(Utils.Prop.TYPENAME, typeName); properties.setProperty(Utils.Prop.EXP_RGB, Boolean.toString(mosaicConfiguration.isExpandToRGB())); properties.setProperty(Utils.Prop.CHECK_AUXILIARY_METADATA, Boolean.toString(mosaicConfiguration.isCheckAuxiliaryMetadata())); properties.setProperty(Utils.Prop.HETEROGENEOUS, Boolean.toString(catalogConfigurationBean.isHeterogeneous())); boolean wrapStore = catalogConfigurationBean.isWrapStore(); if (wrapStore) { // Avoid setting this property when false, since it's default properties.setProperty(Utils.Prop.WRAP_STORE, Boolean.toString(wrapStore)); } if (cachedReaderSPI != null) { // suggested spi properties.setProperty(Utils.Prop.SUGGESTED_SPI, cachedReaderSPI.getClass().getName()); } // write down imposed bbox if (imposedBBox != null) { properties.setProperty(Utils.Prop.ENVELOPE2D, imposedBBox.getMinX() + "," + imposedBBox.getMinY() + " " + imposedBBox.getMaxX() + "," + imposedBBox.getMaxY()); } properties.setProperty(Utils.Prop.CACHING, Boolean.toString(catalogConfigurationBean.isCaching())); if (mosaicConfiguration.getAuxiliaryFilePath() != null) { properties.setProperty(Utils.Prop.AUXILIARY_FILE, mosaicConfiguration.getAuxiliaryFilePath()); } if (mosaicConfiguration.getAuxiliaryDatastorePath() != null) { properties.setProperty(Utils.Prop.AUXILIARY_DATASTORE_FILE, mosaicConfiguration.getAuxiliaryDatastorePath()); } if (mosaicConfiguration.getCoverageNameCollectorSpi() != null){ properties.setProperty(Utils.Prop.COVERAGE_NAME_COLLECTOR_SPI, mosaicConfiguration.getCoverageNameCollectorSpi()); } if (mosaicConfiguration.getCrs() != null) { properties.setProperty(Prop.MOSAIC_CRS, CRS.toSRS(mosaicConfiguration.getCrs())); } OutputStream outStream = null; String filePath = runConfiguration.getParameter(Prop.ROOT_MOSAIC_DIR) + "/" // + runConfiguration.getIndexName() + ".properties")); + mosaicConfiguration.getName() + ".properties"; try { outStream = new BufferedOutputStream(new FileOutputStream(filePath)); properties.store(outStream, "-Automagically created from GeoTools-"); } catch (IOException e) { eventHandler.fireEvent(Level.SEVERE, e.getLocalizedMessage(), 0); } finally { if (outStream != null) { IOUtils.closeQuietly(outStream); } } } /** * Check whether the specified coverage already exist in the reader. This allows to get the rasterManager associated with that coverage instead of * creating a new one. * * @param coverageName the name of the coverage to be searched * @return {@code true} in case that coverage already exists * @throws IOException */ protected boolean coverageExists(String coverageName) throws IOException { String[] coverages = getParentReader().getGridCoverageNames(); for (String coverage : coverages) { if (coverage.equals(coverageName)) { return true; } } return false; } /** * Use the passed coverageReader to create or update the all the needed configurations<br/> * It not responsible of the passed coverageReader which should be disposed outside (in the caller). * * @param coverageReader * @param inputCoverageName * @param fileBeingProcessed * @param fileIndex * @param numFiles * @param transaction * @throws IOException * @throws FactoryException * @throws NoSuchAuthorityCodeException * @throws TransformException */ public void updateConfiguration(GridCoverage2DReader coverageReader, final String inputCoverageName, File fileBeingProcessed, int fileIndex, double numFiles, DefaultTransaction transaction) throws IOException, GranuleHandlingException, NoSuchAuthorityCodeException, FactoryException, TransformException { final String targetCoverageName = getTargetCoverageName(coverageReader, inputCoverageName); final Indexer indexer = getRunConfiguration().getIndexer(); // checking whether the coverage already exists final boolean coverageExists = coverageExists(targetCoverageName); MosaicConfigurationBean mosaicConfiguration = null; MosaicConfigurationBean currentConfigurationBean = null; RasterManager rasterManager = null; if (coverageExists) { // Get the manager for this coverage so it can be updated rasterManager = getParentReader().getRasterManager(targetCoverageName); mosaicConfiguration = rasterManager.getConfiguration(); this.configurations.put(mosaicConfiguration.getName(), mosaicConfiguration); } // STEP 2 // Collecting all Coverage properties to setup a MosaicConfigurationBean through // the builder final MosaicBeanBuilder configBuilder = new MosaicBeanBuilder(); final GeneralEnvelope envelope = coverageReader.getOriginalEnvelope(inputCoverageName); final CoordinateReferenceSystem actualCRS = coverageReader .getCoordinateReferenceSystem(inputCoverageName); SampleModel sm = null; ColorModel cm = null; int numberOfLevels = 1; double[][] resolutionLevels = null; CatalogBuilderConfiguration catalogConfig; Boolean heterogeneousCRS = Boolean.valueOf(IndexerUtils.getParameter(Prop.HETEROGENEOUS_CRS, indexer)); if (mosaicConfiguration == null) { catalogConfig = getRunConfiguration(); // We don't have a configuration for this configuration // Get the type specifier for this image and the check that the // image has the correct sample model and color model. // If this is the first cycle of the loop we initialize everything. // ImageLayout layout = coverageReader.getImageLayout(inputCoverageName); cm = layout.getColorModel(null); sm = layout.getSampleModel(null); // at the first step we initialize everything that we will // reuse afterwards starting with color models, sample // models, crs, etc.... configBuilder.setSampleModel(sm); configBuilder.setColorModel(cm); ColorModel defaultCM = cm; // Checking palette if (defaultCM instanceof IndexColorModel) { IndexColorModel icm = (IndexColorModel) defaultCM; byte[][] defaultPalette = Utils.extractPalette(icm); configBuilder.setPalette(defaultPalette); } // STEP 2.A // Preparing configuration String mosaicCrs = IndexerUtils.getParameter(Utils.Prop.MOSAIC_CRS, indexer); if (mosaicCrs != null) { configBuilder.setCrs(CRS.decode(mosaicCrs, true)); } else { configBuilder.setCrs(actualCRS); } // get/compute the resolution levels resolutionLevels = getResolutionLevels(coverageReader, inputCoverageName, configBuilder.getCrs()); numberOfLevels = resolutionLevels.length; configBuilder.setLevels(resolutionLevels); configBuilder.setLevelsNum(numberOfLevels); configBuilder.setName(targetCoverageName); configBuilder.setTimeAttribute( IndexerUtils.getAttribute(targetCoverageName, Utils.TIME_DOMAIN, indexer)); configBuilder.setCrsAttribute( IndexerUtils.getAttribute(targetCoverageName, Prop.CRS_ATTRIBUTE, indexer)); configBuilder.setElevationAttribute( IndexerUtils.getAttribute(targetCoverageName, Utils.ELEVATION_DOMAIN, indexer)); configBuilder.setAdditionalDomainAttributes(IndexerUtils .getAttribute(targetCoverageName, Utils.ADDITIONAL_DOMAIN, indexer)); final Hints runHints = getRunConfiguration().getHints(); if (runHints != null) { if (runHints.containsKey(Utils.AUXILIARY_FILES_PATH)) { String auxiliaryFilePath = (String) runHints.get(Utils.AUXILIARY_FILES_PATH); if (auxiliaryFilePath != null && auxiliaryFilePath.trim().length() > 0) { configBuilder.setAuxiliaryFilePath(auxiliaryFilePath); } } if (runHints.containsKey(Utils.AUXILIARY_DATASTORE_PATH)) { String auxiliaryDatastorePath = (String) runHints .get(Utils.AUXILIARY_DATASTORE_PATH); if (auxiliaryDatastorePath != null && auxiliaryDatastorePath.trim().length() > 0) { configBuilder.setAuxiliaryDatastorePath(auxiliaryDatastorePath); } } } final CatalogConfigurationBean catalogConfigurationBean = new CatalogConfigurationBean(); catalogConfigurationBean .setCaching(IndexerUtils.getParameterAsBoolean(Prop.CACHING, indexer)); catalogConfigurationBean.setAbsolutePath( IndexerUtils.getParameterAsBoolean(Prop.ABSOLUTE_PATH, indexer)); catalogConfigurationBean.setLocationAttribute( IndexerUtils.getParameter(Prop.LOCATION_ATTRIBUTE, indexer)); catalogConfigurationBean .setWrapStore(IndexerUtils.getParameterAsBoolean(Prop.WRAP_STORE, indexer)); String configuredTypeName = IndexerUtils.getParameter(Prop.TYPENAME, indexer); if (configuredTypeName != null) { catalogConfigurationBean.setTypeName(configuredTypeName); } else { catalogConfigurationBean.setTypeName(targetCoverageName); } configBuilder.setCatalogConfigurationBean(catalogConfigurationBean); configBuilder.setCheckAuxiliaryMetadata( IndexerUtils.getParameterAsBoolean(Prop.CHECK_AUXILIARY_METADATA, indexer)); currentConfigurationBean = configBuilder.getMosaicConfigurationBean(); if(heterogeneousCRS) { currentConfigurationBean.getCatalogConfigurationBean().setHeterogeneous(true); } // Creating a rasterManager which will be initialized after populating the catalog getParentReader().addRasterManager(currentConfigurationBean, false); // Creating a granuleStore if (!useExistingSchema) { // creating the schema SimpleFeatureType indexSchema = createSchema(getRunConfiguration(), currentConfigurationBean.getName(), configBuilder.getCrs()); getParentReader().createCoverage(targetCoverageName, indexSchema); } getConfigurations().put(currentConfigurationBean.getName(), currentConfigurationBean); } else { catalogConfig = new CatalogBuilderConfiguration(); CatalogConfigurationBean bean = mosaicConfiguration.getCatalogConfigurationBean(); catalogConfig.setParameter(Prop.LOCATION_ATTRIBUTE, (bean.getLocationAttribute())); catalogConfig.setParameter(Prop.ABSOLUTE_PATH, Boolean.toString(bean.isAbsolutePath())); catalogConfig.setParameter(Prop.ROOT_MOSAIC_DIR/* setRootMosaicDirectory( */, getRunConfiguration().getParameter(Prop.ROOT_MOSAIC_DIR)); // We already have a Configuration for this coverage. // Check its properties are compatible with the existing coverage. CatalogConfigurationBean catalogConfigurationBean = bean; // make sure we pick the same resolution irrespective of order of harvest resolutionLevels = getResolutionLevels(coverageReader, inputCoverageName, mosaicConfiguration.getCrs()); numberOfLevels = resolutionLevels.length; int originalNumberOfLevels = mosaicConfiguration.getLevelsNum(); boolean needUpdate = false; if (Utils.homogeneousCheck(Math.min(numberOfLevels, originalNumberOfLevels), resolutionLevels, mosaicConfiguration.getLevels())) { if (numberOfLevels != originalNumberOfLevels) { catalogConfigurationBean.setHeterogeneous(true); if (numberOfLevels > originalNumberOfLevels) { needUpdate = true; // pick the one with highest number of levels } } } else { catalogConfigurationBean.setHeterogeneous(true); if (isHigherResolution(resolutionLevels, mosaicConfiguration.getLevels())) { needUpdate = true; // pick the one with the highest resolution } } // configuration need to be updated if (needUpdate) { mosaicConfiguration.setLevels(resolutionLevels); mosaicConfiguration.setLevelsNum(numberOfLevels); getConfigurations().put(mosaicConfiguration.getName(), mosaicConfiguration); } } // STEP 3 if (!useExistingSchema) { // create and store features updateCatalog(targetCoverageName, fileBeingProcessed, coverageReader, getParentReader(), catalogConfig, envelope, transaction, getPropertiesCollectors()); } } private double[][] getResolutionLevels(GridCoverage2DReader coverageReader, final String inputCoverageName, CoordinateReferenceSystem mosaicCRS) throws IOException, FactoryException, TransformException { double[][] resolutionLevels; resolutionLevels = coverageReader.getResolutionLevels(inputCoverageName); final CoordinateReferenceSystem readerCRS = coverageReader.getCoordinateReferenceSystem(); if (mosaicCRS != null && readerCRS != null && !CRS.equalsIgnoreMetadata(mosaicCRS, readerCRS)) { resolutionLevels = transformResolutionLevels(resolutionLevels, readerCRS, mosaicCRS, coverageReader.getOriginalEnvelope()); } return resolutionLevels; } /** * Transforms the given resolution levels from a start CRS to a target one. * * @param resolutionLevels * @param fromCRS * @param toCRS * @param envelope * @return * @throws FactoryException * @throws TransformException */ private double[][] transformResolutionLevels(double[][] resolutionLevels, CoordinateReferenceSystem fromCRS, CoordinateReferenceSystem toCRS, GeneralEnvelope sourceEnvelope) throws FactoryException, TransformException { // prepare a set of points at middle of the envelope and their // corresponding offsets based on resolutions final int numLevels = resolutionLevels.length; double[] points = new double[numLevels * 8]; double baseX = sourceEnvelope.getMedian(0); double baseY = sourceEnvelope.getMedian(1); for (int i = 0, j = 0; i < numLevels; i++) { // delta x point points[j++] = baseX; points[j++] = baseY; points[j++] = baseX + resolutionLevels[i][0]; points[j++] = baseY; // delta y point points[j++] = baseX; points[j++] = baseY; points[j++] = baseX; points[j++] = baseY + resolutionLevels[i][1]; } // transform to get offsets in the target CRS MathTransform mt = CRS.findMathTransform(fromCRS, toCRS); mt.transform(points, 0, points, 0, numLevels * 4); // compute back the offsets double[][] result = new double[numLevels][2]; for (int i = 0; i < numLevels; i++) { result[i][0] = distance(points, i * 8); result[i][1] = distance(points, i * 8 + 4); } return result; } private double distance(double[] points, int base) { double dx = points[base + 2] - points[base]; double dy = points[base + 3] - points[base + 1]; return Math.sqrt(dx * dx + dy * dy); } /** * Get the name of the target coverage for a given reader. For most input coverages, the target coverage is simply the default coverage. For * structured coverages the target coverage has the same name as the input coverage. * * @param inputCoverageReader the coverage being added to the index * @param inputCoverageName the name of the input coverage * @return the target coverage name for the input coverage */ public String getTargetCoverageName(GridCoverage2DReader inputCoverageReader, String inputCoverageName) { Map<String, String> map = new HashMap<String, String>(); map.put(Prop.INDEX_NAME, getRunConfiguration().getParameter(Prop.INDEX_NAME)); map.put(Prop.INPUT_COVERAGE_NAME, inputCoverageName); return coverageNameHandler.getTargetCoverageName(inputCoverageReader, map); } private boolean isHigherResolution(double[][] a, double[][] b) { for (int i = 0; i < Math.min(a.length, b.length); i++) { for (int j = 0; i < Math.min(a[i].length, b[i].length); i++) { if (a[i][j] < b[i][j]) { return true; } else if (a[i][j] > b[i][j]) { return false; } } } return false; } public void dispose() { reset(); } public Map<String, MosaicConfigurationBean> getConfigurations() { return configurations; } public GranuleCatalog getCatalog() { return catalog; } public CatalogBuilderConfiguration getRunConfiguration() { return runConfiguration; } public ImageMosaicReader getParentReader() { return parentReader; } public void setParentReader(ImageMosaicReader parentReader) { this.parentReader = parentReader; } public List<PropertiesCollector> getPropertiesCollectors() { return propertiesCollectors; } public boolean isUseExistingSchema() { return useExistingSchema; } public ImageReaderSpi getCachedReaderSPI() { return cachedReaderSPI; } public void setCachedReaderSPI(ImageReaderSpi cachedReaderSPI) { this.cachedReaderSPI = cachedReaderSPI; } public List<GranuleAcceptor> getGranuleAcceptors() { return granuleAcceptors; } public RasterManager getRasterManagerForTargetCoverage(String targetCoverageName) { return this.getParentReader().getRasterManager(targetCoverageName); } }