Java tutorial
/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2007-2013, 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.Rectangle; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.image.ColorModel; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import javax.media.jai.ImageLayout; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridCoverageFactory; import org.geotools.coverage.grid.GridEnvelope2D; import org.geotools.coverage.grid.io.AbstractGridFormat; import org.geotools.coverage.grid.io.DecimationPolicy; import org.geotools.coverage.grid.io.DefaultDimensionDescriptor; import org.geotools.coverage.grid.io.DimensionDescriptor; import org.geotools.coverage.grid.io.GranuleSource; import org.geotools.coverage.grid.io.GranuleStore; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.coverage.grid.io.GridFormatFinder; import org.geotools.coverage.grid.io.OverviewPolicy; import org.geotools.coverage.grid.io.StructuredGridCoverage2DReader; import org.geotools.data.DataUtilities; import org.geotools.data.Query; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.factory.Hints; import org.geotools.feature.visitor.CalcResult; import org.geotools.feature.visitor.FeatureCalc; import org.geotools.feature.visitor.MaxVisitor; import org.geotools.feature.visitor.MinVisitor; import org.geotools.feature.visitor.UniqueVisitor; import org.geotools.filter.SortByImpl; import org.geotools.gce.imagemosaic.OverviewsController.OverviewLevel; import org.geotools.gce.imagemosaic.catalog.CatalogConfigurationBean; import org.geotools.gce.imagemosaic.catalog.GranuleCatalog; import org.geotools.gce.imagemosaic.catalog.GranuleCatalogSource; import org.geotools.gce.imagemosaic.catalog.GranuleCatalogStore; import org.geotools.gce.imagemosaic.catalog.GranuleCatalogVisitor; import org.geotools.geometry.GeneralEnvelope; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.parameter.DefaultParameterDescriptor; import org.geotools.referencing.CRS; import org.geotools.referencing.operation.builder.GridToEnvelopeMapper; import org.geotools.referencing.operation.transform.AffineTransform2D; import org.geotools.referencing.operation.transform.IdentityTransform; import org.geotools.referencing.operation.transform.ProjectiveTransform; import org.geotools.resources.coverage.CoverageUtilities; import org.geotools.resources.coverage.FeatureUtilities; import org.geotools.resources.image.ImageUtilities; import org.geotools.util.Range; import org.geotools.util.Utilities; import org.opengis.coverage.grid.GridCoverage; import org.opengis.coverage.grid.GridEnvelope; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory2; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.sort.SortOrder; import org.opengis.geometry.BoundingBox; import org.opengis.geometry.Envelope; import org.opengis.metadata.Identifier; import org.opengis.parameter.GeneralParameterValue; import org.opengis.parameter.ParameterDescriptor; import org.opengis.referencing.FactoryException; import org.opengis.referencing.ReferenceIdentifier; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.datum.PixelInCell; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.MathTransform2D; import org.opengis.referencing.operation.TransformException; /** * * @author Simone Giannecchini, GeoSolutions SAS * @author Daniele Romagnoli, GeoSolutions SAS * */ @SuppressWarnings({ "rawtypes", "unchecked" }) public class RasterManager { final Hints excludeMosaicHints = new Hints(Utils.EXCLUDE_MOSAIC, true); /** * This class is responsible for putting together all the 2D spatial information needed for a certain raster. * * <p> * Notice that when this structure will be extended to work in ND this will become much more complex or as an * alternative a sibling TemporalDomainManager will be created. * * @author Simone Giannecchini, GeoSolutions SAS * */ static class SpatialDomainManager { /** The base envelope 2D */ ReferencedEnvelope coverageBBox; /** The CRS for the coverage */ CoordinateReferenceSystem coverageCRS; /** The CRS related to the base envelope 2D */ CoordinateReferenceSystem coverageCRS2D; // //////////////////////////////////////////////////////////////////////// // // Base coverage properties // // //////////////////////////////////////////////////////////////////////// /** The base envelope read from file */ GeneralEnvelope coverageEnvelope = null; double[] coverageFullResolution; /** WGS84 envelope 2D for this coverage */ ReferencedEnvelope coverageGeographicBBox; CoordinateReferenceSystem coverageGeographicCRS2D; MathTransform2D coverageGridToWorld2D; /** The base grid range for the coverage */ Rectangle coverageRasterArea; GridEnvelope gridEnvelope; public SpatialDomainManager(final GeneralEnvelope envelope, final GridEnvelope2D coverageGridrange, final CoordinateReferenceSystem crs, final MathTransform coverageGridToWorld2D, final OverviewsController overviewsController) throws TransformException, FactoryException { this.coverageEnvelope = envelope.clone(); this.gridEnvelope = coverageGridrange.clone(); this.coverageRasterArea = (Rectangle) gridEnvelope; this.coverageCRS = crs; this.coverageGridToWorld2D = (MathTransform2D) coverageGridToWorld2D; this.coverageFullResolution = new double[2]; final OverviewLevel highestLevel = overviewsController.resolutionsLevels.get(0); coverageFullResolution[0] = highestLevel.resolutionX; coverageFullResolution[1] = highestLevel.resolutionY; prepareCoverageSpatialElements(); } /** * Initialize the 2D properties (CRS and Envelope) of this coverage * * @throws TransformException * * @throws FactoryException * @throws TransformException * @throws FactoryException */ private void prepareCoverageSpatialElements() throws TransformException, FactoryException { // // basic initialization // coverageGeographicBBox = ImageUtilities.getWGS84ReferencedEnvelope(coverageEnvelope); coverageGeographicCRS2D = coverageGeographicBBox != null ? coverageGeographicBBox.getCoordinateReferenceSystem() : null; // // Get the original envelope 2d and its spatial reference system // coverageCRS2D = CRS.getHorizontalCRS(coverageCRS); assert coverageCRS2D.getCoordinateSystem().getDimension() == 2; if (coverageCRS.getCoordinateSystem().getDimension() != 2) { final MathTransform transform = CRS.findMathTransform(coverageCRS, (CoordinateReferenceSystem) coverageCRS2D); final GeneralEnvelope bbox = CRS.transform(transform, coverageEnvelope); bbox.setCoordinateReferenceSystem(coverageCRS2D); coverageBBox = new ReferencedEnvelope(bbox); } else { // it is already a bbox coverageBBox = new ReferencedEnvelope(coverageEnvelope); } } public MathTransform getOriginalGridToWorld(final PixelInCell pixInCell) { synchronized (this) { if (coverageGridToWorld2D == null) { final GridToEnvelopeMapper geMapper = new GridToEnvelopeMapper(gridEnvelope, coverageEnvelope); geMapper.setPixelAnchor(PixelInCell.CELL_CENTER); coverageGridToWorld2D = (MathTransform2D) geMapper.createTransform(); } } // we do not have to change the pixel datum if (pixInCell == PixelInCell.CELL_CENTER) return coverageGridToWorld2D; // we do have to change the pixel datum if (coverageGridToWorld2D instanceof AffineTransform) { final AffineTransform tr = new AffineTransform((AffineTransform) coverageGridToWorld2D); tr.concatenate(AffineTransform.getTranslateInstance(-0.5, -0.5)); return ProjectiveTransform.create(tr); } if (coverageGridToWorld2D instanceof IdentityTransform) { final AffineTransform tr = new AffineTransform(1, 0, 0, 1, 0, 0); tr.concatenate(AffineTransform.getTranslateInstance(-0.5, -0.5)); return ProjectiveTransform.create(tr); } throw new IllegalStateException("This reader's grid to world transform is invalud!"); } } /** Logger. */ private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(RasterManager.class); /** The coverage factory producing a {@link GridCoverage} from an image */ private GridCoverageFactory coverageFactory; /** * {@link DomainDescriptor} describe a single domain in terms of name and {@link ParameterDescriptor} that can be used to filter values during a * read operation. * * <p> * Notice that there is no caching of values for the domain itself right now. * * <p> * The domain must have unique identifiers. * * @author Simone Giannecchini, GeoSolutions SAS * */ class DomainDescriptor { static final String DOMAIN_SUFFIX = "_DOMAIN"; static final String HAS_PREFIX = "HAS_"; static final String DATATYPE_SUFFIX = "_DATATYPE"; private DomainType domainType = DomainType.SINGLE_VALUE; /** Unique identifier for this domain. */ private final String identifier; /** propertyName for this domain that tells me which Property from the underlying catalog provides values for it. */ private final String propertyName; /** additionalPropertyName for this domain. It won't be null ONLY in case of ranged domains. */ private final String additionalPropertyName; /** domain dataType */ private final String dataType; /** The {@link ParameterDescriptor} that can be used to filter on this domain during a read operation. */ private final DefaultParameterDescriptor<List> domainParameterDescriptor; /** * @return the identifier */ private String getIdentifier() { return identifier; } public boolean isHasRanges() { return additionalPropertyName != null; } public String getDataType() { return dataType; } /** * @return the domainaParameterDescriptor */ private DefaultParameterDescriptor<List> getDomainaParameterDescriptor() { return domainParameterDescriptor; } private DomainDescriptor(final String identifier, final DomainType domainType, final String dataType, final String propertyName, final String additionalPropertyName) { this.identifier = identifier; this.propertyName = propertyName; this.domainType = domainType; this.dataType = dataType; this.additionalPropertyName = additionalPropertyName; final String name = identifier.toUpperCase(); this.domainParameterDescriptor = DefaultParameterDescriptor.create(name, "Additional " + identifier + " domain", List.class, null, false); } @Override public String toString() { return "DomainDescriptor [identifier=" + identifier + ", propertyName=" + propertyName + ", dataType=" + dataType + ", additionalPropertyName=" + (additionalPropertyName != null ? additionalPropertyName : "__UNAVAILABLE__") + "]"; } /** * Extract the time domain extrema. * * @param extrema a {@link String} either TIME_DOMAIN_MAXIMUM or TIME_DOMAIN_MINIMUM. * * @return either TIME_DOMAIN_MAXIMUM or TIME_DOMAIN_MINIMUM as a {@link String}. * TODO use num for extrema */ private String getExtrema(String extrema) { try { String attribute = propertyName; // In case the domain has range, we will check the second element // in case we are looking for the maximum if (domainType != DomainType.SINGLE_VALUE && extrema.toLowerCase().endsWith("maximum")) { attribute = additionalPropertyName; } final FeatureCalc visitor = createExtremaQuery(extrema, attribute); // check result CalcResult tempRes = visitor.getResult(); if (tempRes == null) { throw new IllegalStateException("Unable to compute extrema value:" + extrema); } final Object result = tempRes.getValue(); if (result == null) { throw new IllegalStateException("Unable to compute extrema value:" + extrema); } return ConvertersHack.convert(result, String.class); } catch (IOException e) { if (LOGGER.isLoggable(Level.WARNING)) LOGGER.log(Level.WARNING, "Unable to compute extrema for TIME_DOMAIN", e); return null; } } /** * Retrieves the values for this domain * @return */ private String getValues() { if (domainType == DomainType.SINGLE_VALUE) { return getSingleValues(); } return getRangeValues(); } /** * Retrieves the Range values for this domain * @return */ private String getRangeValues() { try { Set<String> result = extractDomain(propertyName, additionalPropertyName, domainType); if (result.size() <= 0) { return ""; } final StringBuilder buff = new StringBuilder(); for (Iterator it = result.iterator(); it.hasNext();) { buff.append(ConvertersHack.convert(it.next(), String.class)); if (it.hasNext()) { buff.append(","); } } return buff.toString(); } catch (IOException e) { if (LOGGER.isLoggable(Level.WARNING)) LOGGER.log(Level.WARNING, "Unable to parse attribute: " + identifier, e); return ""; } } /** * Retrieves the single values list of this domain (no ranges available) * @return */ private String getSingleValues() { try { // implicit ordering final Set result = new TreeSet(extractDomain(propertyName)); // check result if (result.size() <= 0) { return ""; } final StringBuilder buff = new StringBuilder(); for (Iterator it = result.iterator(); it.hasNext();) { buff.append(ConvertersHack.convert(it.next(), String.class)); if (it.hasNext()) { buff.append(","); } } return buff.toString(); } catch (IOException e) { if (LOGGER.isLoggable(Level.WARNING)) LOGGER.log(Level.WARNING, "Unable to parse attribute: " + identifier, e); return ""; } } /** * This method is responsible for creating {@link Filter} that encompasses the * provided {@link List} of values for this {@link DomainManager}. * * @param values the {@link List} of values to use for building the containment {@link Filter}. * @return a {@link Filter} that encompasses the * provided {@link List} of values for this {@link DomainManager}. */ private Filter createFilter(List values) { // === create the filter // loop values and AND them final int size = values.size(); final List<Filter> filters = new ArrayList<Filter>(); FilterFactory2 ff = FeatureUtilities.DEFAULT_FILTER_FACTORY; for (int i = 0; i < size; i++) { // checks Object value = values.get(i); if (value == null) { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.info("Ignoring null date for the filter:" + this.identifier); } continue; } if (domainType == DomainType.SINGLE_VALUE) { // Domain made of single values if (value instanceof Range) { // RANGE final Range range = (Range) value; filters.add(ff.and( ff.lessOrEqual(ff.property(propertyName), ff.literal(range.getMaxValue())), ff.greaterOrEqual(ff.property(propertyName), ff.literal(range.getMinValue())))); } else { // SINGLE value filters.add(ff.equal(ff.property(propertyName), ff.literal(value), true)); } } else { //domainType == DomainType.RANGE // Domain made of ranges such as (beginTime,endTime) , (beginElevation,endElevation) , ... if (value instanceof Range) { // RANGE final Range range = (Range) value; final Comparable maxValue = range.getMaxValue(); final Comparable minValue = range.getMinValue(); if (maxValue.compareTo(minValue) != 0) { // logic comes from Range.intersectsNC(Range) // in summary, requestedMax > min && requestedMin < max Filter maxCondition = ff.greaterOrEqual(ff.literal(maxValue), ff.property(propertyName)); Filter minCondition = ff.lessOrEqual(ff.literal(minValue), ff.property(additionalPropertyName)); filters.add(ff.and(Arrays.asList(maxCondition, minCondition))); continue; } else { value = maxValue; } } filters.add(ff.and(ff.lessOrEqual(ff.property(propertyName), ff.literal(value)), ff.greaterOrEqual(ff.property(additionalPropertyName), ff.literal(value)))); } } return ff.or(filters); } } /** * An {@link DomainManager} class which allows to deal with additional domains * (if any) defined inside the mosaic. It provides DOMAIN_ALIAS <--to--> original attribute mapping * capabilities, metadata retrieval, filter creation, and domain support check * * @author Daniele Romagnoli, GeoSolutions SAS. */ class DomainManager { private final Map<String, DomainDescriptor> domainsMap = new HashMap<String, DomainDescriptor>(); private final List<DimensionDescriptor> dimensions = new ArrayList<DimensionDescriptor>(); private final boolean attributeHasRange(String attribute) { return attribute.contains(Utils.RANGE_SPLITTER_CHAR); } DomainManager(Map<String, String> additionalDomainAttributes, SimpleFeatureType simpleFeatureType) { Utilities.ensureNonNull("additionalDomainAttributes", additionalDomainAttributes); Utilities.ensureNonNull("simpleFeatureType", simpleFeatureType); init(additionalDomainAttributes, simpleFeatureType); } /** * @param domainAttributes * @param simpleFeatureType * @throws IllegalArgumentException */ private void init(Map<String, String> domainAttributes, SimpleFeatureType simpleFeatureType) throws IllegalArgumentException { for (java.util.Map.Entry<String, String> entry : domainAttributes.entrySet()) { DomainType domainType = DomainType.SINGLE_VALUE; final String domainName = entry.getKey(); String propertyName = entry.getValue(); // is the name equals to the propertyname? try { // Domain with ranges management if (attributeHasRange(propertyName)) { domainType = domainAttributes.containsKey(Utils.TIME_DOMAIN) ? DomainType.TIME_RANGE : DomainType.NUMBER_RANGE; addDomain(domainName, propertyName, domainType, simpleFeatureType); continue; } else { propertyName = extractAttributes(propertyName); if (simpleFeatureType.getDescriptor(propertyName) != null) { // add addDomain(domainName, propertyName, domainType, simpleFeatureType); // continue continue; } } } catch (Exception e) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, e.getLocalizedMessage(), e); } } // ok why we don't have it? Maybe shapefile name truncation? if (propertyName.length() > 10) { // hakc for shapes propertyName = propertyName.substring(0, 10); // alias in provided type try { if (simpleFeatureType.getDescriptor(propertyName) != null) { // add addDomain(domainName, propertyName, domainType, simpleFeatureType); // continue continue; } } catch (Exception e) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, e.getLocalizedMessage(), e); } } } // if I got here, we are in trouble. No way to add this param throw new IllegalArgumentException("Unable to add this domain:" + domainName + "-" + propertyName); } } /** * build an AdditionalDomainManager on top of the provided additionalDomainAttributes (a comma separated list of attribute names). * * @param additionalDomainAttributes * @param simpleFeatureType */ DomainManager(String additionalDomainAttributes, SimpleFeatureType simpleFeatureType) { Utilities.ensureNonNull("additionalDomainAttributes", additionalDomainAttributes); Utilities.ensureNonNull("simpleFeatureType", simpleFeatureType); final Map<String, String> domainPairs = new HashMap<String, String>(); // split, looking for multiple values final String[] additionalDomainsNames = additionalDomainAttributes.split(","); if (additionalDomainsNames.length <= 0) { throw new IllegalArgumentException("Number of Domains should be > 0"); } // add al the provided domain for (String propertyName : additionalDomainsNames) { String domainName = cleanupDomainName(propertyName); domainPairs.put(domainName, propertyName); } init(domainPairs, simpleFeatureType); } /** * * @param domainName * @return * * @TODO We can surely improve it by making use of Regular Expressions */ private String cleanupDomainName(String domainName) { if (attributeHasRange(domainName) || (domainName.contains("(") && domainName.contains(")"))) { // Getting rid of the attributes definition to get only the domain name domainName = domainName.substring(0, domainName.indexOf("(")); } return domainName; } /** * Add a domain to the manager * * @param domain the name of the domain * @param propertyName * @param featureType */ private void addDomain(String name, String propertyName, final DomainType domainType, final SimpleFeatureType featureType) { Utilities.ensureNonNull("name", name); Utilities.ensureNonNull("propertyName", propertyName); // === checks // existing! if (domainsMap.containsKey(name)) { throw new IllegalArgumentException("Trying to add a domain with an existing name" + name); } // === checks // has Ranges String basePropertyName = propertyName; String additionalPropertyName = null; if (domainType != DomainType.SINGLE_VALUE) { // Deal with a case like this: time(begin,endtime) propertyName = extractAttributes(propertyName); // Getting 2 attributes for this domain String properties[] = propertyName.split(Utils.RANGE_SPLITTER_CHAR); if (properties == null || properties.length != 2) { throw new IllegalArgumentException( "Malformed domain with ranges: it should contain 2 attributes"); } basePropertyName = properties[0]; additionalPropertyName = properties[1]; } // ad with uppercase and with suffix, the parameter that describes it will match this final String upperCase = name.toUpperCase(); final AttributeDescriptor descriptor = featureType.getDescriptor(basePropertyName); final String type = descriptor.getType().getBinding().getName(); domainsMap.put(upperCase + DomainDescriptor.DOMAIN_SUFFIX, new DomainDescriptor(name, domainType, type, basePropertyName, additionalPropertyName)); addDimensionDescriptor(name, upperCase, basePropertyName, additionalPropertyName); } private void addDimensionDescriptor(String name, String upperCase, String basePropertyName, String additionalPropertyName) { final String unitsName = upperCase.equalsIgnoreCase(Utils.TIME_DOMAIN) ? CoverageUtilities.UCUM.TIME_UNITS.getName() : upperCase.equalsIgnoreCase(Utils.ELEVATION_DOMAIN) ? CoverageUtilities.UCUM.ELEVATION_UNITS.getName() : "FIXME"; //TODO: ADD UCUM units Management final String unitsSymbol = upperCase.equalsIgnoreCase(Utils.TIME_DOMAIN) ? CoverageUtilities.UCUM.TIME_UNITS.getSymbol() : upperCase.equalsIgnoreCase(Utils.ELEVATION_DOMAIN) ? CoverageUtilities.UCUM.ELEVATION_UNITS.getSymbol() : "FIXME"; //TODO: ADD UCUM units Management final DimensionDescriptor dimensionDescriptor = new DefaultDimensionDescriptor(name, unitsName, unitsSymbol, basePropertyName, additionalPropertyName); dimensions.add(dimensionDescriptor); } private String extractAttributes(String propertyName) { if (propertyName.contains("(") && propertyName.contains(")")) { // extract the ranges attributes propertyName = propertyName.substring(propertyName.indexOf("(")).replace("(", "").replace(")", ""); } return propertyName; } /** * Check whether a specific parameter (identified by the {@link Identifier} name) is supported by this manager (and therefore, by the reader). * * @param name * @return */ public boolean isParameterSupported(final Identifier name) { if (!domainsMap.isEmpty()) { for (DomainDescriptor domain : domainsMap.values()) { final ReferenceIdentifier nameLoc = domain.getDomainaParameterDescriptor().getName(); if (nameLoc.equals(name)) { return true; } } } return false; } /** * Setup the List of metadataNames for this additional domains manager * * @return */ public List<String> getMetadataNames() { final List<String> metadataNames = new ArrayList<String>(); if (!domainsMap.isEmpty()) { for (DomainDescriptor domain : domainsMap.values()) { String domainName = domain.getIdentifier().toUpperCase(); metadataNames.add(domainName + DomainDescriptor.DOMAIN_SUFFIX); if (domain.getDataType() != null) { metadataNames.add( domainName + DomainDescriptor.DOMAIN_SUFFIX + DomainDescriptor.DATATYPE_SUFFIX); } metadataNames.add(DomainDescriptor.HAS_PREFIX + domainName + DomainDescriptor.DOMAIN_SUFFIX); } } return metadataNames; } /** * Return the value of a specific metadata by parsing the requested name as a Domain Name * * @param name * @return */ public String getMetadataValue(String name) { Utilities.ensureNonNull("name", name); String value = null; if (domainsMap.size() > 0) { // is a domain? if (domainsMap.containsKey(name)) { final DomainDescriptor domainDescriptor = domainsMap.get(name); value = domainDescriptor.getValues(); } else { // is a simple Has domain query? if (name.startsWith(DomainDescriptor.HAS_PREFIX)) { final String substring = name.substring(DomainDescriptor.HAS_PREFIX.length(), name.length()); if (domainsMap.containsKey(substring)) { return Boolean.toString(Boolean.TRUE); } else { return Boolean.toString(Boolean.FALSE); } } else if (name.endsWith(DomainDescriptor.DATATYPE_SUFFIX)) { return domainsMap.get(name.substring(0, name.lastIndexOf(DomainDescriptor.DATATYPE_SUFFIX))) .getDataType(); } else { // MINUM or MAXIMUM if (name.endsWith("MINIMUM") || name.endsWith("MAXIMUM")) { return domainsMap.get(name.substring(0, name.lastIndexOf("_"))).getExtrema(name); } } } } return value; } /** * Setup a Filter on top of the specified domainRequest which is in the form "key=value" * * @param domain * @param values * @return */ public Filter createFilter(String domain, List values) { // === checks if (domain == null || domain.isEmpty()) { throw new IllegalArgumentException("Null domain requested"); } if (values == null || values.isEmpty()) { throw new IllegalArgumentException("Null domain values provided"); } if (domainsMap.isEmpty() || !domainsMap.containsKey(domain)) { throw new IllegalArgumentException("requested domain is not supported by this mosaic: " + domain); } // get the property name DomainDescriptor domainDescriptor = domainsMap.get(domain); return domainDescriptor.createFilter(values); } /** * Return the set of dynamic parameterDescriptors (the ones related to domains) for this reader * * @return */ public Set<ParameterDescriptor<List>> getDynamicParameters() { Set<ParameterDescriptor<List>> dynamicParameters = new HashSet<ParameterDescriptor<List>>(); if (!domainsMap.isEmpty()) { for (DomainDescriptor domain : domainsMap.values()) { dynamicParameters.add(domain.getDomainaParameterDescriptor()); } } // return return dynamicParameters; } } enum DomainType { SINGLE_VALUE, TIME_RANGE, NUMBER_RANGE } /** Default {@link ColorModel}. */ ColorModel defaultCM; /** Default {@link SampleModel}. */ SampleModel defaultSM; /** * The name of the input coverage TODO consider URI */ private String coverageIdentifier; /** The hints to be used to produce this coverage */ private Hints hints; OverviewsController overviewsController; OverviewPolicy overviewPolicy; DecimationPolicy decimationPolicy; private PathType pathType; boolean expandMe; boolean heterogeneousGranules; double[][] levels; SpatialDomainManager spatialDomainManager; ImageLayout defaultImageLayout; /** The inner {@link DomainManager} instance which allows to manage custom dimensions */ DomainManager domainsManager; DomainManager elevationDomainManager; DomainManager timeDomainManager; volatile boolean enableEvents = false;//start disabled List<DimensionDescriptor> dimensionDescriptors = new ArrayList<DimensionDescriptor>(); ImageMosaicReader parentReader; GranuleCatalog granuleCatalog; GranuleStore granuleStore; GranuleSource granuleSource; String typeName; Envelope imposedEnvelope; MosaicConfigurationBean configuration; public RasterManager(final ImageMosaicReader parentReader, MosaicConfigurationBean configuration) throws IOException { Utilities.ensureNonNull("ImageMosaicReader", parentReader); this.parentReader = parentReader; this.expandMe = parentReader.expandMe; boolean checkAuxiliaryMetadata = configuration.isCheckAuxiliaryMetadata(); this.heterogeneousGranules = configuration.getCatalogConfigurationBean().isHeterogeneous(); this.configuration = configuration; hints = parentReader.getHints(); if (configuration != null && configuration.getAuxiliaryFilePath() != null) { hints.add(new RenderingHints(Utils.AUXILIARY_FILES_PATH, configuration.getAuxiliaryFilePath())); } if (checkAuxiliaryMetadata) { hints.add(new RenderingHints(Utils.CHECK_AUXILIARY_METADATA, checkAuxiliaryMetadata)); } // take ownership of the index : TODO: REMOVE THAT ONCE DEALING WITH MORE CATALOGS/RASTERMANAGERS // granuleCatalog = new HintedGranuleCatalog(parentReader.granuleCatalog, hints); granuleCatalog = parentReader.granuleCatalog; this.coverageFactory = parentReader.getGridCoverageFactory(); this.coverageIdentifier = configuration != null ? configuration.getName() : ImageMosaicReader.UNSPECIFIED; this.pathType = configuration.getCatalogConfigurationBean().isAbsolutePath() ? PathType.ABSOLUTE : PathType.RELATIVE; extractOverviewPolicy(); extractDecimationPolicy(); // load defaultSM and defaultCM by using the sample_image if it was provided loadSampleImage(configuration); if (configuration != null) { CatalogConfigurationBean catalogBean = configuration.getCatalogConfigurationBean(); typeName = catalogBean != null ? catalogBean.getTypeName() : null; initDomains(configuration); if (defaultSM == null) { defaultSM = configuration.getSampleModel(); } if (defaultCM == null) { defaultCM = configuration.getColorModel(); } if (defaultSM != null && defaultCM != null && defaultImageLayout == null) { defaultImageLayout = new ImageLayout().setColorModel(defaultCM).setSampleModel(defaultSM); } levels = configuration.getLevels(); final double[] highRes = levels[0]; final int numOverviews = configuration.getLevelsNum() - 1; double[][] overviews = null; if (numOverviews > 0) { overviews = new double[numOverviews][2]; for (int i = 0; i < numOverviews; i++) { overviews[i][0] = levels[i + 1][0]; overviews[i][1] = levels[i + 1][1]; } } overviewsController = new OverviewsController(highRes, numOverviews, overviews); imposedEnvelope = configuration.getEnvelope(); } } private void initDomains(MosaicConfigurationBean configuration) throws IOException { checkTypeName(); if (typeName != null) { final SimpleFeatureType schema = granuleCatalog.getType(typeName); if (schema != null) { // additional domain attributes final String additionalDomainConfig = configuration.getAdditionalDomainAttributes(); if (additionalDomainConfig != null && domainsManager == null) { domainsManager = new DomainManager(additionalDomainConfig, schema); dimensionDescriptors.addAll(domainsManager.dimensions); } // time attribute final String timeDomain = configuration.getTimeAttribute(); if (timeDomain != null && timeDomainManager == null) { final HashMap<String, String> init = new HashMap<String, String>(); init.put(Utils.TIME_DOMAIN, timeDomain); timeDomainManager = new DomainManager(init, schema); dimensionDescriptors.addAll(timeDomainManager.dimensions); } // elevation attribute final String elevationAttribute = configuration.getElevationAttribute(); if (elevationAttribute != null && elevationDomainManager == null) { final HashMap<String, String> init = new HashMap<String, String>(); init.put(Utils.ELEVATION_DOMAIN, elevationAttribute); elevationDomainManager = new DomainManager(init, schema); dimensionDescriptors.addAll(elevationDomainManager.dimensions); } } } } private void checkTypeName() throws IOException { if (typeName == null) { URL sourceURL = parentReader.sourceURL; if (sourceURL.getPath().endsWith("shp")) { typeName = FilenameUtils.getBaseName(DataUtilities.urlToFile(sourceURL).getCanonicalPath()); } else { typeName = configuration.getName(); } } if (typeName == null && granuleCatalog != null) { String[] typeNames = granuleCatalog.getTypeNames(); typeName = (typeNames != null && typeNames.length > 0) ? typeNames[0] : null; } } /** * This code tries to load the sample image from which we can extract SM and CM to use when answering to requests * that falls within a hole in the mosaic. * @param configuration */ private void loadSampleImage(MosaicConfigurationBean configuration) { if (this.parentReader.sourceURL == null) { //TODO: I need to define the sampleImage somehow for the ImageMosaicDescriptor case return; } final URL baseURL = this.parentReader.sourceURL; final File baseFile = DataUtilities.urlToFile(baseURL); // in case we do not manage to convert the source URL we leave right awaycd sr if (baseFile == null) { if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine("Unable to find sample image for path " + baseURL); return; } String baseName = baseFile.getParent() + "/"; String fileName = null; File sampleImageFile = null; if (configuration != null) { String name = configuration.getName(); if (name != null) { fileName = baseName + name + Utils.SAMPLE_IMAGE_NAME; sampleImageFile = new File(fileName); if (!sampleImageFile.exists() || !sampleImageFile.canRead()) { sampleImageFile = null; } } } if (sampleImageFile == null) { sampleImageFile = new File(baseName + Utils.SAMPLE_IMAGE_NAME); } final RenderedImage sampleImage = Utils.loadSampleImage(sampleImageFile); if (sampleImage != null) { // load SM and CM defaultCM = sampleImage.getColorModel(); defaultSM = sampleImage.getSampleModel(); // default ImageLayout defaultImageLayout = new ImageLayout().setColorModel(defaultCM).setSampleModel(defaultSM); } else if (LOGGER.isLoggable(Level.WARNING)) LOGGER.warning("Unable to find sample image for path " + baseURL); } /** * This method is responsible for checking the overview policy as defined by * the provided {@link Hints}. * * @return the overview policy which can be one of * {@link OverviewPolicy#IGNORE}, * {@link OverviewPolicy#NEAREST}, * {@link OverviewPolicy#SPEED}, {@link OverviewPolicy#QUALITY}. * Default is {@link OverviewPolicy#NEAREST}. */ private OverviewPolicy extractOverviewPolicy() { // check if a policy was provided using hints (check even the // deprecated one) if (this.hints != null) if (this.hints.containsKey(Hints.OVERVIEW_POLICY)) overviewPolicy = (OverviewPolicy) this.hints.get(Hints.OVERVIEW_POLICY); // use default if not provided. Default is nearest if (overviewPolicy == null) { overviewPolicy = OverviewPolicy.getDefaultPolicy(); } assert overviewPolicy != null; return overviewPolicy; } /** * This method is responsible for checking the decimation policy as defined by * the provided {@link Hints}. * * @return the decimation policy which can be one of * {@link DecimationPolicy#ALLOW}, * {@link DecimationPolicy#DISALLOW}. * Default is {@link DecimationPolicy#ALLOW}. */ private DecimationPolicy extractDecimationPolicy() { if (this.hints != null) if (this.hints.containsKey(Hints.DECIMATION_POLICY)) decimationPolicy = (DecimationPolicy) this.hints.get(Hints.DECIMATION_POLICY); // use default if not provided. Default is allow if (decimationPolicy == null) { decimationPolicy = DecimationPolicy.getDefaultPolicy(); } assert decimationPolicy != null; return decimationPolicy; } public Collection<GridCoverage2D> read(final GeneralParameterValue[] params) throws IOException { // create a request final RasterLayerRequest request = new RasterLayerRequest(params, this); if (request.isEmpty()) { if (LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE, "Request is empty: " + request.toString()); return Collections.emptyList(); } // create a response for the provided request final RasterLayerResponse response = new RasterLayerResponse(request, this); // execute the request final GridCoverage2D elem = response.createResponse(); if (elem != null) { return Collections.singletonList(elem); } return Collections.emptyList(); } void getGranuleDescriptors(final Query q, final GranuleCatalogVisitor visitor) throws IOException { granuleCatalog.getGranuleDescriptors(q, visitor); } public PathType getPathType() { return pathType; } public String getCoverageIdentifier() { return coverageIdentifier; } public Hints getHints() { return hints; } public GridCoverageFactory getCoverageFactory() { return coverageFactory; } public String getTypeName() { return typeName; } /** * @param metadataName * @param attributeName * @return * @throws IOException */ FeatureCalc createExtremaQuery(String metadataName, String attributeName) throws IOException { final Query query = new Query(typeName); query.setPropertyNames(Arrays.asList(attributeName)); final FeatureCalc visitor = metadataName.toLowerCase().endsWith("maximum") ? new MaxVisitor(attributeName) : new MinVisitor(attributeName); granuleCatalog.computeAggregateFunction(query, visitor); return visitor; } /** * Extract the domain of a dimension as a set of unique values. * * <p> * It retrieves a comma separated list of values as a Set of {@link String}. * * @return a comma separated list of values as a {@link String}. * @throws IOException */ private Set extractDomain(final String attribute) throws IOException { Query query = new Query(typeName); query.setPropertyNames(Arrays.asList(attribute)); final UniqueVisitor visitor = new UniqueVisitor(attribute); granuleCatalog.computeAggregateFunction(query, visitor); return visitor.getUnique(); } /** * Extract the domain of a dimension (with Range) as a set of values. * * <p> * It retrieves a comma separated list of values as a Set of {@link String}. * * @param domainType * * @return a comma separated list of values as a Set of {@link String}. * @throws IOException */ private Set extractDomain(final String attribute, final String secondAttribute, final DomainType domainType) throws IOException { final Query query = new Query(typeName); final PropertyName propertyName = FeatureUtilities.DEFAULT_FILTER_FACTORY.property(attribute); query.setPropertyNames(Arrays.asList(attribute, secondAttribute)); final SortByImpl[] sb = new SortByImpl[] { new SortByImpl(propertyName, SortOrder.ASCENDING) }; // Checking whether it supports sorting capabilities if (granuleCatalog.getQueryCapabilities(typeName).supportsSorting(sb)) { query.setSortBy(sb); } final FeatureCalc visitor = domainType == DomainType.TIME_RANGE ? new DateRangeVisitor(attribute, secondAttribute) : new RangeVisitor(attribute, secondAttribute); granuleCatalog.computeAggregateFunction(query, visitor); return domainType == DomainType.TIME_RANGE ? ((DateRangeVisitor) visitor).getRange() : ((RangeVisitor) visitor).getRange(); } /** * TODO this should not leak through * @return */ public GranuleCatalog getGranuleCatalog() { return granuleCatalog; } /** * Create a store for the coverage related to this {@link RasterManager} using the * provided schema * * @param indexSchema * @throws IOException */ public void createStore(SimpleFeatureType indexSchema) throws IOException { final String typeName = indexSchema.getTypeName(); final SimpleFeatureType type = typeName != null ? granuleCatalog.getType(typeName) : null; if (type == null) { granuleCatalog.createType(indexSchema); this.typeName = typeName; } else { if (this.typeName == null) { this.typeName = typeName; } // remove them all, assuming the schema has not changed final Query query = new Query(type.getTypeName()); query.setFilter(Filter.INCLUDE); granuleCatalog.removeGranules(query); } } /** * Remove a store for the coverage related to this {@link RasterManager} * @param forceDelete * * @param indexSchema * @throws IOException */ public void removeStore(String typeName, boolean forceDelete, boolean checkForReferences) throws IOException { Utilities.ensureNonNull("typeName", typeName); if (typeName != null) { // Preliminar granules removal... // Should we send a message instead reporting that the catalog // still contain some granules before allowing for a removal?? final Query query = new Query(typeName); query.setFilter(Filter.INCLUDE); // cleaning up granules and underlying readers cleanupGranules(query, checkForReferences, forceDelete); // removing records from the catalog granuleCatalog.removeGranules(query); granuleCatalog.removeType(typeName); } } /** * Delete granules from query. * @param query * @param checkForReferences * @throws IOException */ private void cleanupGranules(Query query, boolean checkForReferences, boolean deleteData) throws IOException { final SimpleFeatureCollection collection = granuleCatalog.getGranules(query); UniqueVisitor visitor = new UniqueVisitor(parentReader.locationAttributeName); collection.accepts(visitor, null); Set<String> features = visitor.getUnique(); final String coverageName = query.getTypeName(); for (String feature : features) { final URL rasterPath = pathType .resolvePath(DataUtilities.fileToURL(parentReader.parentDirectory).toString(), feature); boolean delete = true; if (checkForReferences) { delete = !checkForReferences(coverageName); } AbstractGridFormat format = (AbstractGridFormat) GridFormatFinder.findFormat(rasterPath, excludeMosaicHints); if (format != null) { GridCoverage2DReader coverageReader = null; try { coverageReader = (GridCoverage2DReader) format.getReader(rasterPath, hints); if (coverageReader instanceof StructuredGridCoverage2DReader) { StructuredGridCoverage2DReader reader = (StructuredGridCoverage2DReader) coverageReader; if (delete) { reader.delete(deleteData); } else { reader.removeCoverage(coverageName, false); } } else if (deleteData) { final boolean removed = FileUtils.deleteQuietly(DataUtilities.urlToFile(rasterPath)); } } finally { if (coverageReader != null) { try { coverageReader.dispose(); } catch (Throwable t) { //Ignoring exceptions on disposing readers } } } } } } /** * Check if there is any granule referred by other coverages. * @param coverageName * @return * @throws IOException */ private boolean checkForReferences(String coverageName) throws IOException { final String[] coverageNames = parentReader.getGridCoverageNames(); for (String typeName : coverageNames) { if (!coverageName.equalsIgnoreCase(typeName)) { Query query = new Query(typeName); final SimpleFeatureCollection collection = granuleCatalog.getGranules(query); UniqueVisitor visitor = new UniqueVisitor(parentReader.locationAttributeName); collection.accepts(visitor, null); Set<String> features = visitor.getUnique(); if (features.size() > 0) { return true; } } } return false; } public GranuleSource getGranuleSource(final boolean readOnly, final Hints hints) { synchronized (this) { if (readOnly) { if (granuleSource == null) { granuleSource = new GranuleCatalogSource(granuleCatalog, typeName, hints); } return granuleSource; } else { if (granuleStore == null) { granuleStore = new GranuleCatalogStore(granuleCatalog, typeName, hints); } return granuleStore; } } } public List<DimensionDescriptor> getDimensionDescriptors() { return dimensionDescriptors; } public MosaicConfigurationBean getConfiguration() { return configuration; } public void setConfiguration(MosaicConfigurationBean configuration) { this.configuration = configuration; } public void dispose() { synchronized (this) { try { if (granuleCatalog != null) { this.granuleCatalog.dispose(); } } catch (Exception e) { if (LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE, e.getLocalizedMessage(), e); } finally { if (granuleSource != null) { granuleSource = null; } if (granuleStore != null) { granuleStore = null; } if (granuleCatalog != null) { granuleCatalog = null; } } } } void initialize(final boolean checkDomains) throws IOException { final BoundingBox bounds = granuleCatalog.getBounds(typeName); if (checkDomains) { initDomains(configuration); } if (bounds.isEmpty()) { throw new IllegalArgumentException("Cannot create a mosaic out of an empty index"); } // we might have an imposed bbox CoordinateReferenceSystem crs = bounds.getCoordinateReferenceSystem(); GeneralEnvelope originalEnvelope = null; if (imposedEnvelope == null) { originalEnvelope = new GeneralEnvelope(bounds); } else { originalEnvelope = new GeneralEnvelope(imposedEnvelope); originalEnvelope.setCoordinateReferenceSystem(crs); } // original gridrange (estimated). I am using the floor here in order to make sure // we always stays inside the real area that we have for the granule OverviewLevel highResOvLevel = overviewsController.resolutionsLevels.get(0); final double highestRes[] = new double[] { highResOvLevel.resolutionX, highResOvLevel.resolutionY }; GridEnvelope2D originalGridRange = new GridEnvelope2D( new Rectangle((int) (originalEnvelope.getSpan(0) / highestRes[0]), (int) (originalEnvelope.getSpan(1) / highestRes[1]))); AffineTransform2D raster2Model = new AffineTransform2D(highestRes[0], 0, 0, -highestRes[1], originalEnvelope.getLowerCorner().getOrdinate(0) + 0.5 * highestRes[0], originalEnvelope.getUpperCorner().getOrdinate(1) - 0.5 * highestRes[1]); try { spatialDomainManager = new SpatialDomainManager(originalEnvelope, (GridEnvelope2D) originalGridRange, crs, raster2Model, overviewsController); } catch (TransformException e) { throw new IOException("Exception occurred while initializing the SpatialDomainManager", e); } catch (FactoryException e) { throw new IOException("Exception occurred while initializing the SpatialDomainManager", e); } } /** * Return the metadataNames for this manager * * @return */ String[] getMetadataNames() { final List<String> metadataNames = new ArrayList<String>(); metadataNames.add(GridCoverage2DReader.TIME_DOMAIN); metadataNames.add(GridCoverage2DReader.HAS_TIME_DOMAIN); metadataNames.add(GridCoverage2DReader.TIME_DOMAIN_MINIMUM); metadataNames.add(GridCoverage2DReader.TIME_DOMAIN_MAXIMUM); metadataNames.add(GridCoverage2DReader.TIME_DOMAIN_RESOLUTION); metadataNames.add(GridCoverage2DReader.TIME_DOMAIN + DomainDescriptor.DATATYPE_SUFFIX); metadataNames.add(GridCoverage2DReader.ELEVATION_DOMAIN); metadataNames.add(GridCoverage2DReader.ELEVATION_DOMAIN_MINIMUM); metadataNames.add(GridCoverage2DReader.ELEVATION_DOMAIN_MAXIMUM); metadataNames.add(GridCoverage2DReader.HAS_ELEVATION_DOMAIN); metadataNames.add(GridCoverage2DReader.ELEVATION_DOMAIN_RESOLUTION); metadataNames.add(GridCoverage2DReader.ELEVATION_DOMAIN + DomainDescriptor.DATATYPE_SUFFIX); if (domainsManager != null) { metadataNames.addAll(domainsManager.getMetadataNames()); } return metadataNames.toArray(new String[metadataNames.size()]); } /** * Return the metadata value for the specified metadata name * @param name the name of the metadata to be returned * @return */ String getMetadataValue(String name) { String value = null; final boolean hasTimeDomain = timeDomainManager != null; final boolean hasElevationDomain = elevationDomainManager != null; if (name.equalsIgnoreCase(GridCoverage2DReader.HAS_ELEVATION_DOMAIN)) return String.valueOf(hasElevationDomain); if (name.equalsIgnoreCase(GridCoverage2DReader.HAS_TIME_DOMAIN)) { return String.valueOf(hasTimeDomain); } // NOT supported if (name.equalsIgnoreCase(GridCoverage2DReader.TIME_DOMAIN_RESOLUTION)) { return null; } // NOT supported if (name.equalsIgnoreCase(GridCoverage2DReader.ELEVATION_DOMAIN_RESOLUTION)) { return null; } if (hasTimeDomain) { if (name.equalsIgnoreCase("time_domain")) { return timeDomainManager.getMetadataValue(name); } if ((name.equalsIgnoreCase("time_domain_minimum") || name.equalsIgnoreCase("time_domain_maximum"))) { return timeDomainManager.getMetadataValue(name); } if (name.equalsIgnoreCase("time_domain_datatype")) { return timeDomainManager.getMetadataValue(name); } } if (hasElevationDomain) { if (name.equalsIgnoreCase("elevation_domain")) { return elevationDomainManager.getMetadataValue(name); } if (name.equalsIgnoreCase("elevation_domain_minimum") || name.equalsIgnoreCase("elevation_domain_maximum")) { return elevationDomainManager.getMetadataValue(name); } if (name.equalsIgnoreCase("elevation_domain_datatype")) { return elevationDomainManager.getMetadataValue(name); } } // check additional domains if (domainsManager != null) { return domainsManager.getMetadataValue(name); } // return value; } }