/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 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.utils.imagemosaic;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.SampleModel;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.stream.ImageInputStream;
import org.apache.commons.cli2.Option;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.coverage.grid.io.GridFormatFinder;
import org.geotools.data.DataSourceException;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.FeatureWriter;
import org.geotools.data.Transaction;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.resources.CRSUtilities;
import org.geotools.utils.progress.BaseArgumentsManager;
import org.geotools.utils.progress.ExceptionEvent;
import org.geotools.utils.progress.ProcessingEvent;
import org.geotools.utils.progress.ProcessingEventListener;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.PrecisionModel;
/**
* This class is in responsible for creating the index for a mosaic of images
* that we want to tie together as a single coverage.
*
* <p>
* To get instructions on how to run the tool just run it without any argument
* and a nice and clean help message will be printed to the command line.
*
* <p>
* Anyway an example of a suitable list of argumentBuilder can be seen here
* below:
*
* <p>
* -s H:\\work\\data\\merano_aime -w *.tif -name merano -abs
* <p>
* where:
* <ol>
* <li>-s H:\\work\\data\\merano_aime is the source directory</li>
* <li>-w *.tif is he wildcard for the files to process</li>
* <li>-name merano sets the name for the output shape</li>
* <li>-abs asks the tool to use absolute paths instead of relative</li>
* </ol>
*
*
* <p>
* It is worth to point out that this tool comes as a command line tool but it
* has been built with GUI in mind . It has the capability 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 processing with no so
* many concerns (hopefully :-) ).
* <p>
*
*
* @author Simone Giannecchini, GeoSolutions
* @author Alessio Fabiani, GeoSolutions
* @author Blaz Repnik
*
* @source $URL$
* @version 0.3
*
*/
public class MosaicIndexBuilder extends BaseArgumentsManager implements
Runnable, ProcessingEventListener {
/** Default Logger * */
private final static Logger LOGGER = org.geotools.util.logging.Logging
.getLogger("it.geosolutions.utils.imagemosaic");
/** Program Version */
private final static String VERSION = "0.3";
private final static String NAME = "MosaicIndexBuilder";
private final Option locationOpt;
private final Option nameOpt;
private final Option relativePathOpt;
private final Option wildcardOpt;
private String locationPath;
/**
* Number of resolution levels for the coverages.
*/
private int numberOfLevels;
/**
* Resolutions levels.
*/
private double[][] resolutionLevels;
/** Number of files to process. */
private int numFiles;
private String wildcardString = "*.*";
/**
* Index file name. Default is index.
*/
private String indexName = "index";
/**
* This field will tell the plugin if it must do a conversion of color from
* the original index color model to an RGB color model. This happens f the
* original images uses different color maps between each other making for
* us impossible to reuse it for the mosaic.
*/
private boolean mustConvertToRGB = false;
private ColorModel actualCM = null;
private ColorModel defaultCM = null;
private SampleModel defaultSM = null;
private SampleModel actualSM = null;
private GeneralEnvelope globEnvelope = null;
private GeneralEnvelope envelope = null;
private byte[][] defaultPalette = null;
private CoordinateReferenceSystem defaultCRS = null;
private CoordinateReferenceSystem actualCRS = null;
private boolean absolute = false;
/**
* Recurses the directory tree and returns valid files.
*/
private void recurse(List<File> allFiles, String locationPath) {
final File dir = new File(locationPath);
final FileFilter fileFilter = new WildcardFileFilter(wildcardString);
final File[] files = dir.listFiles(fileFilter);
final File[] dirs = dir
.listFiles((FileFilter) DirectoryFileFilter.INSTANCE);
for (int i = 0; i < files.length; i++) {
allFiles.add(files[i]);
}
for (int i = 0; i < dirs.length; i++) {
recurse(allFiles, new StringBuilder(locationPath).append('/')
.append(dirs[i].getName()).toString());
}
}
/**
* 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=null;
try {
boolean append = true;
handler = new FileHandler(new StringBuffer(locationPath).append(
"/error.txt").toString(), append);
handler.setLevel(Level.SEVERE);
// Add to the desired logger
LOGGER.addHandler(handler);
// /////////////////////////////////////////////////////////////////////
//
// Create a set of file names that have to be skipped since these are
// our metadata files
//
// /////////////////////////////////////////////////////////////////////
final Set<String> skipFiles = new HashSet<String>(Arrays
.asList(new String[] { indexName + ".shp", indexName + ".dbf",
indexName + ".shx", indexName + ".prj", "error.txt",
"error.txt.lck", indexName + ".properties" }));
// /////////////////////////////////////////////////////////////////////
//
// Creating temp vars
//
// /////////////////////////////////////////////////////////////////////
ShapefileDataStore index = null;
Transaction t = new DefaultTransaction();
// declaring a preciosion model to adhere the java double type
// precision
PrecisionModel precMod = new PrecisionModel(PrecisionModel.FLOATING);
GeometryFactory geomFactory = new GeometryFactory(precMod);
try {
index = new ShapefileDataStore(new File(locationPath + File.separator
+ indexName + ".shp").toURI().toURL());
} catch (MalformedURLException ex) {
if (LOGGER.isLoggable(Level.SEVERE))
LOGGER.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
fireException(ex);
return;
}
final List<File> files = new ArrayList<File>();
recurse(files, locationPath);
// /////////////////////////////////////////////////////////////////////
//
// Cycling over the files that have filtered out
//
// /////////////////////////////////////////////////////////////////////
numFiles = files.size();
String validFileName = null;
final Iterator<File> filesIt = files.iterator();
FeatureWriter<SimpleFeatureType, SimpleFeature> fw = null;
boolean doneSomething = false;
for (int i = 0; i < numFiles; i++) {
StringBuffer message;
// //
//
// Check that this file is actually good to go
//
// //
final File fileBeingProcessed = ((File) filesIt.next());
if(!fileBeingProcessed.exists()||!fileBeingProcessed.canRead()||!fileBeingProcessed.isFile())
{
// send a message
message = new StringBuffer("Skipped file ").append(
files.get(i)).append(
" snce it seems invalid.");
if (LOGGER.isLoggable(Level.INFO))
LOGGER.info(message.toString());
fireEvent(message.toString(), ((i * 99.0) / numFiles));
continue;
}
// //
//
// Anyone has asked us to stop?
//
// //
if (getStopThread()) {
message = new StringBuffer("Stopping requested at file ")
.append(i).append(" of ").append(numFiles).append(
" files");
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
fireEvent(message.toString(), ((i * 100.0) / numFiles));
return;
} // replacing chars on input path
try {
validFileName = fileBeingProcessed.getCanonicalPath();
} catch (IOException e1) {
fireException(e1);
return;
}
validFileName = validFileName.replace('\\', '/');
validFileName = validFileName.substring(locationPath.length() + 1,
fileBeingProcessed.getAbsolutePath().length());
if (skipFiles.contains(validFileName))
continue;
message = new StringBuffer("Now indexing file ")
.append(validFileName);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
fireEvent(message.toString(), ((i * 100.0) / numFiles));
try {
// ////////////////////////////////////////////////////////
//
//
// STEP 1
// Getting an ImageIO reader for this coverage.
//
//
// ////////////////////////////////////////////////////////
ImageInputStream inStream = ImageIO
.createImageInputStream(fileBeingProcessed);
if(inStream==null)
{
if(LOGGER.isLoggable(Level.SEVERE))
LOGGER.severe(fileBeingProcessed+" has been skipped since we could not get a stream for it");
continue;
}
inStream.mark();
final Iterator<ImageReader> it = ImageIO.getImageReaders(inStream);
ImageReader r = null;
if (it.hasNext()) {
r = (ImageReader) it.next();
r.setInput(inStream);
} else {
// release resources
try {
inStream.close();
} catch (Exception e) {
// ignore exception
}
// try {
// r.dispose();
// } catch (Exception e) {
// // ignore exception
// }
// send a message
message = new StringBuffer("Skipped file ").append(
files.get(i)).append(
":No ImageIO readeres avalaible.");
if (LOGGER.isLoggable(Level.INFO))
LOGGER.info(message.toString());
fireEvent(message.toString(), ((i * 99.0) / numFiles));
continue;
}
// ////////////////////////////////////////////////////////
//
// STEP 2
// Getting a coverage reader for this coverage.
//
// ////////////////////////////////////////////////////////
if (LOGGER.isLoggable(Level.FINE))
LOGGER
.fine(new StringBuffer("Getting a reader")
.toString());
final AbstractGridFormat format = (AbstractGridFormat) GridFormatFinder
.findFormat(files.get(i));
if (format == null||!format.accepts(files.get(i))) {
// release resources
try {
inStream.close();
} catch (Exception e) {
// ignore exception
}
try {
r.dispose();
} catch (Exception e) {
// ignore exception
}
message = new StringBuffer("Skipped file ").append(
files.get(i)).append(
": File format is not supported.");
if (LOGGER.isLoggable(Level.INFO))
LOGGER.info(message.toString());
fireEvent(message.toString(), ((i * 99.0) / numFiles));
continue;
}
final AbstractGridCoverage2DReader reader = (AbstractGridCoverage2DReader) format
.getReader(files.get(i));
envelope = (GeneralEnvelope) reader.getOriginalEnvelope();
actualCRS = reader.getCrs();
// /////////////////////////////////////////////////////////////////////
//
// STEP 3
// Get the type specifier for this image and the check that the
// image has the correct sample model and color model.
// If this is the first cycle of the loop we initialize
// eveything.
//
// /////////////////////////////////////////////////////////////////////
final ImageTypeSpecifier its = ((ImageTypeSpecifier) r.getImageTypes(0).next());
boolean skipFeature = false;
if (globEnvelope == null) {
// /////////////////////////////////////////////////////////////////////
//
// at the first step we initialize everything that we will
// reuse afterwards starting with color models, sample
// models, crs, etc....
//
// /////////////////////////////////////////////////////////////////////
defaultCM = its.getColorModel();
if (defaultCM instanceof IndexColorModel) {
IndexColorModel icm = (IndexColorModel) defaultCM;
int numBands = defaultCM.getNumColorComponents();
defaultPalette = new byte[3][icm.getMapSize()];
icm.getReds(defaultPalette[0]);
icm.getGreens(defaultPalette[0]);
icm.getBlues(defaultPalette[0]);
if (numBands == 4)
icm.getAlphas(defaultPalette[0]);
}
defaultSM = its.getSampleModel();
defaultCRS = actualCRS;
globEnvelope = new GeneralEnvelope(envelope);
// /////////////////////////////////////////////////////////////////////
//
// getting information about resolution
//
// /////////////////////////////////////////////////////////////////////
// //
//
// get the dimension of the hr image and build the model
// as well as
// computing the resolution
// //
// resetting reader and recreating stream, turnaround for a
// strange imageio bug
r.reset();
try{
inStream.reset();
}catch (IOException e) {
inStream= ImageIO.createImageInputStream(fileBeingProcessed);
}
//let's check if we got something now
if(inStream==null)
{
//skip file
if(LOGGER.isLoggable(Level.WARNING))
LOGGER.warning("Skipping file "+fileBeingProcessed.toString());
continue;
}
r.setInput(inStream);
numberOfLevels = r.getNumImages(true);
resolutionLevels = new double[2][numberOfLevels];
double[] res = getResolution(envelope, new Rectangle(r
.getWidth(0), r.getHeight(0)), defaultCRS);
resolutionLevels[0][0] = res[0];
resolutionLevels[1][0] = res[1];
// resolutions levels
if (numberOfLevels > 1) {
for (int k = 0; k < numberOfLevels; k++) {
res = getResolution(envelope, new Rectangle(r
.getWidth(k), r.getHeight(k)), defaultCRS);
resolutionLevels[0][k] = res[0];
resolutionLevels[1][k] = res[1];
}
}
// /////////////////////////////////////////////////////////////////////
//
// creating the schema
//
// /////////////////////////////////////////////////////////////////////
final SimpleFeatureTypeBuilder featureBuilder = new SimpleFeatureTypeBuilder();
featureBuilder.setName("Flag");
featureBuilder.setNamespaceURI("http://www.geo-solutions.it/");
featureBuilder.add("location", String.class);
featureBuilder.add("the_geom", Polygon.class,this.actualCRS);
featureBuilder.setDefaultGeometry("the_geom");
final SimpleFeatureType simpleFeatureType = featureBuilder.buildFeatureType();
// create the schema for the new shape file
index.createSchema(simpleFeatureType);
// get a feature writer
fw = index.getFeatureWriter( t);
} else {
// ////////////////////////////////////////////////////////
//
// comparing ColorModel
// comparing SampeModel
// comparing CRSs
// ////////////////////////////////////////////////////////
globEnvelope.add(envelope);
actualCM = its.getColorModel();
actualSM = its.getSampleModel();
skipFeature = (i > 0 ? !(CRS.equalsIgnoreMetadata(
defaultCRS, actualCRS)) : false);
if (skipFeature)
LOGGER.warning(new StringBuffer("Skipping image ")
.append(files.get(i)).append(
" because CRSs do not match.")
.toString());
skipFeature = checkColorModels(defaultCM, defaultPalette,
actualCM);
if (skipFeature)
LOGGER.warning(new StringBuffer("Skipping image ")
.append(files.get(i)).append(
" because color models do not match.")
.toString());
// defaultCM.getNumComponents()==actualCM.getNumComponents()&&
// defaultCM.getClass().equals(actualCM.getClass())
// && defaultSM.getNumBands() == actualSM
// .getNumBands()
// && defaultSM.getDataType() == actualSM
// .getDataType() &&
//
// if (skipFeature)
// LOGGER
// .warning(new StringBuffer("Skipping image ")
// .append(files.get(i))
// .append(
// " because cm or sm does not match.")
// .toString());
// res = getResolution(envelope, new
// Rectangle(r.getWidth(0),
// r.getHeight(0)), defaultCRS);
// if (Math.abs((resX - res[0]) / resX) > EPS
// || Math.abs(resY - res[1]) > EPS) {
// LOGGER.warning(new StringBuffer("Skipping image
// ").append(
// files.get(i)).append(
// " because resolutions does not match.")
// .toString());
// skipFeature = true;
// }
}
// ////////////////////////////////////////////////////////
//
// STEP 4
//
// create and store features
//
// ////////////////////////////////////////////////////////
if (!skipFeature) {
final SimpleFeature feature = fw.next();
feature.setAttribute(1, geomFactory
.toGeometry(new ReferencedEnvelope(
(Envelope) envelope)));
feature.setAttribute(0, absolute ? new StringBuilder(
this.locationPath).append(File.separatorChar)
.append(validFileName).toString() : validFileName);
fw.write();
message = new StringBuffer("Done with file ").append(files
.get(i));
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
message.append('\n');
fireEvent(message.toString(), (((i + 1) * 99.0) / numFiles));
doneSomething = true;
} else
skipFeature = false;
// ////////////////////////////////////////////////////////
//
// STEP 5
//
// release resources
//
// ////////////////////////////////////////////////////////
try {
inStream.close();
} catch (Exception e) {
// ignore exception
}
try {
r.dispose();
} catch (Exception e) {
// ignore exception
}
// release resources
reader.dispose();
} catch (IOException e) {
fireException(e);
break;
} catch (ArrayIndexOutOfBoundsException e) {
fireException(e);
break;
}
}
try {
if (fw != null)
fw.close();
t.commit();
t.close();
index.dispose();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
}
createPropertiesFiles(globEnvelope, doneSomething);
} catch (SecurityException el) {
fireException(el);
return;
} catch (IOException el) {
fireException(el);
return;
}
finally{
try{
if(handler!=null)
handler.close();
}catch (Throwable e) {
// ignore
}
}
}
/**
* @param globEnvelope
* @param doneSomething
*/
private void createPropertiesFiles(GeneralEnvelope globEnvelope,
boolean doneSomething) {
StringBuffer message;
if (numFiles > 0 && doneSomething) {
// /////////////////////////////////////////////////////////////////////
//
// FINAL STEP
//
// CREATING GENERAL INFO FILE
//
// /////////////////////////////////////////////////////////////////////
message = new StringBuffer("Creating final properties file ");
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
fireEvent(message.toString(), 99.9);
// envelope
final Properties properties = new Properties();
properties.setProperty("AbsolutePath", Boolean.toString(absolute));
properties.setProperty("NumFiles", Integer.toString(numFiles));
properties.setProperty("Envelope2D", new StringBuffer(Double
.toString(globEnvelope.getMinimum(0))).append(",").append(
Double.toString(globEnvelope.getMinimum(1))).append(" ")
.append(Double.toString(globEnvelope.getMaximum(0)))
.append(",").append(
Double.toString(globEnvelope.getMaximum(1)))
.toString());
properties.setProperty("LevelsNum", Integer
.toString(numberOfLevels));
final StringBuffer levels = new StringBuffer();
for (int k = 0; k < numberOfLevels; k++) {
levels.append(Double.toString(resolutionLevels[0][k])).append(
",").append(Double.toString(resolutionLevels[1][k]));
if (k < numberOfLevels - 1)
levels.append(" ");
}
properties.setProperty("Levels", levels.toString());
properties.setProperty("Name", indexName);
properties.setProperty("ExpandToRGB", Boolean
.toString(mustConvertToRGB));
OutputStream outStream=null;
try {
outStream=new BufferedOutputStream(new FileOutputStream(locationPath + "/" + indexName + ".properties"));
properties.store(outStream, "");
} catch (FileNotFoundException e) {
fireEvent(e.getLocalizedMessage(), 0);
} catch (IOException e) {
fireEvent(e.getLocalizedMessage(), 0);
}
finally{
try{
if(outStream!=null)
outStream.close();
}catch (Throwable e) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
}
}
// processing information
message = new StringBuffer("Done!!!");
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
fireEvent(message.toString(), 100);
} else {
// processing information
message = new StringBuffer("No file to process!!!");
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(message.toString());
}
fireEvent(message.toString(), 100);
}
}
/**
* This method checks the {@link ColorModel} of the current image with the
* one of the first image in order to check if they are compatible or not in
* order to perform a mosaic operation.
*
* <p>
* It is worth to point out that we also check if, in case we have two index
* color model image, we also try to suggest whether or not we should do a
* color expansion.
*
* @param defaultCM
* @param defaultPalette
* @param actualCM
* @return a boolean asking to skip this feature.
*/
private boolean checkColorModels(ColorModel defaultCM,
byte[][] defaultPalette, ColorModel actualCM) {
// /////////////////////////////////////////////////////////////////////
//
//
// ComponentColorModel
//
//
// /////////////////////////////////////////////////////////////////////
if (defaultCM instanceof ComponentColorModel
&& actualCM instanceof ComponentColorModel) {
final ComponentColorModel defCCM = (ComponentColorModel) defaultCM, actualCCM = (ComponentColorModel) actualCM;
return !(defCCM.getNumColorComponents() == actualCCM
.getNumColorComponents()
&& defCCM.hasAlpha() == actualCCM.hasAlpha()
&& defCCM.getColorSpace().equals(actualCCM.getColorSpace())
&& defCCM.getTransparency() == actualCCM.getTransparency() && defCCM
.getTransferType() == actualCCM.getTransferType());
}
// /////////////////////////////////////////////////////////////////////
//
//
// IndexColorModel
//
//
// /////////////////////////////////////////////////////////////////////
if (defaultCM instanceof IndexColorModel
&& actualCM instanceof IndexColorModel) {
final IndexColorModel defICM = (IndexColorModel) defaultCM, actualICM = (IndexColorModel) actualCM;
if (defICM.getNumColorComponents() != actualICM
.getNumColorComponents()
|| defICM.hasAlpha() != actualICM.hasAlpha()
|| !defICM.getColorSpace()
.equals(actualICM.getColorSpace())
|| defICM.getTransferType() != actualICM.getTransferType())
return true;
// ///
//
// Suggesting expansion in the simplest case
//
// ///
if (defICM.getMapSize() != actualICM.getMapSize()
|| defICM.getTransparency() != actualICM.getTransparency()
|| defICM.getTransferType() != actualICM.getTransferType()
|| defICM.getTransparentPixel() != actualICM
.getTransparentPixel()) {
mustConvertToRGB = true;
return false;
}
// //
//
// Now checking palettes to see if we need to do a color convert
//
// //
// get the palette for this color model
int numBands = actualICM.getNumColorComponents();
byte[][] actualPalette = new byte[3][actualICM.getMapSize()];
actualICM.getReds(actualPalette[0]);
actualICM.getGreens(actualPalette[0]);
actualICM.getBlues(actualPalette[0]);
if (numBands == 4)
actualICM.getAlphas(defaultPalette[0]);
// compare them
for (int i = 0; i < defICM.getMapSize(); i++)
for (int j = 0; j < numBands; j++)
if (actualPalette[j][i] != defaultPalette[j][i]) {
mustConvertToRGB = true;
break;
}
return false;
}
// //
//
// if we get here this means that the two color models where completely
// different, hence skip this feature.
//
// //
return true;
}
/**
* Default constructor
*/
public MosaicIndexBuilder() {
super(NAME, VERSION);
// /////////////////////////////////////////////////////////////////////
// Options for the command line
// /////////////////////////////////////////////////////////////////////
locationOpt = optionBuilder.withShortName("s").withLongName(
"source_directory").withArgument(
argumentBuilder.withName("source").withMinimum(1)
.withMaximum(1).create()).withDescription(
"path where files are located").withRequired(true).create();
wildcardOpt = optionBuilder.withShortName("w").withLongName(
"wildcardOpt").withArgument(
argumentBuilder.withName("wildcardOpt").withMinimum(0)
.withMaximum(1).create()).withDescription(
"wildcardOpt to use for selecting files").withRequired(false)
.create();
nameOpt = optionBuilder.withShortName("name")
.withLongName("index_name").withArgument(
argumentBuilder.withName("name").withMinimum(0)
.withMaximum(1).create()).withDescription(
"name for the index file").withRequired(false).create();
relativePathOpt = optionBuilder.withShortName("abs").withLongName(
"absolute_path").withDescription(
"whether or not paths shuld be absolute").withRequired(false)
.create();
addOption(locationOpt);
addOption(wildcardOpt);
addOption(nameOpt);
addOption(relativePathOpt);
// /////////////////////////////////////////////////////////////////////
//
// Help Formatter
//
// /////////////////////////////////////////////////////////////////////
finishInitialization();
}
/**
* Entry point for the index builder.
*
* @param args
*/
public static void main(String[] args) {
final MosaicIndexBuilder mosaicIndexBuilder = new MosaicIndexBuilder();
mosaicIndexBuilder.addProcessingEventListener(mosaicIndexBuilder);
if (mosaicIndexBuilder.parseArgs(args)) {
final Thread t = new Thread(mosaicIndexBuilder,
"MosaicIndexBuilder");
t.setPriority(mosaicIndexBuilder.getPriority());
t.start();
try {
t.join();
} catch (InterruptedException e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
}
} else
LOGGER.fine("Exiting...");
}
public boolean parseArgs(String[] args) {
if (!super.parseArgs(args))
return false;
// ////////////////////////////////////////////////////////////////
//
// parsing command line parameters and setting up
// Mosaic Index Builder options
//
// ////////////////////////////////////////////////////////////////
locationPath = (String) getOptionValue(locationOpt);
final File inDir = new File(locationPath);
if (!inDir.isDirectory()) {
LOGGER.severe("Provided input dir does not exist or is not a dir!");
return false;
}
try {
locationPath = inDir.getCanonicalPath();
locationPath = locationPath.replace('\\', '/');
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
return false;
}
// wildcard
if (hasOption(wildcardOpt))
wildcardString = (String) getOptionValue(wildcardOpt);
// index name
if (hasOption(nameOpt))
indexName = (String) getOptionValue(nameOpt);
// //
//
// Type of path
//
// //
if (hasOption(relativePathOpt))
absolute = true;
return true;
}
/**
* This method is responsible for computing the resolutions in for the
* provided grid geometry in the provided crs.
*
* <P>
* It is worth to note that the returned resolution array is of length of 2
* and it always is lon, lat for the moment.<br>
* It might be worth to remove the axes reordering code when we are
* confident enough with the code to handle the north-up crs.
* <p>
* TODO use orthodromic distance?
*
* @param envelope
* the GeneralEnvelope
* @param dim
* @param crs
* @throws DataSourceException
*/
protected final double[] getResolution(GeneralEnvelope envelope,
Rectangle2D dim, CoordinateReferenceSystem crs)
throws DataSourceException {
double[] requestedRes = null;
try {
if (dim != null && envelope != null) {
// do we need to transform the originalEnvelope?
final CoordinateReferenceSystem crs2D = CRSUtilities
.getCRS2D(envelope.getCoordinateReferenceSystem());
if (crs != null && !CRS.equalsIgnoreMetadata(crs, crs2D)) {
final MathTransform tr = CRS.findMathTransform(crs2D, crs);
if (!tr.isIdentity())
envelope = CRS.transform(tr, envelope);
}
requestedRes = new double[2];
requestedRes[0] = envelope.getLength(0) / dim.getWidth();
requestedRes[1] = envelope.getLength(1) / dim.getHeight();
}
return requestedRes;
} catch (TransformException e) {
throw new DataSourceException("Unable to get the resolution", e);
} catch (FactoryException e) {
throw new DataSourceException("Unable to get the resolution", e);
}
}
/**
* This method is repsonbile for sending the process progress events to the
* logger.
*
* <p>
* 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) {
LOGGER.info(new StringBuffer("Progress is at ").append(
event.getPercentage()).append("\n").append(
"attached message is: ").append(event.getMessage()).toString());
}
public void exceptionOccurred(ExceptionEvent event) {
LOGGER.log(Level.SEVERE, "An error occurred during processing", event
.getException());
}
/**
* @param locationPath
* the locationPath to set
*/
public final void setLocationPath(String locationPath) {
this.locationPath = locationPath;
final File inDir = new File(locationPath);
if (!inDir.isDirectory()) {
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 {
locationPath = inDir.getCanonicalPath();
locationPath = locationPath.replace('\\', '/');
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
final IllegalArgumentException ex = new IllegalArgumentException();
ex.initCause(e);
throw ex;
}
}
/**
* @param wildcardString
* the wildcardString to set
*/
public final void setWildcardString(String wildcardString) {
this.wildcardString = wildcardString;
}
public String getIndexName() {
return indexName;
}
public void setIndexName(String indexName) {
this.indexName = indexName;
}
public double getResolutionX() {
return this.resolutionLevels[0][0];
}
public double getResolutionY() {
return this.resolutionLevels[1][0];
}
}