/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2006-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.gce.imagemosaic;
import java.awt.Rectangle;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.spi.ImageReaderSpi;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataUtilities;
import org.geotools.data.Query;
import org.geotools.data.QueryCapabilities;
import org.geotools.factory.Hints;
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.catalog.GranuleCatalog;
import org.geotools.gce.imagemosaic.catalog.GranuleCatalogFactory;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.operation.transform.AffineTransform2D;
import org.geotools.resources.coverage.FeatureUtilities;
import org.geotools.util.Utilities;
import org.opengis.coverage.grid.Format;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.coverage.grid.GridCoverageReader;
import org.opengis.coverage.grid.GridCoverageWriter;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.filter.sort.SortBy;
import org.opengis.filter.sort.SortOrder;
import org.opengis.geometry.BoundingBox;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.ParameterValue;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
/**
* This reader is responsible for providing access to mosaic of georeferenced
* images. Citing JAI documentation:
*
* The "Mosaic" operation creates a mosaic of two or more source images. This
* operation could be used for example to assemble a set of overlapping
* geospatially rectified images into a contiguous image. It could also be used
* to create a montage of photographs such as a panorama.
*
* All source images are assumed to have been geometrically mapped into a common
* coordinate space. The origin (minX, minY) of each image is therefore taken to
* represent the location of the respective image in the common coordinate
* system of the source images. This coordinate space will also be that of the
* destination image.
*
* All source images must have the same data type and sample size for all bands
* and have the same number of bands as color components. The destination will
* have the same data type, sample size, and number of bands and color
* components as the sources.
*
*
* @author Simone Giannecchini, GeoSolutions S.A.S
* @author Stefan Alfons Krueger (alfonx), Wikisquare.de : Support for jar:file:foo.jar/bar.properties URLs
* @since 2.3
*
*
* @source $URL$
*/
public final class ImageMosaicReader extends AbstractGridCoverage2DReader implements GridCoverageReader, GridCoverageWriter {
/** Logger. */
private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(ImageMosaicReader.class);
/**
* The source {@link URL} pointing to the index shapefile for this
* {@link ImageMosaicReader}.
*/
URL sourceURL;
boolean expandMe;
PathType pathType;
ExecutorService multiThreadedLoader = null;
String locationAttributeName="location";
RasterManager rasterManager;
int maxAllowedTiles=ImageMosaicFormat.MAX_ALLOWED_TILES.getDefaultValue();
/** The suggested SPI to avoid SPI lookup*/
ImageReaderSpi suggestedSPI;
GranuleCatalog catalog;
String timeAttribute;
boolean cachingIndex;
String elevationAttribute;
String runtimeAttribute;
boolean imposedBBox;
boolean heterogeneousGranules;
/**
* UTC timezone to serve as reference
*/
static final TimeZone UTC_TZ = TimeZone.getTimeZone("UTC");
/**
* Constructor.
*
* @param source
* The source object.
* @throws IOException
* @throws UnsupportedEncodingException
*
*/
public ImageMosaicReader(Object source, Hints uHints) throws IOException {
super(source,uHints);
if (this.hints.containsKey(Hints.EXECUTOR_SERVICE)) {
final Object executor = uHints.get(Hints.EXECUTOR_SERVICE);
if (executor != null && executor instanceof ExecutorService){
multiThreadedLoader = (ExecutorService) executor;
if (LOGGER.isLoggable(Level.FINE)){
if (multiThreadedLoader instanceof ThreadPoolExecutor){
final ThreadPoolExecutor tpe = (ThreadPoolExecutor) multiThreadedLoader;
LOGGER.fine("Using ThreadPoolExecutor with the following settings: " +
"core pool size = " + tpe.getCorePoolSize() +
"\nmax pool size = " + tpe.getMaximumPoolSize() +
"\nkeep alive time " + tpe.getKeepAliveTime(TimeUnit.MILLISECONDS));
}
}
}
}
if(this.hints.containsKey(Hints.MAX_ALLOWED_TILES))
this.maxAllowedTiles= ((Integer)this.hints.get(Hints.MAX_ALLOWED_TILES));
// /////////////////////////////////////////////////////////////////////
//
// Check source
//
// /////////////////////////////////////////////////////////////////////
if (source instanceof ImageMosaicDescriptor) {
initReaderFromDescriptor((ImageMosaicDescriptor) source, uHints);
} else {
initReaderFromURL(source, uHints);
}
}
/**
* Init this {@link ImageMosaicReader} using the provided {@link ImageMosaicDescriptor} as source.
* @param source
* @param uHints
* @throws DataSourceException
*/
private void initReaderFromDescriptor(final ImageMosaicDescriptor source, final Hints uHints) throws DataSourceException {
Utilities.ensureNonNull("source", source);
final MosaicConfigurationBean configuration = source.getConfiguration();
if (configuration == null) {
throw new DataSourceException("Unable to create reader for this mosaic since we could not parse the configuration.");
}
extractProperties(configuration);
catalog = source.getCatalog();
if (catalog == null) {
throw new DataSourceException("Unable to create reader for this mosaic since the inner catalog is null.");
}
setGridGeometry();
rasterManager = new RasterManager(this);
rasterManager.defaultSM = configuration.getSampleModel();
}
/**
* Init this {@link ImageMosaicReader} using the provided object as a source referring to an {@link URL}.
*
* @param source
* @param uHints
* @throws DataSourceException
*/
private void initReaderFromURL(final Object source, final Hints hints) throws MalformedURLException, DataSourceException {
this.sourceURL=Utils.checkSource(source,hints);
if(this.sourceURL==null)
throw new DataSourceException("This plugin accepts File, URL or String. The string may describe a File or an URL");
//
// Load properties file with information about levels and envelope
//
final MosaicConfigurationBean configuration = loadMosaicProperties();
if(configuration==null)
throw new DataSourceException("Unable to create reader for this mosaic since we could not parse the configuration.");
//location attribute override
if(this.hints.containsKey(Hints.MOSAIC_LOCATION_ATTRIBUTE))
this.locationAttributeName=((String)this.hints.get(Hints.MOSAIC_LOCATION_ATTRIBUTE));
//
//
// Load tiles informations, especially the bounds, which will be
// reused
//
//
try {
// create the index
catalog= GranuleCatalogFactory.createGranuleCatalog(sourceURL, configuration);
// error
if(catalog==null)
throw new DataSourceException("Unable to create index for this URL "+sourceURL);
// everything is fine
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Connected mosaic reader to its index "
+ sourceURL.toString());
final SimpleFeatureType type= catalog.getType();
if (type==null)
throw new IllegalArgumentException("Problems when opening the index, no typenames for the schema are defined");
setGridGeometry(configuration.getEnvelope());
//
// get the crs if able to
//
final Object tempCRS = this.hints.get(Hints.DEFAULT_COORDINATE_REFERENCE_SYSTEM);
if (tempCRS != null) {
this.crs = (CoordinateReferenceSystem) tempCRS;
LOGGER.log(Level.WARNING, "Using forced coordinate reference system ");
} else {
final CoordinateReferenceSystem tempcrs = type.getGeometryDescriptor().getCoordinateReferenceSystem();
if (tempcrs == null) {
// use the default crs
crs = AbstractGridFormat.getDefaultCRS();
LOGGER.log(Level.WARNING, "Unable to find a CRS for this coverage, using a default one" );
} else
crs = tempcrs;
}
//
// location attribute field checks
//
if(this.locationAttributeName==null)
{
//get the first string
for(AttributeDescriptor attribute: type.getAttributeDescriptors()){
if(attribute.getType().getBinding().equals(String.class))
this.locationAttributeName=attribute.getName().toString();
}
}
if(type.getDescriptor(this.locationAttributeName)==null)
throw new DataSourceException("The provided name for the location attribute is invalid.");
//
// time attribute field checks
//
//time attribute override
if(this.timeAttribute==null)
{
//get the first attribute that can be use as date
for(AttributeDescriptor attribute: type.getAttributeDescriptors()){
// TODO improve this code
if(attribute.getType().getBinding().equals(Date.class))
{
this.timeAttribute=attribute.getName().toString();
break;
}
if(attribute.getType().getBinding().equals(Timestamp.class))
{
this.timeAttribute=attribute.getName().toString();
break;
}
if(attribute.getType().getBinding().equals(java.sql.Date.class))
{
this.timeAttribute=attribute.getName().toString();
break;
}
}
}
if(this.timeAttribute!=null&&this.timeAttribute.length()>0&&type.getDescriptor(this.timeAttribute)==null)
throw new DataSourceException("The provided name for the timeAttribute attribute is invalid.");
// creating the raster manager
rasterManager = new RasterManager(this);
}
catch (Throwable e) {
try {
if(catalog!=null)
catalog.dispose();
} catch (Throwable e1) {
if (LOGGER.isLoggable(Level.FINEST))
LOGGER.log(Level.FINEST, e1.getLocalizedMessage(), e1);
}
finally{
catalog=null;
}
// dispose raster manager as well
try {
if(rasterManager!=null)
rasterManager.dispose();
} catch (Throwable e1) {
if (LOGGER.isLoggable(Level.FINEST))
LOGGER.log(Level.FINEST, e1.getLocalizedMessage(), e1);
}
finally{
rasterManager=null;
}
throw new DataSourceException(e);
}
}
private void setGridGeometry(ReferencedEnvelope envelope) {
Utilities.ensureNonNull("index", catalog);
//
// save the bbox and prepare other info
//
final BoundingBox bounds = catalog.getBounds();
if(bounds.isEmpty()) {
throw new IllegalArgumentException("Cannot create a mosaic out of an empty index");
}
// we might have an imposed bbox
this.crs=bounds.getCoordinateReferenceSystem();
if(envelope==null)
this.originalEnvelope=new GeneralEnvelope(bounds);
else{
this.originalEnvelope=new GeneralEnvelope(envelope);
this.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
originalGridRange = new GridEnvelope2D(
new Rectangle(
(int) (originalEnvelope.getSpan(0)/ highestRes[0]),
(int) (originalEnvelope.getSpan(1)/ highestRes[1])
)
);
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]);
}
private void setGridGeometry () {
setGridGeometry(null) ;
}
/**
* Loads the properties file that contains useful information about this
* coverage.
*
* @throws UnsupportedEncodingException
* @throws IOException
*/
private MosaicConfigurationBean loadMosaicProperties(){
// discern if we have a shapefile based index or a datastore based index
final File sourceFile=DataUtilities.urlToFile(sourceURL);
final String extension= FilenameUtils.getExtension(sourceFile.getAbsolutePath());
MosaicConfigurationBean configuration=null;
if(extension.equalsIgnoreCase("shp"))
{
// shapefile
configuration=Utils.loadMosaicProperties(DataUtilities.changeUrlExt(sourceURL, "properties"),this.locationAttributeName);
}
else
{
// we need to look for properties files that do NOT define a datastore
final File[] properties = sourceFile.getParentFile().listFiles(
(FilenameFilter)
FileFilterUtils.andFileFilter(
FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter("indexer.properties")),
FileFilterUtils.andFileFilter(
FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter("datastore.properties")),
FileFilterUtils.makeFileOnly(FileFilterUtils.suffixFileFilter(".properties")
)
)
));
// check the valid mosaic properties files
for(File propFile:properties)
if(Utils.checkFileReadable(propFile))
{
// try to load the config
configuration=Utils.loadMosaicProperties(DataUtilities.fileToURL(propFile), this.locationAttributeName);
if(configuration!=null)
break;
// proceed with next prop file
}
}
// we did not find any good candidate for mosaic.properties file, this will signal it
if(configuration!=null)
return extractProperties(configuration);
return configuration;
}
private MosaicConfigurationBean extractProperties(final MosaicConfigurationBean configuration) {
// resolutions levels
numOverviews = configuration.getLevelsNum() - 1;
final double[][] resolutions = configuration.getLevels();
overViewResolutions = numOverviews >= 1 ? new double[numOverviews][2]: null;
highestRes = new double[2];
highestRes[0] = resolutions[0][0];
highestRes[1] = resolutions[0][1];
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine(new StringBuilder("Highest res ").append(highestRes[0])
.append(" ").append(highestRes[1]).toString());
if (numOverviews > 0){
for (int i = 0; i < numOverviews; i++) {
overViewResolutions[i][0] = resolutions[i+1][0];
overViewResolutions[i][1] = resolutions[i+1][1];
}
}
// name
coverageName = configuration.getName();
// need a color expansion?
// this is a newly added property we have to be ready to the case where
// we do not find it.
expandMe = configuration.isExpandToRGB();
heterogeneousGranules = configuration.isHeterogeneous();
// absolute or relative path
pathType = configuration.isAbsolutePath()?PathType.ABSOLUTE:PathType.RELATIVE;
//
// location attribute
//
locationAttributeName = configuration.getLocationAttribute();
// suggested SPI
final String suggestedSPIClass = configuration.getSuggestedSPI();
if (suggestedSPIClass != null){
try {
final Class<?> clazz=Class.forName(suggestedSPIClass);
if(clazz.newInstance() instanceof ImageReaderSpi)
suggestedSPI=(ImageReaderSpi)clazz.newInstance();
else
suggestedSPI=null;
} catch (ClassNotFoundException e) {
if(LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE,e.getLocalizedMessage(),e);
suggestedSPI=null;
} catch (InstantiationException e) {
if(LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE,e.getLocalizedMessage(),e);
suggestedSPI=null;
} catch (IllegalAccessException e) {
if(LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE,e.getLocalizedMessage(),e);
suggestedSPI=null;
}
}
// time param
final String timeAttribute = configuration.getTimeAttribute();
if(timeAttribute != null)
this.timeAttribute = timeAttribute;
// elevation param
final String elevationAttribute = configuration.getElevationAttribute();
if(elevationAttribute != null)
this.elevationAttribute = elevationAttribute;
// runtime param
final String runtimeAttribute = configuration.getRuntimeAttribute();
if(runtimeAttribute != null)
this.runtimeAttribute = runtimeAttribute;
// caching for the index
cachingIndex = configuration.isCaching();
// imposed BBOX?
this.imposedBBox=true;
return configuration;
}
/**
* Constructor.
*
* @param source
* The source object.
* @throws IOException
* @throws UnsupportedEncodingException
*
*/
public ImageMosaicReader(Object source) throws IOException {
this(source, null);
}
/**
*
* @see org.opengis.coverage.grid.GridCoverageReader#getFormat()
*/
public Format getFormat() {
return new ImageMosaicFormat();
}
/**
*
* @see org.opengis.coverage.grid.GridCoverageReader#read(org.opengis.parameter.GeneralParameterValue[])
*/
public GridCoverage2D read(GeneralParameterValue[] params) throws IOException {
// check if we were disposed already
if(rasterManager==null){
throw new IOException("Looks like this reader has been already disposed or it has not been properly initialized.");
}
if (LOGGER.isLoggable(Level.FINE)) {
if (sourceURL != null) {
LOGGER.fine("Reading mosaic from " + sourceURL.toString());
} else {
LOGGER.fine("Reading mosaic");
}
LOGGER.fine("Highest res "+highestRes[0]+" "+highestRes[1]);
}
//
// add max allowed tiles if missing
//
if(this.maxAllowedTiles!=Integer.MAX_VALUE){
if(params!=null){
// first thing let's see if we have it already, in which case we do nothing since a read parameter override a Hint
boolean found=false;
for(GeneralParameterValue pv:params)
{
if(pv.getDescriptor().getName().equals(ImageMosaicFormat.MAX_ALLOWED_TILES.getName()))
{
found=true;
break;
}
}
//ok, we did not find it, let's add it back
if(!found)
{
final GeneralParameterValue[] temp = new GeneralParameterValue[params.length+1];
System.arraycopy(params, 0, temp, 0, params.length);
ParameterValue<Integer> tempVal = ImageMosaicFormat.MAX_ALLOWED_TILES.createValue();
tempVal.setValue(this.maxAllowedTiles);
temp[params.length]=tempVal;
}
}
else
{
// we do not have nay read params, we have to create the array for them
ParameterValue<Integer> tempVal = ImageMosaicFormat.MAX_ALLOWED_TILES.createValue();
tempVal.setValue(this.maxAllowedTiles);
params= new GeneralParameterValue[]{tempVal};
}
}
// /////////////////////////////////////////////////////////////////////
//
// Loading tiles trying to optimize as much as possible
//
// /////////////////////////////////////////////////////////////////////
final Collection<GridCoverage2D> response = rasterManager.read(params);
if (response.isEmpty()) {
if (LOGGER.isLoggable(Level.FINE)){
LOGGER.fine("The response is empty. ==> returning a null GridCoverage");
}
return null;
} else {
return response.iterator().next();
}
}
/**
* Package private accessor for {@link Hints}.
*
* @return this {@link Hints} used by this reader.
*/
Hints getHints(){
return super.hints;
}
/**
* Package private accessor for the highest resolution values.
*
* @return the highest resolution values.
*/
double[] getHighestRes(){
return super.highestRes;
}
/**
*
* @return
*/
double[][] getOverviewsResolution(){
return super.overViewResolutions;
}
int getNumberOfOvervies(){
return super.numOverviews;
}
/** Package scope grid to world transformation accessor */
MathTransform getRaster2Model() {
return raster2Model;
}
/**
* Let us retrieve the {@link GridCoverageFactory} that we want to use.
*
* @return
* retrieves the {@link GridCoverageFactory} that we want to use.
*/
GridCoverageFactory getGridCoverageFactory(){
return coverageFactory;
}
String getName() {
return super.coverageName;
}
public Object getDestination() {
return null;
}
public void setCurrentSubname(String arg0) throws IOException {
throw new UnsupportedOperationException("Unsupported method");
}
public void setMetadataValue(String arg0, String arg1) throws IOException {
throw new UnsupportedOperationException("Unsupported method");
}
public void write(GridCoverage arg0, GeneralParameterValue[] arg1)throws IllegalArgumentException, IOException {
throw new UnsupportedOperationException("Unsupported method");
}
/**
* Number of coverages for this reader is 1
*
* @return the number of coverages for this reader.
*/
@Override
public int getGridCoverageCount() {
return 1;
}
/**
* Releases resources held by this reader.
*
*/
@Override
public synchronized void dispose() {
super.dispose();
try{
if(rasterManager!=null)
rasterManager.dispose();
} catch (Exception e) {
if(LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE,e.getLocalizedMessage(),e);
} finally {
rasterManager=null;
}
}
@Override
public String[] getMetadataNames() {
final String []parentNames = super.getMetadataNames();
final List<String> metadataNames = new ArrayList<String>();
metadataNames.add("TIME_DOMAIN");
metadataNames.add("HAS_TIME_DOMAIN");
metadataNames.add("TIME_DOMAIN_MINIMUM");
metadataNames.add("TIME_DOMAIN_MAXIMUM");
metadataNames.add("TIME_DOMAIN_RESOLUTION");
metadataNames.add("ELEVATION_DOMAIN");
metadataNames.add("ELEVATION_DOMAIN_MINIMUM");
metadataNames.add("ELEVATION_DOMAIN_MAXIMUM");
metadataNames.add("HAS_ELEVATION_DOMAIN");
metadataNames.add("ELEVATION_DOMAIN_RESOLUTION");
if(parentNames!=null)
metadataNames.addAll(Arrays.asList(parentNames));
return metadataNames.toArray(new String[metadataNames.size()]);
}
@Override
public String getMetadataValue(final String name) {
final String superValue=super.getMetadataValue(name);
if(superValue!=null)
return superValue;
if (name.equalsIgnoreCase("HAS_ELEVATION_DOMAIN"))
return String.valueOf(elevationAttribute != null);
if (name.equalsIgnoreCase("HAS_TIME_DOMAIN"))
return String.valueOf(timeAttribute != null);
if (name.equalsIgnoreCase("TIME_DOMAIN_RESOLUTION"))
return null;
final boolean getTimeDomain = (timeAttribute != null && name.equalsIgnoreCase("time_domain"));
if (getTimeDomain) {
return extractTimeDomain();
}
final boolean getTimeExtrema = timeAttribute != null
&& (name.equalsIgnoreCase("time_domain_minimum") || name.equalsIgnoreCase("time_domain_maximum"));
if (getTimeExtrema) {
return extractTimeExtrema(name);
}
final boolean getElevationAttribute = (elevationAttribute != null && name.equalsIgnoreCase("elevation_domain"));
if (getElevationAttribute) {
return extractElevationDomain();
}
final boolean getElevationExtrema = elevationAttribute != null
&& (name.equalsIgnoreCase("elevation_domain_minimum") || name.equalsIgnoreCase("elevation_domain_maximum"));
if (getElevationExtrema) {
return extractElevationExtrema(name);
}
// final boolean getRuntimeAttribute=name.equalsIgnoreCase("runtime_domain");
// if(getRuntimeAttribute){
// Query query;
// try {
// query = new DefaultQuery(rasterManager.granuleCatalog.getType().getTypeName());
// query.setPropertyNames(Arrays.asList("runtime"));
// final SortBy[] sortBy=new SortBy[]{
// new SortByImpl(
// FeatureUtilities.DEFAULT_FILTER_FACTORY.property("runtime"),
// SortOrder.DESCENDING
// )};
// if(queryCapabilities.supportsSorting(sortBy))
// query.setSortBy(sortBy);
//// else
//// manualSort=true;
// final UniqueVisitor visitor= new UniqueVisitor("runtime");
// rasterManager.granuleCatalog.computeAggregateFunction(query, visitor);
//
// // check result
// final Set<Integer> result = new TreeSet<Integer>(new Comparator<Integer>() {
//
// public int compare(Integer o1, Integer o2) {
// // Revert Order
// if (o1 > 02)
// return -1;
// else if (o1 < o2)
// return 1;
// return 0;
// }
// });
// result.addAll(visitor.getUnique());
// if(result.size()<=0)
// return null;
// final StringBuilder buff= new StringBuilder();
// for(Iterator<Integer> it=result.iterator();it.hasNext();){
// final int value= it.next();
// buff.append(value);
// if(it.hasNext())
// buff.append(",");
// }
// return buff.toString();
// } catch (IOException e) {
// if(LOGGER.isLoggable(Level.WARNING))
// LOGGER.log(Level.WARNING,"Unable to parse attribute:"+name,e);
// }
//
// }
//
return super.getMetadataValue(name);
}
/**
* Extract the time domain extrema.
*
* @param metadataName a {@link String} either TIME_DOMAIN_MAXIMUM or TIME_DOMAIN_MINIMUM.
*
* @return either TIME_DOMAIN_MAXIMUM or TIME_DOMAIN_MINIMUM as a {@link String}.
*/
private String extractTimeExtrema(String metadataName) {
if(timeAttribute==null){
if(LOGGER.isLoggable(Level.INFO))
LOGGER.info("Requesting extrema on attribute "+metadataName+" when no such an attribute is supported!");
return null;
}
try {
final FeatureCalc visitor = createExtremaQuery(metadataName,rasterManager.timeAttribute);
// check result
final Date result=(Date) visitor.getResult().getValue();
final SimpleDateFormat df= new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
df.setTimeZone(UTC_TZ);
return df.format(result)+"Z";//ZULU
} catch (IOException e) {
if(LOGGER.isLoggable(Level.WARNING))
LOGGER.log(Level.WARNING,"Unable to compute extrema for TIME_DOMAIN",e);
return null;
}
}
/**
* @param metadataName
* @param attributeName
* @return
* @throws IOException
*/
private FeatureCalc createExtremaQuery(String metadataName, String attributeName) throws IOException {
final Query query = new Query(rasterManager.granuleCatalog.getType().getTypeName());
query.setPropertyNames(Arrays.asList(attributeName));
final FeatureCalc visitor=
metadataName.toLowerCase().endsWith("maximum")?
new MaxVisitor(attributeName):new MinVisitor(attributeName);
rasterManager.granuleCatalog.computeAggregateFunction(query, visitor);
return visitor;
}
/**
* Extract the elevation domain extrema.
*
* @param metadataName a {@link String} either ELEVATION_DOMAIN_MAXIMUM or ELEVATION_DOMAIN_MINIMUM.
*
* @return either ELEVATION_DOMAIN_MAXIMUM or ELEVATION_DOMAIN_MINIMUM as a {@link String}.
*/
private String extractElevationExtrema(String metadataName) {
if(elevationAttribute==null){
if(LOGGER.isLoggable(Level.INFO))
LOGGER.info("Requesting extrema on attribute "+metadataName+" when no such an attribute is supported!");
return null;
}
try {
final FeatureCalc visitor = createExtremaQuery(metadataName,rasterManager.elevationAttribute);
// check result
final Double result=(Double) visitor.getResult().getValue();
return Double.toString(result);
} catch (IOException e) {
if(LOGGER.isLoggable(Level.WARNING))
LOGGER.log(Level.WARNING,"Unable to compute extrema for ELEVATION_DOMAIN",e);
return null;
}
}
/**
* Extract the elevation domain as a comma separated list of string values.
* @return a {@link String} that contains a comma separated list of values.
*/
private String extractElevationDomain() {
if(elevationAttribute==null){
if(LOGGER.isLoggable(Level.INFO))
LOGGER.info("Requesting domain on attribute elevation when no such an attribute is supported!");
return null;
}
try {
final Set<Double> result = extractDomain(elevationAttribute);
// check result
if(result.size()<=0)
return null;
final StringBuilder buff= new StringBuilder();
for(Iterator<Double> it=result.iterator();it.hasNext();){
final double value= (Double) it.next();
buff.append(value);
if(it.hasNext())
buff.append(",");
}
return buff.toString();
} catch (IOException e) {
if(LOGGER.isLoggable(Level.WARNING))
LOGGER.log(Level.WARNING,"Unable to parse attribute: ELEVATION_DOMAIN",e);
return null;
}
}
/**
* Extract the domain of a dimension as a set of unique values.
*
* <p>
* It retrieves a comma separated list of values as a {@link String}.
*
* @return a comma separated list of values as a {@link String}.
* @throws IOException
*/
private Set extractDomain(final String attribute)
throws IOException {
final QueryCapabilities queryCapabilities = rasterManager.granuleCatalog.getQueryCapabilities();
boolean manualSort=false;
Query query = new Query(rasterManager.granuleCatalog.getType().getTypeName());
query.setPropertyNames(Arrays.asList(attribute));
final SortBy[] sortBy=new SortBy[]{
new SortByImpl(
FeatureUtilities.DEFAULT_FILTER_FACTORY.property(attribute),
SortOrder.ASCENDING
)};
if(queryCapabilities.supportsSorting(sortBy))
query.setSortBy(sortBy);
else
manualSort=true;
final UniqueVisitor visitor= new UniqueVisitor(attribute);
rasterManager.granuleCatalog.computeAggregateFunction(query, visitor);
// check result
final Set result = manualSort?
new TreeSet(visitor.getUnique()):
visitor.getUnique();
if(result.size()<=0)
return null;
return result;
}
/**
* Extract the elevation domain as a comma separated list of string values.
* @return a {@link String} that contains a comma separated list of values.
*/
private String extractTimeDomain() {
if(timeAttribute==null){
if(LOGGER.isLoggable(Level.INFO))
LOGGER.info("Requesting domain on attribute time when no such an attribute is supported!");
return null;
}
try {
final Collection<Date>result =extractDomain(timeAttribute);
// check result
if(result.size()<=0)
return null;
final StringBuilder buff= new StringBuilder();
final SimpleDateFormat df= new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
df.setTimeZone(UTC_TZ);
for(Date date:result){
buff.append(df.format(date)).append("Z");//ZULU
buff.append(",");
}
return buff.substring(0,buff.length()-1).toString();
} catch (IOException e) {
if(LOGGER.isLoggable(Level.WARNING))
LOGGER.log(Level.WARNING,"Unable to parse attribute:TIME_DOMAIN",e);
return null;
}
}
}