/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2007-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.gce.imagemosaic;
import it.geosolutions.imageio.stream.input.spi.URLImageInputStreamSpi;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.stream.ImageInputStream;
import javax.media.jai.RasterFactory;
import javax.media.jai.remote.SerializableRenderedImage;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataUtilities;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.gce.imagemosaic.IndexBuilder.ExceptionEvent;
import org.geotools.gce.imagemosaic.IndexBuilder.IndexBuilderConfiguration;
import org.geotools.gce.imagemosaic.IndexBuilder.ProcessingEvent;
import org.geotools.gce.imagemosaic.IndexBuilder.ProcessingEventListener;
import org.geotools.geometry.Envelope2D;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.image.ImageWorker;
import org.geotools.metadata.iso.spatial.PixelTranslation;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.util.Utilities;
import org.opengis.geometry.BoundingBox;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.datum.PixelInCell;
import com.vividsolutions.jts.geom.Geometry;
/**
* Sparse utilities for the various mosaic classes. I use them to extract complex code from other places.
*
* @author Simone Giannecchini, GeoSolutions S.A.S.
*
*/
class ImageMosaicUtils {
static class Prop {
final static String LOCATION_ATTRIBUTE = "LocationAttribute";
final static String ENVELOPE2D = "Envelope2D";
final static String LEVELS_NUM = "LevelsNum";
final static String LEVELS = "Levels";
final static String SUGGESTED_SPI = "SuggestedSPI";
final static String EXP_RGB = "ExpandToRGB";
final static String ABSOLUTE_PATH = "AbsolutePath";
final static String NAME = "Name";
final static String FOOTPRINT_MANAGEMENT = "FootprintManagement";
}
final static Boolean IGNORE_FOOTPRINT = Boolean.getBoolean("org.geotools.footprint.ignore");
/**{@link AffineTransform} that can be used to go from an image datum placed at the center of pixels to one that is placed at ULC.*/
final static AffineTransform CENTER_TO_CORNER= AffineTransform.getTranslateInstance(
PixelTranslation.getPixelTranslation(PixelInCell.CELL_CORNER),
PixelTranslation.getPixelTranslation(PixelInCell.CELL_CORNER));
/**{@link AffineTransform} that can be used to go from an image datum placed at the ULC corner of pixels to one that is placed at center.*/
final static AffineTransform CORNER_TO_CENTER= AffineTransform.getTranslateInstance(
-PixelTranslation.getPixelTranslation(PixelInCell.CELL_CORNER),
-PixelTranslation.getPixelTranslation(PixelInCell.CELL_CORNER));
/**
* Very simple bean to hold the configuration of the mosaic.
*
* @author Simone Giannecchini, GeoSolutions S.A.S.
* @author Stefan Alfons Krueger (alfonx), Wikisquare.de : Support for jar:file:foo.jar/bar.properties URLs
*/
static final class MosaicConfigurationBean {
/**
* <code>true</code> it tells us if the mosaic points to absolute paths or to relative ones. (in case of <code>false</code>).
*/
private boolean absolutePath;
/**
* <code>true</code> if we need to expand to RGB(A) the single tiles in case they use a different {@link IndexColorModel}.
*/
private boolean expandToRGB;
/**
* <code>true</code> if we need to manage footprint if available.
*/
private boolean footprintManagement;
/** The envelope for the whole mosaic.**/
private Envelope2D envelope2D;
/** OverviewLevel levels */
private double[][] levels;
/** name for the mosaic.*/
private String name;
/** number of levels*/
private int levelsNum;
/** location attribute name*/
private String locationAttribute;
/**Suggested SPI for the various tiles. May be null.**/
private String suggestedSPI;
/**
* @return the suggestedSPI
*/
public String getSuggestedSPI() {
return suggestedSPI;
}
/**
* @param suggestedSPI the suggestedSPI to set
*/
public void setSuggestedSPI(String suggestedSPI) {
this.suggestedSPI = suggestedSPI;
}
public boolean isAbsolutePath() {
return absolutePath;
}
public void setAbsolutePath(boolean absolutePath) {
this.absolutePath = absolutePath;
}
public boolean isExpandToRGB() {
return expandToRGB;
}
public void setExpandToRGB(boolean expandToRGB) {
this.expandToRGB = expandToRGB;
}
public boolean isFootprintManagement() {
return footprintManagement;
}
public void setFootprintManagement(boolean footprintManagement) {
this.footprintManagement = footprintManagement;
}
public Envelope2D getEnvelope2D() {
return envelope2D;
}
public void setEnvelope2D(Envelope2D envelope2D) {
this.envelope2D = envelope2D;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getLevelsNum() {
return levelsNum;
}
public void setLevelsNum(int levelsNum) {
this.levelsNum = levelsNum;
}
public double[][] getLevels() {
return levels.clone();
}
public void setLevels(double[][] levels) {
this.levels = levels.clone();
}
public String getLocationAttribute() {
return locationAttribute;
}
public void setLocationAttribute(String locationAttribute) {
this.locationAttribute = locationAttribute;
}
}
/**
* Logger.
*/
private final static Logger LOGGER = org.geotools.util.logging.Logging
.getLogger(ImageMosaicUtils.class.toString());
/**
* Default wildcard for creating mosaics.
*/
static final String DEFAULT_WILCARD = "*.*";
static final boolean DEFAULT_FOOTPRINT_MANAGEMENT = true;
/**
* Default path behavior with respect to absolute paths.
*/
static final boolean DEFAULT_PATH_BEHAVIOR = false;
/**
* Cached instance of {@link URLImageInputStreamSpi} for creating {@link ImageInputStream} instances.
*/
private static ImageInputStreamSpi cachedStreamSPI=new URLImageInputStreamSpi();
/**
* Creates a mosaic for the provided input parameters.
*
* @param location path to the directory where to gather the elements for the mosaic.
* @param indexName name to give to this mosaic
* @param wildcard wildcard to use for walking through files. We are using commonsIO for this task
* @return <code>true</code> if everything is right, <code>false</code>if something bad happens, in which case the reason should be logged to the logger.
*/
static boolean createMosaic(
final String location,
final String indexName,
final String wildcard,
final boolean absolutePath) {
//create a mosaic index builder and set the relevant elements
final IndexBuilderConfiguration configuration = new IndexBuilderConfiguration();
configuration.setAbsolute(absolutePath);
configuration.setRootMosaicDirectory(location);
configuration.setIndexingDirectories(Arrays.asList(location));
configuration.setIndexName(indexName);
final IndexBuilder indexBuilder= new IndexBuilder(configuration);
//this is going to help us with catching exceptions and logging them
final Queue<Throwable> exceptions=new LinkedList<Throwable>();
try{
final IndexBuilder.ProcessingEventListener listener= new ProcessingEventListener(){
@Override
public void exceptionOccurred(ExceptionEvent event) {
final Throwable t=event.getException();
exceptions.add(t);
if(LOGGER.isLoggable(Level.SEVERE))
LOGGER.log(Level.SEVERE,t.getLocalizedMessage(),t);
}
@Override
public void getNotification(ProcessingEvent event) {
if(LOGGER.isLoggable(Level.FINE))
LOGGER.fine(event.getMessage());
}
};
indexBuilder.addProcessingEventListener(listener);
indexBuilder.run();
} catch (Throwable e) {
LOGGER.log(Level.SEVERE,"Unable to build mosaic",e);
return false;
} finally {
indexBuilder.dispose();
}
//check that nothing bad happened
if(exceptions.size()>0)
return false;
return true;
}
static String getMessageFromException(Exception exception) {
if(exception.getLocalizedMessage() != null)
return exception.getLocalizedMessage();
else
return exception.getMessage();
}
static URL checkSource(Object source) throws MalformedURLException,
DataSourceException {
URL sourceURL=null;
File sourceFile = null;
// /////////////////////////////////////////////////////////////////////
//
// Check source
//
// /////////////////////////////////////////////////////////////////////
//if it is a URL or a String let's try to see if we can get a file to check if we have to build the index
if (source instanceof File)
{
sourceFile = (File)source;
sourceURL = DataUtilities.fileToURL(sourceFile);
sourceURL = checkURLForMosaicQuery((URL) sourceURL);
}
else if (source instanceof URL)
{
sourceURL = checkURLForMosaicQuery((URL) source);
if (sourceURL.getProtocol().equals("file")) {
sourceFile =DataUtilities.urlToFile(sourceURL);
}
}
else if (source instanceof String) {
//is it a File?
final String tempSource=(String) source;
File tempFile = new File(tempSource);
if (!tempFile.exists())
{
//is it a URL
try{
sourceURL= new URL(tempSource);
sourceURL = checkURLForMosaicQuery(sourceURL);
source =DataUtilities.urlToFile(sourceURL);
}catch (MalformedURLException e) {
sourceURL=null;
source=null;
}
}
else
{
sourceURL = DataUtilities.fileToURL(tempFile);
// so that we can do our magic here below
sourceFile=tempFile;
}
}
// at this point we have tried to convert the thing to a File as hard as we could, let's see what we can do
if (sourceFile != null)
{
if(!sourceFile.isDirectory())
sourceURL = DataUtilities.fileToURL((File) sourceFile);
else
{
//it's a directory, let's look for a possible properties files that we want to load
final String locationPath=sourceFile.getAbsolutePath();
final String defaultWildcardString=DEFAULT_WILCARD;
final String defaultIndexName=FilenameUtils.getName(locationPath);
//now let's see f we have at least a properties file with its own shapefile
final File[] properties = sourceFile.listFiles((FilenameFilter)FileFilterUtils.makeFileOnly(FileFilterUtils.suffixFileFilter(".properties")) );
//now get the first one with a shapefile
File granuleIndex=null;
for(File propFile:properties){
final File shpFile= new File(locationPath,FilenameUtils.getBaseName(propFile.getName())+".shp");
if(shpFile.exists()&&shpFile.isFile()&&shpFile.canRead()&&propFile.canRead()&&propFile.isFile())
{
granuleIndex=shpFile;
break;
}
}
//did we find anything?
if (granuleIndex == null){
//try to build a mosaic inside this directory and see what happens
createMosaic(locationPath, defaultIndexName,defaultWildcardString,DEFAULT_PATH_BEHAVIOR);
granuleIndex= new File(locationPath,defaultIndexName+".shp");
File propertiesFile = new File(locationPath,defaultIndexName+".properties");
if(!granuleIndex.exists()||!granuleIndex.canRead()||!propertiesFile.exists()||!propertiesFile.canRead())
sourceURL=null;
else
// now set the new source and proceed
sourceURL = granuleIndex.toURI().toURL(); //TODO Comment by Stefan Krueger: Shouldn't we use DataUtilities.fileToURL(file)
} else {
if (!granuleIndex.exists()||!granuleIndex.canRead()){
sourceURL = null;
} else {
final String shapeFileName = granuleIndex.getAbsolutePath();
final String pathPrefix = FilenameUtils.getFullPathNoEndSeparator(shapeFileName) + File.separatorChar;
final File footprintSummaryFile = new File(new StringBuilder(pathPrefix).append(FilenameUtils.getBaseName(shapeFileName)).append(FootprintUtils.FOOTPRINT_EXT).toString());
final File footprintShapeFile = new File(new StringBuilder(pathPrefix).append(FootprintUtils.FOOTPRINT).toString());
if (footprintShapeFile != null && footprintShapeFile.exists() && footprintShapeFile.canRead()){
if (footprintSummaryFile != null && !footprintSummaryFile.exists()) {
MosaicConfigurationBean props = loadPropertiesFile(DataUtilities.fileToURL(granuleIndex), null, ImageMosaicUtils.DEFAULT_LOCATION_ATTRIBUTE, FootprintUtils.IGNORE_PROPS);
if (props.footprintManagement) {
ShapefileDataStore footprintStore = null;
try {
//associate locations to footprint to then write down granule's feature id + footprint
final Map <String,Geometry> footprintsLocationGeometryMap = new HashMap<String, Geometry>();
footprintStore = new ShapefileDataStore(DataUtilities.fileToURL(footprintShapeFile));
FootprintUtils.initFootprintsLocationGeometryMap(footprintStore, footprintsLocationGeometryMap);
FootprintUtils.writeFootprintSummary(footprintSummaryFile, granuleIndex, footprintsLocationGeometryMap);
footprintsLocationGeometryMap.clear();
} catch ( Throwable t){
if (LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE, t.getLocalizedMessage(), t);
} finally {
try {
if(footprintStore != null)
footprintStore.dispose();
} catch (Throwable e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
}
footprintStore = null;
}
}
}
}
final File sampleImageFile = new File(new StringBuilder(pathPrefix).append("sample_image").toString());
if (!sampleImageFile.exists() || !sampleImageFile.isFile() || !sampleImageFile.canRead()) {
createSampleImage(pathPrefix);
}
// now set the new source and proceed
sourceURL= granuleIndex.toURI().toURL(); // TODO Comment by Stefan Krueger: Shouldn't we use DataUtilities.fileToURL(file)
}
}
}
}
else {
// SK: We don't set SourceURL to null now, just because it doesn't point to a file
// sourceURL=null;
}
return sourceURL;
}
/**
* Load a sample image from which we can take the sample model and color
* model to be used to fill holes in responses.
*
* @param sampleImageFile
* the path to sample image.
* @return a sample image from which we can take the sample model and color
* model to be used to fill holes in responses.
*/
static RenderedImage loadSampleImage(final File sampleImageFile) {
// serialize it
InputStream inStream = null;
ObjectInputStream oiStream = null;
try {
// do we have the sample image??
if (DataUtilities.checkFileReadable(sampleImageFile, LOGGER)) {
inStream = new BufferedInputStream(new FileInputStream(sampleImageFile));
oiStream = new ObjectInputStream(inStream);
// load the image
return (RenderedImage) oiStream.readObject();
} else {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.warning("Unable to find sample image for path " + sampleImageFile);
return null;
}
} catch (FileNotFoundException e) {
if (LOGGER.isLoggable(Level.WARNING))
LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
return null;
} catch (IOException e) {
if (LOGGER.isLoggable(Level.WARNING))
LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
return null;
} catch (ClassNotFoundException e) {
if (LOGGER.isLoggable(Level.WARNING))
LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
return null;
} finally {
try {
if (inStream != null)
inStream.close();
} catch (Throwable e) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
}
try {
if (oiStream != null)
oiStream.close();
} catch (Throwable e) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
}
}
}
@SuppressWarnings("unchecked")
private static synchronized void createSampleImage(final String pathPrefix) {
final File sampleImageFile = new File(new StringBuilder(pathPrefix).append("sample_image").toString());
if (!sampleImageFile.exists() || !sampleImageFile.isFile() || !sampleImageFile.canRead()) {
final IOFileFilter finalFilter = IndexBuilder.createIndexingFilter(new WildcardFileFilter("*.*", IOCase.INSENSITIVE));
final File directoryToScan = new File(pathPrefix);
final Collection<File> files = FileUtils.listFiles(directoryToScan, finalFilter, TrueFileFilter.INSTANCE);
for (File file : files) {
ImageTypeSpecifier its = getImageTypeSpecifier(file);
if (its != null) {
ColorModel defaultCM = its.getColorModel();
SampleModel defaultSM = its.getSampleModel();
try {
storeSampleImage(sampleImageFile, defaultSM, defaultCM);
break;
} catch (IOException e) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
}
}
}
}
}
}
static ImageTypeSpecifier getImageTypeSpecifier(final File fileBeingProcessed) {
// Check that this file is actually good
//
if(!fileBeingProcessed.exists() || !fileBeingProcessed.canRead() || !fileBeingProcessed.isFile())
return null;
// replacing chars on input path
String validFileName;
try {
validFileName = fileBeingProcessed.getCanonicalPath();
validFileName = FilenameUtils.normalize(validFileName);
} catch (IOException e1) {
return null;
}
validFileName = FilenameUtils.getName(validFileName);
ImageInputStream inStream = null;
ImageReader imageioReader = null;
try {
//
// Getting an ImageIO reader for this file.
//
inStream = ImageIO.createImageInputStream(fileBeingProcessed);
if (inStream == null) {
return null;
}
inStream.mark();
final Iterator<ImageReader> it = ImageIO.getImageReaders(inStream);
if (it.hasNext()) {
imageioReader = it.next();
if (imageioReader != null) {
imageioReader.setInput(inStream);
}
} else {
imageioReader = null;
}
// did we found a reader
if (imageioReader == null) {
return null;
}
//
// Get the type specifier for this image
//
final ImageTypeSpecifier its = ((ImageTypeSpecifier) imageioReader.getImageTypes(0).next());
return its;
} catch (IOException e) {
return null;
} catch (ArrayIndexOutOfBoundsException e) {
return null;
} finally {
//
// release resources
//
try {
if (inStream != null)
inStream.close();
} catch (Throwable e) {
// ignore exception
if (LOGGER.isLoggable(Level.FINEST))
LOGGER.log(Level.FINEST, e.getLocalizedMessage(), e);
}
try {
if (imageioReader != null)
imageioReader.dispose();
} catch (Throwable e) {
// ignore exception
if (LOGGER.isLoggable(Level.FINEST))
LOGGER.log(Level.FINEST, e.getLocalizedMessage(), e);
}
}
}
/**
* Checks the provided {@link URL} in order to see if it is a a query to build a mosaic or not.
*
* @param sourceURL
* @return a modified version of the provided {@link URL} which points to a shapefile in case we created a mosaic, or to the original {@link URL}otherwise.
*/
static URL checkURLForMosaicQuery(final URL sourceURL ) {
////
//
// Query with parameters, it might be that the user is
// trying to build the mosaic specifying the params as
// well
//
////
if (sourceURL.getProtocol().equalsIgnoreCase("file")) {
final String query=sourceURL.getQuery();
if(query!=null)
{
final String[] tokens = query.split("\\&");
final String locationPath=sourceURL.getPath();// remove 'file:' prefix
String indexName=null;
final File sourceDir= new File(locationPath);
if(!(sourceDir.isDirectory()&&sourceDir.exists()&&sourceDir.canRead()))
return null;
String wildcardString=null;
boolean absolutePath=DEFAULT_PATH_BEHAVIOR;
for(String token:tokens)
{
//splitting token
final String[] values=token.split("\\=");
if(values[0].equalsIgnoreCase("name"))
indexName=values[1];
else
if(values[0].equalsIgnoreCase("w")||values[0].equalsIgnoreCase("wildcard"))
wildcardString=values[1];
else
if(values[0].equalsIgnoreCase("p")||values[0].equalsIgnoreCase("path"))
absolutePath=Boolean.parseBoolean(values[1]);
}
//now check if the shapefle is already there
final File shapeFile= new File(locationPath,indexName+".shp");
File propertiesFile = new File(locationPath,indexName+".properties");
if(!shapeFile.exists()||!shapeFile.canRead()||!shapeFile.isFile()||!propertiesFile.exists()||!propertiesFile.canRead()||!propertiesFile.isFile())
{
//try to build it
createMosaic(locationPath, indexName!=null?indexName:FilenameUtils.getBaseName(locationPath),wildcardString!=null?wildcardString:DEFAULT_WILCARD,absolutePath);
}
//check URL again
if(!shapeFile.exists()||!shapeFile.canRead()||!shapeFile.isFile()||!propertiesFile.exists()||!propertiesFile.canRead()||!propertiesFile.isFile())
return null;
else
try {
return shapeFile.toURI().toURL();
} catch (MalformedURLException e) {
if(LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE,e.getLocalizedMessage(),e);
}
}
}
return sourceURL;
}
static MosaicConfigurationBean loadPropertiesFile(final URL sourceURL, final CoordinateReferenceSystem crs, final String defaultLocationAttribute){
return loadPropertiesFile(sourceURL, crs, defaultLocationAttribute, null);
}
static MosaicConfigurationBean loadPropertiesFile(final URL sourceURL, final CoordinateReferenceSystem crs, final String defaultLocationAttribute, final Set<String> ignorePropertiesSet){
//ret value
final MosaicConfigurationBean retValue= new MosaicConfigurationBean();
final boolean ignoreSome = ignorePropertiesSet != null && !ignorePropertiesSet.isEmpty();
//
// load the properties file
//
final Properties properties = new Properties();
URL propsURL = DataUtilities.changeUrlExt(sourceURL, "properties");
InputStream stream = null;
InputStream openStream = null;
try {
openStream = propsURL.openStream();
stream = new BufferedInputStream(openStream);
properties.load(stream);
} catch (FileNotFoundException e) {
if(LOGGER.isLoggable(Level.SEVERE))
LOGGER.log(Level.SEVERE,e.getLocalizedMessage(),e);
return null;
} catch (IOException e) {
if(LOGGER.isLoggable(Level.SEVERE))
LOGGER.log(Level.SEVERE,e.getLocalizedMessage(),e);
return null;
} finally {
if(stream!=null)
IOUtils.closeQuietly(stream);
if (openStream != null)
IOUtils.closeQuietly(openStream);
}
String pairs[] = null;
String pair[] = null;
//
// load the envelope
//
if((!ignoreSome && !properties.containsKey(Prop.ENVELOPE2D))
||ignoreSome && !ignorePropertiesSet.contains(Prop.ENVELOPE2D) && !properties.containsKey(Prop.ENVELOPE2D)) {
if(LOGGER.isLoggable(Level.SEVERE))
LOGGER.severe("Required key Envelope2D not found.");
return null;
}
if (!ignoreSome || !ignorePropertiesSet.contains(Prop.ENVELOPE2D)){
final String envelope = properties.getProperty(Prop.ENVELOPE2D).trim();
pairs = envelope.split(" ");
final double cornersV[][] = new double[2][2];
for (int i = 0; i < 2; i++) {
pair = pairs[i].split(",");
cornersV[i][0] = Double.parseDouble(pair[0]);
cornersV[i][1] = Double.parseDouble(pair[1]);
}
final GeneralEnvelope originalEnvelope = new GeneralEnvelope(cornersV[0], cornersV[1]);
originalEnvelope.setCoordinateReferenceSystem(crs);
retValue.setEnvelope2D(new Envelope2D(originalEnvelope));
}
//
// resolutions levels
//
if (!ignoreSome || !ignorePropertiesSet.contains(Prop.LEVELS)){
int levelsNumber = Integer.parseInt(properties.getProperty(Prop.LEVELS_NUM,"1").trim()) ;
retValue.setLevelsNum(levelsNumber);
if(!properties.containsKey(Prop.LEVELS)) {
if(LOGGER.isLoggable(Level.SEVERE))
LOGGER.severe("Required key Levels not found.");
return null;
}
final String levels = properties.getProperty(Prop.LEVELS).trim();
pairs = levels.split(" ");
if (pairs == null || pairs.length != levelsNumber) {
LOGGER.severe("Levels number is different from the provided number of levels resoltion.");
return null;
}
final double[][] resolutions = new double[levelsNumber][2];
for (int i = 0; i < levelsNumber; i++) {
pair = pairs[i].split(",");
if (pair == null || pair.length != 2) {
if (LOGGER.isLoggable(Level.SEVERE))
LOGGER.severe("OverviewLevel number is different from the provided number of levels resoltion.");
return null;
}
resolutions[i][0] = Double.parseDouble(pair[0]);
resolutions[i][1] = Double.parseDouble(pair[1]);
}
retValue.setLevels(resolutions);
}
//
// suggested spi is optional
//
if (!ignoreSome || !ignorePropertiesSet.contains(Prop.SUGGESTED_SPI)) {
if (properties.containsKey(Prop.SUGGESTED_SPI)) {
String suggestedSPI = properties.getProperty(Prop.SUGGESTED_SPI).trim();
retValue.setSuggestedSPI(suggestedSPI);
}
}
//
// name is not optional
//
if (!ignoreSome || !ignorePropertiesSet.contains(Prop.NAME)){
if(!properties.containsKey(Prop.NAME)) {
if(LOGGER.isLoggable(Level.SEVERE))
LOGGER.severe("Required key Name not found.");
return null;
}
String coverageName = properties.getProperty(Prop.NAME).trim();
retValue.setName(coverageName);
}
// need a color expansion?
// this is a newly added property we have to be ready to the case where
// we do not find it.
if (!ignoreSome || !ignorePropertiesSet.contains(Prop.EXP_RGB)) {
final boolean expandMe = Boolean.valueOf(properties.getProperty(Prop.EXP_RGB,"false").trim());
retValue.setExpandToRGB(expandMe);
}
//
// Absolute or relative path
//
if (!ignoreSome || !ignorePropertiesSet.contains(Prop.ABSOLUTE_PATH)) {
boolean absolutePath = Boolean.parseBoolean(properties.getProperty(Prop.ABSOLUTE_PATH, Boolean.toString(ImageMosaicUtils.DEFAULT_PATH_BEHAVIOR)).trim());
retValue.setAbsolutePath(absolutePath);
}
//
// Footprint management
//
if (!ignoreSome || !ignorePropertiesSet.contains(Prop.FOOTPRINT_MANAGEMENT)) {
final boolean footprintManagement=Boolean.valueOf(properties.getProperty(Prop.FOOTPRINT_MANAGEMENT, "false").trim());
retValue.setFootprintManagement(footprintManagement);
}
//
// location
//
if (!ignoreSome || !ignorePropertiesSet.contains(Prop.LOCATION_ATTRIBUTE)) {
retValue.setLocationAttribute(properties.getProperty(Prop.LOCATION_ATTRIBUTE, ImageMosaicUtils.DEFAULT_LOCATION_ATTRIBUTE).trim());
}
//retrn value
return retValue;
}
/**
* Returns a suitable threshold depending on the {@link DataBuffer} type.
*
* <p>
* Remember that the threshold works with >=.
*
* @param dataType
* to create a low threshold for.
* @return a minimum threshold value suitable for this data type.
*/
static double getThreshold(int dataType) {
switch (dataType) {
case DataBuffer.TYPE_BYTE:
case DataBuffer.TYPE_USHORT:
// this may cause problems and truncations when the native mosaic operations is enabled
return 0.0;
case DataBuffer.TYPE_INT:
return Integer.MIN_VALUE;
case DataBuffer.TYPE_SHORT:
return Short.MIN_VALUE;
case DataBuffer.TYPE_DOUBLE:
return -Double.MAX_VALUE;
case DataBuffer.TYPE_FLOAT:
return -Float.MAX_VALUE;
}
return 0;
}
/**
* Builds a {@link ReferencedEnvelope} from a {@link GeographicBoundingBox}.
* This is useful in order to have an implementation of {@link BoundingBox}
* from a {@link GeographicBoundingBox} which strangely does implement
* {@link GeographicBoundingBox}.
*
* @param geographicBBox
* the {@link GeographicBoundingBox} to convert.
* @return an instance of {@link ReferencedEnvelope}.
*/
static ReferencedEnvelope getReferencedEnvelopeFromGeographicBoundingBox(final GeographicBoundingBox geographicBBox) {
Utilities.ensureNonNull("GeographicBoundingBox", geographicBBox);
return new ReferencedEnvelope(geographicBBox.getEastBoundLongitude(),
geographicBBox.getWestBoundLongitude(), geographicBBox
.getSouthBoundLatitude(), geographicBBox
.getNorthBoundLatitude(), DefaultGeographicCRS.WGS84);
}
/**
* @param transparentColor
* @param image
* @return
* @throws IllegalStateException
*/
static RenderedImage makeColorTransparent(
final Color transparentColor, final RenderedImage image)
throws IllegalStateException {
final ImageWorker w = new ImageWorker(image);
if (image.getSampleModel() instanceof MultiPixelPackedSampleModel)
w.forceComponentColorModel();
return w.makeColorTransparent(transparentColor).getRenderedImage();
}
static ImageReadParam cloneImageReadParam(
ImageReadParam param) {
// The ImageReadParam passed in is non-null. As the
// ImageReadParam class is not Cloneable, if the param
// class is simply ImageReadParam, then create a new
// ImageReadParam instance and set all its fields
// which were set in param. This will eliminate problems
// with concurrent modification of param for the cases
// in which there is not a special ImageReadparam used.
// Create a new ImageReadParam instance.
ImageReadParam newParam = new ImageReadParam();
// Set all fields which need to be set.
// IIOParamController field.
if(param.hasController()) {
newParam.setController(param.getController());
}
// Destination fields.
newParam.setDestination(param.getDestination());
if(param.getDestinationType() != null) {
// Set the destination type only if non-null as the
// setDestinationType() clears the destination field.
newParam.setDestinationType(param.getDestinationType());
}
newParam.setDestinationBands(param.getDestinationBands());
newParam.setDestinationOffset(param.getDestinationOffset());
// Source fields.
newParam.setSourceBands(param.getSourceBands());
newParam.setSourceRegion(param.getSourceRegion());
if(param.getSourceMaxProgressivePass() != Integer.MAX_VALUE) {
newParam.setSourceProgressivePasses(
param.getSourceMinProgressivePass(),
param.getSourceNumProgressivePasses());
}
if(param.canSetSourceRenderSize()) {
newParam.setSourceRenderSize(param.getSourceRenderSize());
}
newParam.setSourceSubsampling(param.getSourceXSubsampling(),
param.getSourceYSubsampling(),
param.getSubsamplingXOffset(),
param.getSubsamplingYOffset());
// Replace the local variable with the new ImageReadParam.
return newParam;
}
/**
* Look for an {@link ImageReader} instance that is able to read the provided {@link ImageInputStream}, which must be non null.
*
* <p>
* In case no reader is found, <code>null</code> is returned.
*
* @param inStream an instance of {@link ImageInputStream} for which we need to find a suitable {@link ImageReader}.
* @return a suitable instance of {@link ImageReader} or <code>null</code> if one cannot be found.
*/
static ImageReader getReader(
final ImageInputStream inStream) {
Utilities.ensureNonNull("inStream", inStream);
// get a reader
inStream.mark();
final Iterator<ImageReader> readersIt = ImageIO.getImageReaders(inStream);
if(!readersIt.hasNext())
{
return null;
}
return readersIt.next();
}
/**
* Retrieves the dimensions of the {@link RenderedImage} at index <code>imageIndex</code> for the provided
* {@link ImageReader} and {@link ImageInputStream}.
*
* <p>
* Notice that none of the input parameters can be <code>null</code> or a {@link NullPointerException} will be thrown.
* Morevoer the <code>imageIndex</code> cannot be negative or an {@link IllegalArgumentException} will be thrown.
*
* @param imageIndex the index of the image to get the dimensions for.
* @param inStream the {@link ImageInputStream} to use as an input
* @param reader the {@link ImageReader} to decode the image dimensions.
* @return a {@link Rectangle} that contains the dimensions for the image at index <code>imageIndex</code>
* @throws IOException in case the {@link ImageReader} or the {@link ImageInputStream} fail.
*/
static Rectangle getDimension(
final int imageIndex,
final ImageInputStream inStream,
final ImageReader reader) throws IOException {
Utilities.ensureNonNull("inStream", inStream);
Utilities.ensureNonNull("reader", reader);
if(imageIndex<0)
throw new IllegalArgumentException(Errors.format(ErrorKeys.INDEX_OUT_OF_BOUNDS_$1,imageIndex));
inStream.reset();
reader.setInput(inStream);
return new Rectangle(0,0,reader.getWidth(imageIndex),reader.getHeight(imageIndex));
}
/**
* Retrieves an {@link ImageInputStream} for the provided input {@link File}.
*
* @param file
* @return
* @throws IOException
*/
public static ImageInputStream getInputStream(final File file) throws IOException {
final ImageInputStream inStream= ImageIO.createImageInputStream(file);
if (inStream == null) {
return null;
}
return inStream;
}
/**
* Retrieves an {@link ImageInputStream} for the provided input {@link URL}.
*
* @param url
* @return
* @throws IOException
*/
static ImageInputStream getInputStream(final URL url)
throws IOException {
final ImageInputStream inStream= cachedStreamSPI.createInputStreamInstance(url,ImageIO.getUseCache(),ImageIO.getCacheDirectory());
if(inStream==null)
return null;
return inStream;
}
/**
* Checks that the provided <code>dimensions</code> when intersected with the source region
* used by the provided {@link ImageReadParam} instance does not result in an empty {@link Rectangle}.
*
* <p>
* Input parameters cannot be null.
*
* @param readParameters an instance of {@link ImageReadParam} for which we want to check the source region element.
* @param dimensions an instance of {@link Rectangle} to use for the check.
* @return <code>true</code> if the intersection is not empty, <code>false</code> otherwise.
*/
static boolean checkEmptySourceRegion(
final ImageReadParam readParameters,
final Rectangle dimensions) {
Utilities.ensureNonNull("readDimension", dimensions);
Utilities.ensureNonNull("readP", readParameters);
final Rectangle sourceRegion=readParameters.getSourceRegion();
Rectangle.intersect(sourceRegion, dimensions, sourceRegion);
if(sourceRegion.isEmpty())
return true;
readParameters.setSourceRegion(sourceRegion);
return false;
}
/**
* Default priority for the underlying {@link Thread}.
*/
public static final int DEFAULT_PRIORITY = Thread.NORM_PRIORITY;
/**
* Default location attribute name.
*/
public static final String DEFAULT_LOCATION_ATTRIBUTE = "location";
public static final String DEFAULT_INDEX_NAME = "index";
static boolean checkURLReadable(URL url) {
try {
url.openStream().close();
} catch (Exception e) {
return false;
}
return true;
}
/**
* Store a sample image from which we can derive the default SM and CM
*
* @param sampleImageFile
* where we should store the image
* @param defaultSM
* the {@link SampleModel} for the sample image.
* @param defaultCM
* the {@link ColorModel} for the sample image.
* @throws IOException
* in case something bad occurs during writing.
*/
static void storeSampleImage(
final File sampleImageFile,
final SampleModel defaultSM,
final ColorModel defaultCM)
throws IOException {
// create 1X1 image
final SampleModel sm = defaultSM.createCompatibleSampleModel(1, 1);
final WritableRaster raster = RasterFactory.createWritableRaster(sm, null);
final BufferedImage sampleImage = new BufferedImage(defaultCM, raster, false, null);
// serialize it
OutputStream outStream = null;
ObjectOutputStream ooStream = null;
try {
outStream = new BufferedOutputStream(new FileOutputStream(sampleImageFile));
ooStream = new ObjectOutputStream(outStream);
ooStream.writeObject(new SerializableRenderedImage(sampleImage));
} finally {
try {
if (ooStream != null)
ooStream.close();
} catch (Throwable e) {
IOUtils.closeQuietly(ooStream);
}
try {
if (outStream != null)
outStream.close();
} catch (Throwable e) {
IOUtils.closeQuietly(outStream);
}
}
}
/**
* Build a background values array using the same dataType of the input {@link SampleModel} (if available).
*
* @param sampleModel
* @param backgroundValues
* @return
*/
static Number[] getBackgroundValues(final SampleModel sampleModel, final double[] backgroundValues) {
Number[] values = null;
final int dataType = sampleModel != null ? sampleModel.getDataType() : DataBuffer.TYPE_DOUBLE;
final int numBands=sampleModel.getNumBands();
switch (dataType){
case DataBuffer.TYPE_BYTE:
values = new Byte[numBands];
if (backgroundValues == null){
Arrays.fill(values, Byte.valueOf((byte)0));
}
else{
//we have background values available
for (int i = 0; i < values.length; i++)
values[i] = i>=backgroundValues.length?Byte.valueOf((byte)backgroundValues[0]):Byte.valueOf((byte)backgroundValues[i]);
break;
}
case DataBuffer.TYPE_SHORT:
case DataBuffer.TYPE_USHORT:
values = new Short[numBands] ;
if (backgroundValues == null)
Arrays.fill(values, Short.valueOf((short)0));
else{
//we have background values available
values = new Short[backgroundValues.length];
for (int i = 0; i < values.length; i++)
values[i] = i>=backgroundValues.length?Short.valueOf((short)backgroundValues[0]):Short.valueOf((short)backgroundValues[i]);
break;
}
case DataBuffer.TYPE_INT:
values = new Integer[numBands] ;
if (backgroundValues == null)
Arrays.fill(values, Integer.valueOf((int)0));
else{
//we have background values available
for (int i = 0; i < values.length; i++)
values[i] = i>=backgroundValues.length?Integer.valueOf((int)backgroundValues[0]):Integer.valueOf((int)backgroundValues[i]);
break;
}
case DataBuffer.TYPE_FLOAT:
values = new Float[numBands] ;
if (backgroundValues == null)
Arrays.fill(values, Float.valueOf(0.f));
else{
//we have background values available
for (int i = 0; i < values.length; i++)
values[i] = i>=backgroundValues.length?Float.valueOf((float)backgroundValues[0]):Float.valueOf((float)backgroundValues[i]);
break;
}
case DataBuffer.TYPE_DOUBLE:
values = new Double[numBands] ;
if (backgroundValues == null)
Arrays.fill(values, Double.valueOf(0.d));
else{
//we have background values available
values = new Double[backgroundValues.length];
for (int i = 0; i < values.length; i++)
values[i] = i>=backgroundValues.length?Double.valueOf((Double)backgroundValues[0]):Double.valueOf((Double)backgroundValues[i]);
break;
}
}
return values;
}
}