001: /**
002: *
003: */package it.geosolutions.utils.coveragetiler;
004:
005: import it.geosolutions.utils.progress.ExceptionEvent;
006: import it.geosolutions.utils.progress.ProcessingEvent;
007: import it.geosolutions.utils.progress.ProcessingEventListener;
008: import it.geosolutions.utils.progress.ProgressManager;
009:
010: import java.awt.Rectangle;
011: import java.awt.geom.Rectangle2D;
012: import java.io.File;
013: import java.io.IOException;
014: import java.net.MalformedURLException;
015: import java.util.logging.Level;
016: import java.util.logging.Logger;
017:
018: import javax.media.jai.Interpolation;
019:
020: import org.apache.commons.cli2.builder.DefaultOptionBuilder;
021: import org.apache.commons.cli2.option.DefaultOption;
022: import org.apache.commons.cli2.option.GroupImpl;
023: import org.apache.commons.cli2.util.HelpFormatter;
024: import org.geotools.coverage.grid.GeneralGridRange;
025: import org.geotools.coverage.grid.GridCoverage2D;
026: import org.geotools.coverage.grid.GridGeometry2D;
027: import org.geotools.coverage.processing.AbstractProcessor;
028: import org.geotools.coverage.processing.DefaultProcessor;
029: import org.geotools.data.coverage.grid.AbstractGridCoverage2DReader;
030: import org.geotools.data.coverage.grid.AbstractGridFormat;
031: import org.geotools.data.coverage.grid.GridFormatFinder;
032: import org.geotools.data.coverage.grid.UnknownFormat;
033: import org.geotools.gce.geotiff.GeoTiffWriter;
034: import org.geotools.gce.imagemosaic.ImageMosaicFormat;
035: import org.geotools.geometry.GeneralEnvelope;
036: import org.opengis.parameter.GeneralParameterValue;
037: import org.opengis.parameter.ParameterValue;
038: import org.opengis.parameter.ParameterValueGroup;
039:
040: /**
041: * <p>
042: * This utility splits rasters into smaller pieces. Having a raster tilized into
043: * pieces, and using them on a mosaic, fo instance, means big performance
044: * improvements.
045: * </p>
046: *
047: * <p>
048: * Example of usage:<br/>
049: * <code>CoverageTiler -t "35,35" -s "/usr/home/tmp/myImage.tiff"</code>
050: * </p>
051: *
052: * <p>
053: * The tiles will be stored on the folder <code>"/usr/home/tmp/tiled"</code>,
054: * which will be automatically created.
055: * </p>
056: *
057: * <pre>
058: *
059: * HINT: set the tile dimensions in order to obtain smaller pieces which size is between 500Kb and 2Mb
060: * The size of the pieces depends on the raster resolution and the Envelope.
061: * Use the CoverageScaler to change your raster resolution.
062: * If you don't know these parameters, however, try first with small values like 20,20 or 40,40
063: * and calibrate then the tile dimension in order to obtain the desired size.
064: * </pre>
065: *
066: * @author Simone Giannecchini
067: * @author Alessio Fabiani
068: * @version 0.2
069: *
070: */
071: public class CoverageTiler extends ProgressManager implements
072: ProcessingEventListener, Runnable {
073: /** Default Logger * */
074: private final static Logger LOGGER = org.geotools.util.logging.Logging
075: .getLogger(CoverageTiler.class.toString());
076:
077: /** Program Version */
078: private final static String versionNumber = "0.2";
079:
080: protected final DefaultOptionBuilder optionBuilder = new DefaultOptionBuilder();
081:
082: private DefaultOption helpOpt;
083:
084: private DefaultOption versionOpt;
085:
086: private DefaultOption inputLocationOpt;
087:
088: private DefaultOption outputLocationOpt;
089:
090: private File inputLocation;
091:
092: private File outputLocation;
093:
094: private DefaultOption tileDimOpt;
095:
096: private int numTileX;
097:
098: private int numTileY;
099:
100: /**
101: * Default constructor
102: */
103: public CoverageTiler() {
104: // /////////////////////////////////////////////////////////////////////
105: // Options for the command line
106: // /////////////////////////////////////////////////////////////////////
107: helpOpt = optionBuilder.withShortName("h").withShortName("?")
108: .withLongName("helpOpt").withDescription(
109: "print this message.").create();
110: versionOpt = optionBuilder.withShortName("v").withLongName(
111: "versionOpt").withDescription("print the versionOpt.")
112: .create();
113: inputLocationOpt = optionBuilder.withShortName("s")
114: .withLongName("src_coverage").withArgument(
115: arguments.withName("source").withMinimum(1)
116: .withMaximum(1).create())
117: .withDescription(
118: "path where the source code is located")
119: .withRequired(true).create();
120: outputLocationOpt = optionBuilder
121: .withShortName("d")
122: .withLongName("dest_directory")
123: .withArgument(
124: arguments.withName("destination")
125: .withMinimum(0).withMaximum(1).create())
126: .withDescription(
127: "output directory, if none is provided, the \"tiled\" directory will be used")
128: .withRequired(false).create();
129: tileDimOpt = optionBuilder
130: .withShortName("t")
131: .withLongName("tiled_dimension")
132: .withArgument(
133: arguments.withName("t").withMinimum(1)
134: .withMaximum(1).create())
135: .withDescription(
136: "number or rows and columns used to split the image as a couple rows,cols")
137: .withRequired(true).create();
138:
139: priorityOpt = optionBuilder.withShortName("p").withLongName(
140: "thread_priority").withArgument(
141: arguments.withName("priority").withMinimum(0)
142: .withMaximum(1).create()).withDescription(
143: "priority for the underlying thread").withRequired(
144: false).create();
145:
146: cmdOpts.add(helpOpt);
147: cmdOpts.add(tileDimOpt);
148: cmdOpts.add(versionOpt);
149: cmdOpts.add(inputLocationOpt);
150: cmdOpts.add(outputLocationOpt);
151: cmdOpts.add(priorityOpt);
152:
153: optionsGroup = new GroupImpl(cmdOpts, "Options",
154: "All the options", 1, 10);
155:
156: // /////////////////////////////////////////////////////////////////////
157: //
158: // Help Formatter
159: //
160: // /////////////////////////////////////////////////////////////////////
161: final HelpFormatter cmdHlp = new HelpFormatter("| ", " ",
162: " |", 75);
163: cmdHlp.setShellCommand("CoverageTiler");
164: cmdHlp.setHeader("Help");
165: cmdHlp.setFooter(new StringBuffer(
166: "CoverageTiler - GeoSolutions S.a.s (C) 2006 - v ")
167: .append(CoverageTiler.versionNumber).toString());
168: cmdHlp
169: .setDivider("|-------------------------------------------------------------------------|");
170:
171: cmdParser.setGroup(optionsGroup);
172: cmdParser.setHelpOption(helpOpt);
173: cmdParser.setHelpFormatter(cmdHlp);
174: }
175:
176: /**
177: * @param args
178: * @throws MalformedURLException
179: * @throws InterruptedException
180: */
181: public static void main(String[] args)
182: throws MalformedURLException, InterruptedException {
183: final CoverageTiler coverageTiler = new CoverageTiler();
184: coverageTiler.addProcessingEventListener(coverageTiler);
185: if (coverageTiler.parseArgs(args)) {
186: final Thread t = new Thread(coverageTiler,
187: "MosaicIndexBuilder");
188: t.setPriority(coverageTiler.priority);
189: t.start();
190: try {
191: t.join();
192: } catch (InterruptedException e) {
193: LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
194: }
195:
196: } else
197: LOGGER.fine("Exiting...");
198: }
199:
200: /**
201: * This method is responsible for sending the process progress events to the
202: * logger.
203: *
204: * <p>
205: * It should be used to do normal logging when running this tools as command
206: * line tools but it should be disable when putting the tool behind a GUI.
207: * In such a case the GUI should register itself as a
208: * {@link ProcessingEventListener} and consume the processing events.
209: *
210: * @param event
211: * is a {@link ProcessingEvent} that informs the receiver on the
212: * precetnage of the progress as well as on what is happening.
213: */
214: public void getNotification(ProcessingEvent event) {
215: LOGGER.info(new StringBuffer("Progress is at ").append(
216: event.getPercentage()).append("\n").append(
217: "attached message is: ").append(event.getMessage())
218: .toString());
219:
220: }
221:
222: public void exceptionOccurred(ExceptionEvent event) {
223: LOGGER.log(Level.SEVERE, "An error occurred during processing",
224: event.getException());
225: }
226:
227: public void run() {
228:
229: // /////////////////////////////////////////////////////////////////////
230: //
231: //
232: // PARSING INPUT PARAMETERS
233: //
234: //
235: // /////////////////////////////////////////////////////////////////////
236:
237: // /////////////////////////////////////////////////////////////////////
238: //
239: //
240: // Opening the base mosaic
241: //
242: //
243: // /////////////////////////////////////////////////////////////////////
244: // mosaic reader
245: StringBuffer message = new StringBuffer(
246: "Acquiring a mosaic reader to mosaic ")
247: .append(inputLocation);
248: if (LOGGER.isLoggable(Level.FINE))
249: LOGGER.fine(message.toString());
250:
251: AbstractGridFormat format = (AbstractGridFormat) GridFormatFinder
252: .findFormat(inputLocation);
253: if (format == null || format instanceof UnknownFormat) {
254: fireException(
255: "Unable to decide format for this coverage",
256: 0,
257: new IOException(
258: "Could not find a format for this coverage"));
259: return;
260: }
261: AbstractGridCoverage2DReader inReader = (AbstractGridCoverage2DReader) format
262: .getReader(inputLocation);
263: if (inReader == null) {
264: message = new StringBuffer(
265: "Unable to instantiate a reader for this coverage");
266: if (LOGGER.isLoggable(Level.WARNING))
267: LOGGER.fine(message.toString());
268: return;
269: }
270:
271: // /////////////////////////////////////////////////////////////////////
272: //
273: //
274: // Preparing all the params
275: //
276: //
277: // /////////////////////////////////////////////////////////////////////
278: if (!outputLocation.exists())
279: outputLocation.mkdir();
280:
281: // getting envelope and other information about dimension
282: final GeneralEnvelope envelope = inReader.getOriginalEnvelope();
283: message = new StringBuffer("Original envelope is ")
284: .append(envelope.toString());
285: if (LOGGER.isLoggable(Level.FINE))
286: LOGGER.fine(message.toString());
287: // world.200401.3x21600x21600.C1.tif
288:
289: final GeneralGridRange range = inReader.getOriginalGridRange();
290:
291: message = new StringBuffer("Original range is ").append(range
292: .toString());
293: if (LOGGER.isLoggable(Level.FINE))
294: LOGGER.fine(message.toString());
295: // world.200401.3x21600x21600.C1.tif
296:
297: message = new StringBuffer(
298: "New matrix dimension is (cols,rows)==(").append(
299: numTileX).append(",").append(numTileY).append(")");
300: if (LOGGER.isLoggable(Level.FINE))
301: LOGGER.fine(message.toString());
302: // world.200401.3x21600x21600.C1.tif
303:
304: final int uppers[] = range.getUppers();
305: final double newRange[] = new double[] { uppers[0] / numTileX,
306: uppers[1] / numTileY };
307: final double minx = envelope.getMinimum(0);
308: final double miny = envelope.getMinimum(1);
309: final double maxx = envelope.getMaximum(0);
310: final double maxy = envelope.getMaximum(1);
311: // getting resolution
312: final double dx = envelope.getLength(0) / numTileX;
313: final double dy = envelope.getLength(1) / numTileY;
314:
315: double _maxx = 0.0;
316: double _maxy = 0.0;
317: double _minx = 0.0;
318: double _miny = 0.0;
319: final AbstractProcessor processor = new DefaultProcessor(null);
320: GridCoverage2D gc = null;
321: File fileOut;
322: GeoTiffWriter writerWI;
323: ParameterValue gg;
324: GeneralEnvelope cropEnvelope;
325:
326: // ///////////////////////////////////////////////////////////////////
327: //
328: // MAIN LOOP
329: //
330: //
331: // ///////////////////////////////////////////////////////////////////
332: for (int i = 0; i < numTileY; i++)
333: for (int j = 0; j < numTileX; j++) {
334:
335: // //
336: //
337: // computing the bbox for this tile
338: //
339: // //
340: _maxx = minx + (j + 1) * dx;
341: _minx = minx + (j) * dx;
342: _maxy = miny + (i + 1) * dy;
343: _miny = miny + (i) * dy;
344: if (_maxx > maxx)
345: _maxx = maxx;
346: if (_maxy > maxy)
347: _maxy = maxy;
348:
349: // //
350: //
351: // building gridgeometry for the read operation
352: //
353: // //
354: gg = (ParameterValue) ImageMosaicFormat.READ_GRIDGEOMETRY2D
355: .createValue();
356: cropEnvelope = new GeneralEnvelope(new double[] {
357: _minx, _miny }, new double[] { _maxx, _maxy });
358: cropEnvelope.setCoordinateReferenceSystem(inReader
359: .getCrs());
360: gg.setValue(new GridGeometry2D(new GeneralGridRange(
361: new Rectangle(0, 0, 800, 800)), cropEnvelope));
362:
363: message = new StringBuffer(
364: "Reading with grid envelope ")
365: .append(cropEnvelope.toString());
366: if (LOGGER.isLoggable(Level.FINE))
367: LOGGER.fine(message.toString());
368:
369: try {
370: gc = (GridCoverage2D) inReader
371: .read(new GeneralParameterValue[] { gg });
372:
373: } catch (IOException e) {
374: LOGGER
375: .log(Level.SEVERE, e.getLocalizedMessage(),
376: e);
377: fireEvent(e.getLocalizedMessage(), 0);
378: return;
379: }
380:
381: fileOut = new File(outputLocation, new StringBuffer(
382: "mosaic").append("_").append(
383: Integer.toString(i * numTileX + j)).append(".")
384: .append("tiff").toString());
385: if (fileOut.exists())
386: fileOut.delete();
387:
388: message = new StringBuffer(
389: "Preparing tile (col,row)==(").append(j)
390: .append(",").append(i).append(") to file ")
391: .append(fileOut);
392: if (LOGGER.isLoggable(Level.FINE))
393: LOGGER.fine(message.toString());
394:
395: // //
396: //
397: // building gridgeometry for the read operation
398: //
399: // //
400:
401: ParameterValueGroup param = processor.getOperation(
402: "CoverageCrop").getParameters();
403: param.parameter("Source").setValue(gc);
404: param.parameter("Envelope").setValue(cropEnvelope);
405:
406: GridCoverage2D cropped = (GridCoverage2D) processor
407: .doOperation(param);
408:
409: final GeneralGridRange newGridrange = new GeneralGridRange(
410: new Rectangle2D.Double(0.0, 0.0, newRange[0],
411: newRange[1]).getBounds());
412: final GridGeometry2D scaledGridGeometry = new GridGeometry2D(
413: newGridrange, cropEnvelope);
414: param = processor.getOperation("Resample")
415: .getParameters();
416: param.parameter("Source").setValue(cropped);
417: param.parameter("CoordinateReferenceSystem").setValue(
418: inReader.getCrs());
419: param.parameter("GridGeometry").setValue(
420: scaledGridGeometry);
421: param
422: .parameter("InterpolationType")
423: .setValue(
424: Interpolation
425: .getInstance(Interpolation.INTERP_NEAREST));
426:
427: GridCoverage2D scaled = (GridCoverage2D) processor
428: .doOperation(param);
429:
430: message = new StringBuffer("Writing out...");
431: if (LOGGER.isLoggable(Level.FINE))
432: LOGGER.fine(message.toString());
433:
434: try {
435: writerWI = new GeoTiffWriter(fileOut);
436: writerWI.write(scaled, null);
437: } catch (IOException e) {
438: fireException(e);
439: return;
440: }
441:
442: }
443:
444: message = new StringBuffer("Done...");
445: if (LOGGER.isLoggable(Level.FINE))
446: LOGGER.fine(message.toString());
447: }
448:
449: private boolean parseArgs(String[] args) {
450: cmdLine = cmdParser.parseAndHelp(args);
451: if (cmdLine != null && cmdLine.hasOption(versionOpt)) {
452: System.out
453: .print(new StringBuffer(
454: "MosaicIndexBuilder - GeoSolutions S.a.s (C) 2006 - v")
455: .append(CoverageTiler.versionNumber)
456: .toString());
457: System.exit(1);
458:
459: } else if (cmdLine != null) {
460: // ////////////////////////////////////////////////////////////////
461: //
462: // parsing command line parameters and setting up
463: // Mosaic Index Builder options
464: //
465: // ////////////////////////////////////////////////////////////////
466: inputLocation = new File((String) cmdLine
467: .getValue(inputLocationOpt));
468:
469: // output files' directory
470: if (cmdLine.hasOption(outputLocationOpt))
471: outputLocation = new File((String) cmdLine
472: .getValue(outputLocationOpt));
473: else
474: outputLocation = new File(
475: inputLocation.getParentFile(), "tiled");
476:
477: // tile dim
478: final String tileDim = (String) cmdLine
479: .getValue(tileDimOpt);
480: final String[] pairs = tileDim.split(",");
481: numTileX = Integer.parseInt(pairs[0]);
482: numTileY = Integer.parseInt(pairs[1]);
483:
484: // //
485: //
486: // Thread priority
487: //
488: // //
489: // index name
490: if (cmdLine.hasOption(priorityOpt))
491: priority = Integer.parseInt((String) cmdLine
492: .getValue(priorityOpt));
493: return true;
494:
495: }
496: return false;
497:
498: }
499:
500: public File getInputLocation() {
501: return inputLocation;
502: }
503:
504: public void setInputLocation(File inputLocation) {
505: this .inputLocation = inputLocation;
506: }
507:
508: public int getNumTileX() {
509: return numTileX;
510: }
511:
512: public void setNumTileX(int numTileX) {
513: this .numTileX = numTileX;
514: }
515:
516: public int getNumTileY() {
517: return numTileY;
518: }
519:
520: public void setNumTileY(int numTileY) {
521: this .numTileY = numTileY;
522: }
523:
524: public File getOutputLocation() {
525: return outputLocation;
526: }
527:
528: public void setOutputLocation(File outputLocation) {
529: this.outputLocation = outputLocation;
530: }
531: }
|