/* * 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.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.io.Serializable; import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; import java.util.HashMap; 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.ImageReader; import javax.imageio.spi.ImageInputStreamSpi; import javax.imageio.stream.ImageInputStream; import javax.media.jai.Histogram; import javax.media.jai.RasterFactory; import javax.media.jai.RenderedOp; import javax.media.jai.remote.SerializableRenderedImage; import net.sf.ehcache.Cache; import net.sf.ehcache.Element; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.filefilter.IOFileFilter; import org.geotools.data.DataAccessFactory.Param; import org.geotools.data.DataSourceException; import org.geotools.data.DataStoreFactorySpi; import org.geotools.data.DataUtilities; import org.geotools.data.shapefile.ShapefileDataStoreFactory; import org.geotools.factory.Hints; import org.geotools.gce.imagemosaic.catalogbuilder.CatalogBuilder; import org.geotools.gce.imagemosaic.catalogbuilder.CatalogBuilder.ExceptionEvent; import org.geotools.gce.imagemosaic.catalogbuilder.CatalogBuilder.ProcessingEvent; import org.geotools.gce.imagemosaic.catalogbuilder.CatalogBuilderConfiguration; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.operation.matrix.XAffineTransform; import org.geotools.resources.XArray; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Errors; import org.geotools.util.Converters; import org.geotools.util.Utilities; import com.sun.media.jai.operator.ImageReadDescriptor; import com.vividsolutions.jts.geom.Envelope; 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. * * * @source $URL: http://svn.osgeo.org/geotools/trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/Utils.java $ */ public class Utils { /** EHCache instance to cache histograms */ private static Cache ehcache; private final static String INDEXER_PROPERTIES = "indexer.properties"; /** RGB to GRAY coefficients (for Luminance computation) */ public final static double RGB_TO_GRAY_MATRIX [][]= {{ 0.114, 0.587, 0.299, 0 }}; /** * Flag indicating whether to compute optimized crop ops (instead of standard * mosaicking op) when possible (As an instance when mosaicking a single granule) */ final static boolean OPTIMIZE_CROP; static { final String prop = System.getProperty("org.geotools.imagemosaic.optimizecrop"); if (prop != null && prop.equalsIgnoreCase("FALSE")){ OPTIMIZE_CROP = false; } else { OPTIMIZE_CROP = true; } } 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 String HETEROGENEOUS = "Heterogeneous"; static final String TIME_ATTRIBUTE = "TimeAttribute"; static final String ELEVATION_ATTRIBUTE = "ElevationAttribute"; static final String RUNTIME_ATTRIBUTE = "RuntimeAttribute"; final static String CACHING= "Caching"; //Indexer Properties static final String ABSOLUTE = "Absolute"; static final String RECURSIVE = "Recursive"; static final String WILDCARD = "Wildcard"; static final String SCHEMA = "Schema"; static final String RESOLUTION_LEVELS = "ResolutionLevels"; static final String PROPERTY_COLLECTORS = "PropertyCollectors"; } /** * Logger. */ private final static Logger LOGGER = org.geotools.util.logging.Logging .getLogger(Utils.class.toString()); /** * Default wildcard for creating mosaics. */ public static final String DEFAULT_WILCARD = "*.*"; /** * Default path behavior with respect to absolute paths. */ public static final boolean DEFAULT_PATH_BEHAVIOR = false; /** * Default path behavior with respect to index caching. */ private static final boolean DEFAULT_CACHING_BEHAVIOR = false; /** * Cached instance of {@link URLImageInputStreamSpi} for creating * {@link ImageInputStream} instances. */ private static ImageInputStreamSpi CACHED_STREAM_SPI = 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 * @param absolutePath * tells the catalogue builder to use absolute paths. * @param hints hints to control reader instantiations * @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, final Hints hints) { // create a mosaic index builder and set the relevant elements final CatalogBuilderConfiguration configuration = new CatalogBuilderConfiguration(); configuration.setAbsolute(absolutePath); configuration.setHints(hints); configuration.setRootMosaicDirectory(location); configuration.setIndexingDirectories(Arrays.asList(location)); configuration.setIndexName(indexName); // look for and indexed.properties file final File parent = new File(location); final File indexerProperties = new File(parent, INDEXER_PROPERTIES); if (Utils.checkFileReadable(indexerProperties)) { // load it and parse it final Properties props = Utils.loadPropertiesFromURL(DataUtilities .fileToURL(indexerProperties)); // name if (props.containsKey(Prop.NAME)) configuration.setIndexName(props.getProperty(Prop.NAME)); // absolute if (props.containsKey(Prop.ABSOLUTE)) configuration.setAbsolute(Boolean.valueOf(props .getProperty(Prop.ABSOLUTE))); // recursive if (props.containsKey(Prop.RECURSIVE)) configuration.setRecursive(Boolean.valueOf(props .getProperty(Prop.RECURSIVE))); // wildcard if (props.containsKey(Prop.WILDCARD)) configuration.setWildcard(props.getProperty(Prop.WILDCARD)); // schema if (props.containsKey(Prop.SCHEMA)) configuration.setSchema(props.getProperty(Prop.SCHEMA)); // time attr if (props.containsKey(Prop.TIME_ATTRIBUTE)) configuration.setTimeAttribute(props.getProperty(Prop.TIME_ATTRIBUTE)); // elevation attr if (props.containsKey(Prop.ELEVATION_ATTRIBUTE)) configuration.setElevationAttribute(props.getProperty(Prop.ELEVATION_ATTRIBUTE)); // runtime attr if (props.containsKey(Prop.RUNTIME_ATTRIBUTE)) configuration.setRuntimeAttribute(props.getProperty(Prop.RUNTIME_ATTRIBUTE)); // imposed BBOX if (props.containsKey(Prop.ENVELOPE2D)) configuration.setEnvelope2D(props.getProperty(Prop.ENVELOPE2D)); // imposed Pyramid Layout if (props.containsKey(Prop.RESOLUTION_LEVELS)) configuration.setResolutionLevels(props.getProperty(Prop.RESOLUTION_LEVELS)); // collectors if (props.containsKey(Prop.PROPERTY_COLLECTORS)) configuration.setPropertyCollectors(props.getProperty(Prop.PROPERTY_COLLECTORS)); if (props.containsKey(Prop.CACHING)) configuration.setCaching(Boolean.valueOf(props.getProperty(Prop.CACHING))); } // create the builder final CatalogBuilder catalogBuilder = new CatalogBuilder(configuration); // this is going to help us with catching exceptions and logging them final Queue<Throwable> exceptions = new LinkedList<Throwable>(); try { final CatalogBuilder.ProcessingEventListener listener = new CatalogBuilder.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()); } }; catalogBuilder.addProcessingEventListener(listener); catalogBuilder.run(); } catch (Throwable e) { LOGGER.log(Level.SEVERE, "Unable to build mosaic", e); return false; } finally { catalogBuilder.dispose(); } // check that nothing bad happened if (exceptions.size() > 0) return false; return true; } public static String getMessageFromException(Exception exception) { if (exception.getLocalizedMessage() != null) return exception.getLocalizedMessage(); else return exception.getMessage(); } static URL checkSource(Object source) throws MalformedURLException, DataSourceException { return checkSource(source, null); } static MosaicConfigurationBean loadMosaicProperties(final URL sourceURL, final String defaultLocationAttribute) { return loadMosaicProperties(sourceURL, defaultLocationAttribute, null); } static MosaicConfigurationBean loadMosaicProperties( final URL sourceURL, 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 // URL propsURL = sourceURL; if (!sourceURL.toExternalForm().endsWith(".properties")) propsURL = DataUtilities.changeUrlExt(sourceURL, "properties"); final Properties properties = loadPropertiesFromURL(propsURL); if (properties == null) { if (LOGGER.isLoggable(Level.INFO)) LOGGER.info("Unable to load mosaic properties file"); return null; } String[] pairs = null; String pair[] = null; // // imposed bbox is optional // if (!ignoreSome || !ignorePropertiesSet.contains(Prop.ENVELOPE2D)) { String bboxString = properties.getProperty(Prop.ENVELOPE2D, null); if(bboxString!=null){ bboxString=bboxString.trim(); try{ ReferencedEnvelope bbox = parseEnvelope(bboxString); if(bbox!=null) retValue.setEnvelope(bbox); else if (LOGGER.isLoggable(Level.INFO)) LOGGER.info("Cannot parse imposed bbox."); }catch (Exception e) { if (LOGGER.isLoggable(Level.INFO)) LOGGER.log(Level.INFO,"Cannot parse imposed bbox.",e); } } } // // 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.INFO)) LOGGER.info("Required key Levels not found."); return null; } final String levels = properties.getProperty(Prop.LEVELS).trim(); pairs = levels.split(" "); if (pairs == null || pairs.length != levelsNumber) { if (LOGGER.isLoggable(Level.INFO)) LOGGER .info("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.INFO)) LOGGER .info("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)) { final String suggestedSPI = properties.getProperty( Prop.SUGGESTED_SPI).trim(); retValue.setSuggestedSPI(suggestedSPI); } } // // time attribute is optional // if (properties.containsKey(Prop.TIME_ATTRIBUTE)) { final String timeAttribute = properties.getProperty("TimeAttribute").trim(); retValue.setTimeAttribute(timeAttribute); } // // elevation attribute is optional // if (properties.containsKey(Prop.ELEVATION_ATTRIBUTE)) { final String elevationAttribute = properties.getProperty(Prop.ELEVATION_ATTRIBUTE).trim(); retValue.setElevationAttribute(elevationAttribute); } // // runtime attribute is optional // if (properties.containsKey(Prop.RUNTIME_ATTRIBUTE)) { final String runtimeAttribute = properties.getProperty(Prop.RUNTIME_ATTRIBUTE).trim(); retValue.setRuntimeAttribute(runtimeAttribute); } // // caching // if (properties.containsKey(Prop.CACHING)) { String caching = properties.getProperty(Prop.CACHING).trim(); try { retValue.setCaching(Boolean.valueOf(caching)); } catch (Throwable e) { retValue.setCaching(Boolean.valueOf(Utils.DEFAULT_CACHING_BEHAVIOR)); } } // // 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); } // // Is heterogeneous granules mosaic // if (!ignoreSome || !ignorePropertiesSet.contains(Prop.HETEROGENEOUS)) { final boolean heterogeneous = Boolean.valueOf(properties.getProperty( Prop.HETEROGENEOUS, "false").trim()); retValue.setHeterogeneous(heterogeneous); } // // Absolute or relative path // if (!ignoreSome || !ignorePropertiesSet.contains(Prop.ABSOLUTE_PATH)) { final boolean absolutePath = Boolean.valueOf(properties .getProperty(Prop.ABSOLUTE_PATH, Boolean.toString(Utils.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, Utils.DEFAULT_LOCATION_ATTRIBUTE) .trim()); } // return value return retValue; } /** * Parses a bbox in the form of MIX,MINY MAXX,MAXY * @param bboxString the string to parse the bbox from * @return a {@link ReferencedEnvelope} with the parse bbox or null */ public static ReferencedEnvelope parseEnvelope(final String bboxString) { if(bboxString==null||bboxString.length()==0) return null; final String[] pairs = bboxString.split(" "); if (pairs != null &&pairs.length == 2) { String[] pair1 = pairs[0].split(","); String[] pair2 = pairs[1].split(","); if(pair1!=null&&pair1.length==2&&pair2!=null&&pair2.length==2) return new ReferencedEnvelope( Double.parseDouble(pair1[0]), Double.parseDouble(pair2[0]), Double.parseDouble(pair1[1]), Double.parseDouble(pair2[1]), null); } // something bad happened return null; } public static Properties loadPropertiesFromURL(URL propsURL) { final Properties properties = new 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); } return properties; } public static IOFileFilter excludeFilters(final IOFileFilter inputFilter, IOFileFilter... filters) { IOFileFilter retFilter = inputFilter; for (IOFileFilter filter : filters) { retFilter = FileFilterUtils.andFileFilter(retFilter, FileFilterUtils.notFileFilter(filter)); } return retFilter; } /** * Retrieves an {@link ImageInputStream} for the provided input {@link File} * . * * @param file * @return * @throws IOException */ static ImageInputStream getInputStream(final File file) throws IOException { Utilities.ensureNonNull("file", file); 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 { Utilities.ensureNonNull("url", url); final ImageInputStream inStream = CACHED_STREAM_SPI .createInputStreamInstance(url, ImageIO.getUseCache(), ImageIO .getCacheDirectory()); if (inStream == null) return null; return inStream; } /** * 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"; /** * Checks that a {@link File} is a real file, exists and is readable. * * @param file * the {@link File} instance to check. Must not be null. * * @return <code>true</code> in case the file is a real file, exists and is * readable; <code>false </code> otherwise. */ public static boolean checkFileReadable(final File file) { if (LOGGER.isLoggable(Level.FINE)) { final StringBuilder builder = new StringBuilder(); builder.append("Checking file:").append( FilenameUtils.getFullPath(file.getAbsolutePath())).append( "\n"); builder.append("canRead:").append(file.canRead()).append("\n"); builder.append("isHidden:").append(file.isHidden()).append("\n"); builder.append("isFile").append(file.isFile()).append("\n"); builder.append("canWrite").append(file.canWrite()).append("\n"); LOGGER.fine(builder.toString()); } if (!file.exists() || !file.canRead() || !file.isFile()) return false; return true; } /** * @param testingDirectory * @return * @throws IllegalArgumentException * @throws IOException */ public static String checkDirectoryReadable(String testingDirectory) throws IllegalArgumentException { File inDir = new File(testingDirectory); if (!inDir.isDirectory() || !inDir.canRead()) { LOGGER.severe("Provided input dir does not exist or is not a dir!"); throw new IllegalArgumentException( "Provided input dir does not exist or is not a dir!"); } try { testingDirectory = inDir.getCanonicalPath(); } catch (IOException e) { throw new IllegalArgumentException(e); } testingDirectory = FilenameUtils.normalize(testingDirectory); if (!testingDirectory.endsWith(File.separator)) testingDirectory = testingDirectory + File.separator; // test to see if things are still good inDir = new File(testingDirectory); if (!inDir.isDirectory() || !inDir.canRead()) { LOGGER.severe("Provided input dir does not exist or is not a dir!"); throw new IllegalArgumentException( "Provided input dir does not exist or is not a dir!"); } return testingDirectory; } static public boolean checkURLReadable(URL url) { try { url.openStream().close(); } catch (Exception e) { return false; } return true; } public static final DataStoreFactorySpi SHAPE_SPI = new ShapefileDataStoreFactory(); public static final DataStoreFactorySpi INDEXED_SHAPE_SPI = new ShapefileDataStoreFactory(); static final String DIRECT_KAKADU_PLUGIN = "it.geosolutions.imageio.plugins.jp2k.JP2KKakaduImageReader"; public static final boolean DEFAULT_RECURSION_BEHAVIOR = true; /** * * @param datastoreProperties * @return * @throws IOException */ public static Map<String, Serializable> createDataStoreParamsFromPropertiesFile( final URL datastoreProperties) throws IOException { // read the properties file Properties properties = loadPropertiesFromURL(datastoreProperties); if (properties == null) return null; // SPI final String SPIClass = properties.getProperty("SPI"); try { // create a datastore as instructed final DataStoreFactorySpi spi = (DataStoreFactorySpi) Class.forName(SPIClass).newInstance(); return createDataStoreParamsFromPropertiesFile(properties, spi); } catch (ClassNotFoundException e) { final IOException ioe = new IOException(); throw (IOException) ioe.initCause(e); } catch (InstantiationException e) { final IOException ioe = new IOException(); throw (IOException) ioe.initCause(e); } catch (IllegalAccessException e) { final IOException ioe = new IOException(); throw (IOException) ioe.initCause(e); } } /** * 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. */ public 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; SerializableRenderedImage sri = null; try { outStream = new BufferedOutputStream(new FileOutputStream( sampleImageFile)); ooStream = new ObjectOutputStream(outStream); sri = new SerializableRenderedImage(sampleImage, true); ooStream.writeObject(sri); } 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); } try { if (sri != null) sri.dispose(); } catch (Throwable e) { } } } /** * 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. */ public static RenderedImage loadSampleImage(final File sampleImageFile) { // serialize it InputStream inStream = null; ObjectInputStream oiStream = null; try { // do we have the sample image?? if (Utils.checkFileReadable(sampleImageFile)) { inStream = new BufferedInputStream(new FileInputStream( sampleImageFile)); oiStream = new ObjectInputStream(inStream); // load the image return (RenderedImage) oiStream.readObject(); } else { if (LOGGER.isLoggable(Level.WARNING)) 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); } } } /** * A transparent color for missing data. */ static final Color TRANSPARENT = new Color(0,0,0,0); final static Boolean IGNORE_FOOTPRINT = Boolean.getBoolean("org.geotools.footprint.ignore"); public static final boolean DEFAULT_FOOTPRINT_MANAGEMENT = true; public static final boolean DEFAULT_CONFIGURATION_CACHING = true; /** * Build a background values array using the same dataType of the input {@link SampleModel} (if * available) and the values provided in the input array. * * @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 != null? sampleModel.getNumBands() : 1; 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 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 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; } public static Map<String, Serializable> createDataStoreParamsFromPropertiesFile( Properties properties, DataStoreFactorySpi spi) throws IOException { // get the params final Map<String, Serializable> params = new HashMap<String, Serializable>(); final Param[] paramsInfo = spi.getParametersInfo(); for (Param p : paramsInfo) { // search for this param and set the value if found if (properties.containsKey(p.key)) params.put(p.key, (Serializable) Converters.convert(properties.getProperty(p.key), p.type)); else if (p.required && p.sample == null) throw new IOException("Required parameter missing: "+ p.toString()); } return params; } static URL checkSource(Object source, Hints hints) { 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); } else if (source instanceof URL) { sourceURL = (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); 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()) // real file, can only be a shapefile at this stage or a // datastore.properties file 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 defaultIndexName = FilenameUtils.getName(locationPath); boolean datastoreFound = false; boolean buildMosaic = false; // // do we have a datastore properties file? It will preempt on // the shapefile // File dataStoreProperties = new File(locationPath,"datastore.properties"); // this can be used to look for properties files that do NOT // define a datastore final File[] properties = sourceFile .listFiles((FilenameFilter) FileFilterUtils .andFileFilter( FileFilterUtils .notFileFilter(FileFilterUtils .nameFileFilter("datastore.properties")), FileFilterUtils .makeFileOnly(FileFilterUtils .suffixFileFilter(".properties")))); // do we have a valid datastore + mosaic properties pair? if (Utils.checkFileReadable(dataStoreProperties)) { // we have a datastore.properties file datastoreFound = true; // check the first valid mosaic properties boolean found = false; for (File propFile : properties) if (Utils.checkFileReadable(propFile)) { // load it if (null != Utils.loadMosaicProperties(DataUtilities.fileToURL(propFile),"location")) { found = true; break; } } // we did not find any good candidate for mosaic.properties // file, this will signal it if (!found) buildMosaic = true; } else { // we did not find any good candidate for mosaic.properties // file, this will signal it buildMosaic = true; datastoreFound = false; } // // now let's try with shapefile and properties couple // File shapeFile = null; if (!datastoreFound) { for (File propFile : properties) { // load properties if (null == Utils.loadMosaicProperties(DataUtilities.fileToURL(propFile), Utils.DEFAULT_LOCATION_ATTRIBUTE)) continue; // look for a couple shapefile, mosaic properties file shapeFile = new File(locationPath, FilenameUtils.getBaseName(propFile.getName())+ ".shp"); if (!Utils.checkFileReadable(shapeFile)&& Utils.checkFileReadable(propFile)) buildMosaic = true; else { buildMosaic = false; break; } } } // did we find anything? if (buildMosaic) { // try to build a mosaic inside this directory and see what // happens createMosaic(locationPath, defaultIndexName,DEFAULT_WILCARD, DEFAULT_PATH_BEHAVIOR,hints); // check that the mosaic properties file was created final File propertiesFile = new File(locationPath, defaultIndexName + ".properties"); if (!Utils.checkFileReadable(propertiesFile)) { sourceURL = null; return sourceURL; } // check that the shapefile was correctly created in case it // was needed if (!datastoreFound) { shapeFile = new File(locationPath, defaultIndexName+ ".shp"); if (!Utils.checkFileReadable(shapeFile)) sourceURL = null; else // now set the new source and proceed sourceURL = DataUtilities.fileToURL(shapeFile); } else { dataStoreProperties = new File(locationPath,"datastore.properties"); // datastore.properties as the source if (!Utils.checkFileReadable(dataStoreProperties)) sourceURL = null; else sourceURL = DataUtilities.fileToURL(dataStoreProperties); } } else // now set the new source and proceed sourceURL = datastoreFound ? DataUtilities.fileToURL(dataStoreProperties) : DataUtilities.fileToURL(shapeFile); } } else { // SK: We don't set SourceURL to null now, just because it doesn't // point to a file // sourceURL=null; } return sourceURL; } // /** // * Scan back the rendered op chain (navigating the sources) // * to find an {@link ImageReader} used to read the main source. // * // * @param rOp // * @return the {@link ImageReader} related to this operation, if any. // * {@code null} in case no readers are found. // */ // public static ImageReader getReader(RenderedImage rOp) { // if (rOp != null) { // if (rOp instanceof RenderedOp) { // RenderedOp renderedOp = (RenderedOp) rOp; // // final int nSources = renderedOp.getNumSources(); // if (nSources > 0) { // for (int k = 0; k < nSources; k++) { // Object source = null; // try { // source = renderedOp.getSourceObject(k); // // } catch (ArrayIndexOutOfBoundsException e) { // // Ignore // } // if (source != null) { // if (source instanceof RenderedOp) { // getReader((RenderedOp) source); // } // } // } // } else { // // get the reader // Object imageReader = rOp.getProperty(ImageReadDescriptor.PROPERTY_NAME_IMAGE_READER); // if (imageReader != null && imageReader instanceof ImageReader) { // final ImageReader reader = (ImageReader) imageReader; // return reader; // } // } // } // } // return null; // } static final double SAMEBBOX_THRESHOLD_FACTOR = 20; static final double AFFINE_IDENTITY_EPS = 1E-6; /** * Private constructor to initialize the ehCache instance. * It can be configured through a Bean. * @param ehcache */ private Utils(Cache ehcache) { Utils.ehcache = ehcache; } /** * Setup a {@link Histogram} object by deserializing * a file representing a serialized Histogram. * * @param file * @return the deserialized histogram. */ public static Histogram getHistogram(final String file){ Utilities.ensureNonNull("file", file); Histogram histogram = null; // Firstly: check if the histogram have been already // deserialized and it is available in cache if (ehcache != null && ehcache.isKeyInCache(file)){ if (ehcache.isElementInMemory(file)){ final Element element = ehcache.get(file); if (element != null){ final Serializable value = element.getValue(); if (value != null && value instanceof Histogram){ histogram = (Histogram) value; return histogram; } } } } // No histogram in cache. Deserializing... if (histogram == null){ FileInputStream fileStream = null; ObjectInputStream objectStream = null; try { fileStream = new FileInputStream(file); objectStream = new ObjectInputStream(fileStream); histogram = (Histogram) objectStream.readObject(); if (ehcache != null){ ehcache.put(new Element(file, histogram)); } } catch (FileNotFoundException e) { if (LOGGER.isLoggable(Level.FINE)){ LOGGER.fine("Unable to parse Histogram:" + e.getLocalizedMessage()); } } catch (IOException e) { if (LOGGER.isLoggable(Level.FINE)){ LOGGER.fine("Unable to parse Histogram:" + e.getLocalizedMessage()); } } catch (ClassNotFoundException e) { if (LOGGER.isLoggable(Level.FINE)){ LOGGER.fine("Unable to parse Histogram:" + e.getLocalizedMessage()); } } finally { if (objectStream != null){ IOUtils.closeQuietly(objectStream); } if (fileStream != null){ IOUtils.closeQuietly(fileStream); } } } return histogram; } /** * Check if the provided granule's footprint covers the same area of the granule's bbox. * @param granuleFootprint the granule Footprint * @param granuleBBOX the granule bbox * @return {@code true} in case the footprint isn't covering the full granule's bbox. */ static boolean areaIsDifferent( final Geometry granuleFootprint, final AffineTransform baseGridToWorld, final ReferencedEnvelope granuleBBOX) { // // // // First preliminar check: // check if the footprint's bbox corners are the same of the granule's bbox // (Using a threshold) // // // final Envelope envelope = granuleFootprint.getEnvelope().getEnvelopeInternal(); double deltaMinX = Math.abs(envelope.getMinX() - granuleBBOX.getMinX()); double deltaMinY = Math.abs(envelope.getMinY() - granuleBBOX.getMinY()); double deltaMaxX = Math.abs(envelope.getMaxX() - granuleBBOX.getMaxX()); double deltaMaxY = Math.abs(envelope.getMaxY() - granuleBBOX.getMaxY()); final double resX = XAffineTransform.getScaleX0(baseGridToWorld); final double resY = XAffineTransform.getScaleY0(baseGridToWorld); final double toleranceX = resX / Utils.SAMEBBOX_THRESHOLD_FACTOR; final double toleranceY = resY / Utils.SAMEBBOX_THRESHOLD_FACTOR; // Taking note of the area of a single cell final double cellArea = resX * resY; if (deltaMinX > toleranceX || deltaMaxX > toleranceX || deltaMinY > toleranceY || deltaMaxY > toleranceY){ // delta exceed tolerance. Area is not the same return true; } // // // // Second check: // Here, the footprint's bbox and the granule's bbox are equal. // However this is not enough: // - suppose the footprint is a diamond // - Create a rectangle by circumscribing the diamond // - If this rectangle match with the granule's bbox, this doesn't imply // that the diamond covers the same area of the bbox. // Therefore, we need to compute the area and compare them. // // // final double footprintArea = granuleFootprint.getArea(); //final double bboxArea = granuleBBOX.getArea(); final double bboxArea = granuleBBOX.getHeight() * granuleBBOX.getWidth(); // If 2 areas are different more than the cellArea, then they are not the same area if (Math.abs(footprintArea - bboxArea) > cellArea) return true; return false; } }