package it.geosolutions.utils.coveragescaler; import it.geosolutions.utils.progress.ExceptionEvent; import it.geosolutions.utils.progress.ProcessingEvent; import it.geosolutions.utils.progress.ProcessingEventListener; import it.geosolutions.utils.progress.ProgressManager; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.logging.FileHandler; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageReader; import javax.imageio.ImageTypeSpecifier; import javax.imageio.stream.ImageInputStream; import org.apache.commons.cli2.option.DefaultOption; import org.apache.commons.cli2.option.GroupImpl; import org.apache.commons.cli2.util.HelpFormatter; import org.apache.commons.cli2.validation.InvalidArgumentException; import org.apache.commons.cli2.validation.Validator; import org.apache.commons.io.filefilter.WildcardFilter; import org.geotools.coverage.processing.operation.FilteredSubsample; import org.geotools.coverage.processing.operation.Scale; import org.geotools.coverage.processing.operation.SubsampleAverage; import org.geotools.data.coverage.grid.AbstractGridCoverage2DReader; import org.geotools.factory.Hints; import org.geotools.geometry.GeneralEnvelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; /** *

* This classis in charge for creating the index for a mosaic of images that we * want to tie together as a sigle bg coverage. *

* *

* To get instructions on how to run the toll just run it without any arguments * and nice and clean help will be printed to the command line. *

* *

* It is worth to point out that this tool comes as a command line tool but it * has been built with in mind a GUI. It has the capapbility to register * {@link ProcessingEventListener} object that receive notifications about what * is going on. Moreover it delegates all the computations to an external * thread, hence we can stop the tool in the middle of processig with no so many * concerns (hpefully :-) ). *

* *

* Usage:
CoverageScaler -h -v -s -w -p -a -f -c *

* *
 *                                                                                                                        
 *     where:                                                                                                                   
 *      -h : Prints a nice command line Help                                                                                    
 *      -v : Prints the tools Version                                                                                           
 *      -s : Is the path where the raster(s) is(are) located                                                                    
 *      -w : Is the wildcard representing just the file we want to process (e.g. *.tiff)                                        
 *      -p : Is the Thread Priority, a number between 1 and 10 -> 1 [LOW] - 5 [MED] - 10 [HIGH]                              
 *      -a : Represents the Scaling algorithm to use. You can choose among one of the following                                 
 *           nn, bil, avg, filt                                                                                                 
 *      -f : Represents the scale factor. If you want a raster which is 1/2 resolution                                          
 *           of the original, f should be 2                                                                                     
 *      -c : Represents the JAI TileCache dimension. This is an optional parameter which allows                                 
 *           you to tune the tool performances.                                                                                 
 * 
* *

* Example of usage:
* CoverageScaler -s "/usr/home/tmp/tiled" -w *.tiff -a nn -f 2 *

* * @author Simone Giannecchini * @author Alessio Fabiani * @version 0.2 * */ public class CoverageScaler extends ProgressManager implements Runnable, ProcessingEventListener { private final static Hints LENIENT_HINT = new Hints( Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE); private final static FilteredSubsample filteredSubsampleFactory = new FilteredSubsample(); private static final SubsampleAverage subsampleAvgFactory = new SubsampleAverage(); private static final Scale scaleFactory = new Scale(); /** Static immutable ap for scaling algorithms. */ private static List scalingAlgorithms; static { scalingAlgorithms = new ArrayList(4); scalingAlgorithms.add("nn"); scalingAlgorithms.add("bil"); scalingAlgorithms.add("avg"); scalingAlgorithms.add("filt"); } /** Static immutable ap for scaling algorithms. */ private static List outputFormats; static { outputFormats = new ArrayList(6); outputFormats.add("tiff"); outputFormats.add("tif"); outputFormats.add("gtiff"); outputFormats.add("gtif"); outputFormats.add("png"); outputFormats.add("jpeg"); } /** * Number of resolution levels for the coverages. */ private int numberOfLevels; /** * Resolutions levels. */ private double[][] resolutionLevels; /** Number of files to process. */ private int numFiles; /** Default Logger * */ private final static Logger LOG = Logger.getLogger(CoverageScaler.class .toString()); /** Program Version */ private final static String versionNumber = "0.2"; private static final double EPS = 1E-6; private final DefaultOption locationOpt; private String locationPath; private final DefaultOption wildcardOpt; private String wildcardString = "*.*"; /** * Index file name. Default is index. */ private String indexName = "index"; private DefaultOption scaleAlgorithmOpt; private DefaultOption tileCacheSizeOpt; private DefaultOption scaleFactorOpt; private int scaleFactor; private String scaleAlgorithm; /** * Main thread for the mosaic index builder. */ public void run() { // ///////////////////////////////////////////////////////////////////// // // CREATING INDEX FILE // // ///////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////// // // Create a file handler that write log record to a file called // my.log // // ///////////////////////////////////////////////////////////////////// FileHandler handler; try { boolean append = true; handler = new FileHandler(new StringBuffer(locationPath).append( "/error.txt").toString(), append); handler.setLevel(Level.SEVERE); // Add to the desired logger LOG.addHandler(handler); } catch (SecurityException el) { LOG.severe(el.getLocalizedMessage()); } catch (IOException el) { LOG.severe(el.getLocalizedMessage()); } // ///////////////////////////////////////////////////////////////////// // // Creating temp vars // // ///////////////////////////////////////////////////////////////////// /** Fixed local variables * */ AbstractGridCoverage2DReader reader; CoordinateReferenceSystem defaultCRS = null, actualCRS = null; GeneralEnvelope globEnvelope = null; GeneralEnvelope envelope; ImageInputStream inStream; ImageTypeSpecifier its; Iterator it; ImageReader r; double[] res; boolean skipFeature = false; double resX = 0, resY = 0; final File dir = new File(locationPath); final FileFilter fileFilter = new WildcardFilter(wildcardString); final File[] files = dir.listFiles(fileFilter); StringBuffer message; // ///////////////////////////////////////////////////////////////////// // // Cycling over the features // // ///////////////////////////////////////////////////////////////////// numFiles = files.length; for (int i = 0; i < numFiles; i++) { // // // // Anyone has asked us to stop? // // // if (getStopThread()) { message = new StringBuffer("Stopping requested at file ") .append(i).append(" of ").append(numFiles).append( " files"); if (LOG.isLoggable(Level.FINE)) { LOG.fine(message.toString()); } fireEvent(message.toString(), ((i * 100.0) / numFiles) - 1); return; } message = new StringBuffer("Now scaling file ").append(files[i]); if (LOG.isLoggable(Level.FINE)) { LOG.fine(message.toString()); } fireEvent(message.toString(), ((i * 100.0) / numFiles)); } if (numFiles <= 0) { // processing information message = new StringBuffer("No file to process!!!"); if (LOG.isLoggable(Level.FINE)) { LOG.fine(message.toString()); } fireEvent(message.toString(), 100); } } /** * Default constructor */ public CoverageScaler() { // ///////////////////////////////////////////////////////////////////// // Options for the command line // ///////////////////////////////////////////////////////////////////// helpOpt = optionBuilder.withShortName("h").withShortName("?") .withLongName("helpOpt").withDescription("print this message.") .create(); versionOpt = optionBuilder.withShortName("v") .withLongName("versionOpt").withDescription( "print the versionOpt.").create(); locationOpt = optionBuilder.withShortName("s").withLongName( "source_directory").withArgument( arguments.withName("source").withMinimum(1).withMaximum(1) .create()).withDescription( "path where files are located").withRequired(true).create(); wildcardOpt = optionBuilder.withShortName("w").withLongName( "wildcardOpt").withArgument( arguments.withName("wildcardOpt").withMinimum(0).withMaximum(1) .create()).withDescription( "wildcardOpt to use for selecting files").withRequired(false) .create(); scaleFactorOpt = optionBuilder .withShortName("f") .withLongName("scale_factor") .withArgument( arguments.withName("f").withMinimum(1).withMaximum(1) .withValidator(new Validator() { public void validate(List args) throws InvalidArgumentException { final int size = args.size(); if (size > 1) throw new InvalidArgumentException( "Only one scaling algorithm at a time can be chosen"); int factor = Integer .parseInt((String) args.get(0)); if (factor <= 0) throw new InvalidArgumentException( new StringBuffer( "The provided scale factor is negative! ") .toString()); if (factor == 1) { LOG .warning("The scale factor is 1, program will exit!"); System.exit(0); } } }).create()).withDescription( "integer scale factor") .withRequired(true).create(); scaleAlgorithmOpt = optionBuilder .withShortName("a") .withLongName("scaling_algorithm") .withArgument( arguments.withName("a").withMinimum(0).withMaximum(1) .withValidator(new Validator() { public void validate(List args) throws InvalidArgumentException { final int size = args.size(); if (size > 1) throw new InvalidArgumentException( "Only one scaling algorithm at a time can be chosen"); if (!scalingAlgorithms.contains(args .get(0))) throw new InvalidArgumentException( new StringBuffer( "The output format ") .append(args.get(0)) .append( " is not permitted") .toString()); } }).create()) .withDescription( "name of the scaling algorithm, eeither one of average (a), filtered (f), bilinear (bil), nearest neigbhor (nn)") .withRequired(false).create(); priorityOpt = optionBuilder.withShortName("p").withLongName( "thread_priority").withArgument( arguments.withName("priority").withMinimum(0).withMaximum(1) .create()).withDescription( "priority for the underlying thread").withRequired(false) .create(); tileCacheSizeOpt = optionBuilder.withShortName("c").withLongName( "cache_size").withArgument( arguments.withName("c").withMinimum(0).withMaximum(1).create()) .withDescription("tile cache sized").withRequired(false) .create(); cmdOpts.add(helpOpt); cmdOpts.add(versionOpt); cmdOpts.add(locationOpt); cmdOpts.add(wildcardOpt); cmdOpts.add(priorityOpt); cmdOpts.add(scaleAlgorithmOpt); cmdOpts.add(scaleFactorOpt); cmdOpts.add(tileCacheSizeOpt); optionsGroup = new GroupImpl(cmdOpts, "Options", "All the options", 1, 10); // ///////////////////////////////////////////////////////////////////// // // Help Formatter // // ///////////////////////////////////////////////////////////////////// final HelpFormatter cmdHlp = new HelpFormatter("| ", " ", " |", 75); cmdHlp.setShellCommand("CoverageScaler"); cmdHlp.setHeader("Help"); cmdHlp.setFooter(new StringBuffer( "CoverageScaler - GeoSolutions S.a.s (C) 2006 - v ").append( CoverageScaler.versionNumber).toString()); cmdHlp .setDivider("|-------------------------------------------------------------------------|"); cmdParser.setGroup(optionsGroup); cmdParser.setHelpOption(helpOpt); cmdParser.setHelpFormatter(cmdHlp); } /** * Entry point for the index builder. * * @param args */ public static void main(String[] args) { final CoverageScaler coverageScaler = new CoverageScaler(); coverageScaler.addProcessingEventListener(coverageScaler); if (coverageScaler.parseArgs(args)) { final Thread t = new Thread(coverageScaler, "CoverageScaler"); t.setPriority(coverageScaler.priority); t.start(); try { t.join(); } catch (InterruptedException e) { LOG.log(Level.SEVERE, e.getLocalizedMessage(), e); } } else LOG.fine("Exiting..."); } private boolean parseArgs(String[] args) { cmdLine = cmdParser.parseAndHelp(args); if (cmdLine != null && cmdLine.hasOption(versionOpt)) { System.out.print(new StringBuffer( "CoverageScaler - GeoSolutions S.a.s (C) 2006 - v").append( CoverageScaler.versionNumber).toString()); System.exit(1); } else if (cmdLine != null) { // //////////////////////////////////////////////////////////////// // // parsing command line parameters and setting up // Mosaic Index Builder options // // //////////////////////////////////////////////////////////////// locationPath = (String) cmdLine.getValue(locationOpt); final File inDir = new File(locationPath); if (!inDir.isDirectory()) { LOG .severe("Provided input dir does not exist or is not a dir!"); return false; } // wildcard if (cmdLine.hasOption(wildcardOpt)) wildcardString = (String) cmdLine.getValue(wildcardOpt); // // // // scale factor // // // final String scaleF = (String) cmdLine.getValue(scaleFactorOpt); scaleFactor = Integer.parseInt(scaleF); // // // // scaling algorithm // // // scaleAlgorithm = (String) cmdLine.getValue(scaleAlgorithmOpt); // // // // Thread priority // // // // index name if (cmdLine.hasOption(priorityOpt)) priority = Integer.parseInt((String) cmdLine .getValue(priorityOpt)); return true; } return false; } /** * This method is repsonbile for sending the process progress events to the * logger. * *

* It should be used to do normal logging when running this tools as command * line tools but it should be disable when putting the tool behind a GUI. * In such a case the GUI should register itself as a * {@link ProcessingEventListener} and consume the processing events. * * @param event * is a {@link ProcessingEvent} that informs the receiver on the * precetnage of the progress as well as on what is happening. */ public void getNotification(ProcessingEvent event) { LOG.info(new StringBuffer("Progress is at ").append( event.getPercentage()).append("\n").append( "attached message is: ").append(event.getMessage()).toString()); } public void exceptionOccurred(ExceptionEvent event) { LOG.log(Level.SEVERE, "An error occurred during processing", event .getException()); } }