/* * 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.imagepyramid; import java.awt.Rectangle; import java.awt.geom.Rectangle2D; import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageWriteParam; import javax.media.jai.Interpolation; import javax.media.jai.InterpolationNearest; import org.apache.commons.cli2.Option; import org.apache.commons.cli2.validation.InvalidArgumentException; import org.apache.commons.cli2.validation.Validator; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridEnvelope2D; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.coverage.grid.io.AbstractGridFormat; import org.geotools.coverage.grid.io.OverviewPolicy; import org.geotools.coverage.grid.io.imageio.GeoToolsWriteParams; import org.geotools.coverage.processing.CoverageProcessor; import org.geotools.factory.Hints; import org.geotools.gce.geotiff.GeoTiffWriter; import org.geotools.gce.imagemosaic.ImageMosaicFormat; import org.geotools.gce.imagemosaic.ImageMosaicReader; import org.geotools.geometry.GeneralEnvelope; import org.geotools.utils.CoverageToolsConstants; 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.coverage.grid.GridEnvelope; import org.opengis.parameter.GeneralParameterValue; import org.opengis.parameter.ParameterValue; import org.opengis.parameter.ParameterValueGroup; /** * <p> * Usage:<br/> <code>PyramidLayerBuilder -h -v -s -t -f -a -o -p -c</code> * </p> * * <pre> * * where: * -h : Prints a nice command line Help * -v : Prints the tools Version * -s : Is the path where the raster(s) is(are) located * -t : Is the tile dimensions as a couple width,height in pixels (e.g. 512,512) * -f : Represents the scale factor. If you want a raster which is 1/2 resolution * of the original, f should be 2 * -a : Represents the Scaling algorithm to use. You can choose among one of the following * nn, bil, avg, filt * -o : Represents the output format. It can be one of the following * tiff, tif, gtiff, gtif, png, jpeg * -p : Is the Thread Priority, a number between 1 and 10 -> 1 [LOW] - 5 [MED] - 10 [HIGH] * -c : Represents the JAI TileCache dimension. This is an optional parameter which allows * you to tune the tool performances. * </pre> * * <p> * Example of usage:<br/> * <code>PyramidLayerBuilder -t "512,512" -s "/usr/home/tmp/tiled/world.200412.3x21600x21600.a1_ref.shp" -f 2 -a nn -c 512</code> * </p> * * @author Simone Giannecchini, GeoSolutions * @author Alessio Fabiani. GeoSolutions * * * * @source $URL$ * @version 0.3 * */ public class PyramidLayerBuilder extends BaseArgumentsManager implements Runnable, ProcessingEventListener { /** Static immutable map for scaling algorithms. */ private static Set<String> scalingAlgorithms; static { scalingAlgorithms = new HashSet<String>(); scalingAlgorithms.add("nn"); scalingAlgorithms.add("bil"); scalingAlgorithms.add("avg"); scalingAlgorithms.add("filt"); } /** Static immutable ap for scaling algorithms. */ private static Set<String> outputFormats; static { outputFormats = new HashSet<String>(); outputFormats.add("tiff"); outputFormats.add("tif"); outputFormats.add("gtiff"); outputFormats.add("gtif"); outputFormats.add("png"); outputFormats.add("jpeg"); } /** Default Logger * */ private final static Logger LOGGER = org.geotools.util.logging.Logging .getLogger(PyramidLayerBuilder.class.toString()); /** Program Version */ private final static String VERSION = "0.3"; private final static String NAME = "PyramidLayerBuilder"; private Option inputLocationOpt; private Option outputLocationOpt; private Option tileDimOpt; private Option scaleAlgorithmOpt; private Option outFormatOpt; private double tileW; private double tileH; private File inputLocation; private File outputLocation; private String scaleAlgorithm; private String outputFormat; private Option scaleFactorOpt; private int scaleFactor; private Option compressionRatioOpt; private Option compressionTypeOpt; private Option internalTileDimOpt; private int internalTileWidth = CoverageToolsConstants.DEFAULT_INTERNAL_TILE_WIDTH; private int internalTileHeight = CoverageToolsConstants.DEFAULT_INTERNAL_TILE_HEIGHT; private String compressionScheme = CoverageToolsConstants.DEFAULT_COMPRESSION_SCHEME; private double compressionRatio = CoverageToolsConstants.DEFAULT_COMPRESSION_RATIO; public PyramidLayerBuilder() { super(NAME, VERSION); // ///////////////////////////////////////////////////////////////////// // Options for the command line // ///////////////////////////////////////////////////////////////////// inputLocationOpt = optionBuilder.withShortName("s").withLongName( "source_directory").withArgument( argumentBuilder.withName("source").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 location can be chosen"); final File source = new File((String) args .get(0)); if (!source.isFile() || !source.exists()) throw new InvalidArgumentException( new StringBuilder( "The provided source is invalid! ") .toString()); } }).create()).withDescription( "path where files are located").withRequired(true).create(); outputLocationOpt = optionBuilder .withShortName("d") .withLongName("dest_directory") .withArgument( argumentBuilder.withName("destination").withMinimum(0) .withMaximum(1).create()) .withDescription( "output directory, if none is provided, the \"tiled\" directory will be used") .withRequired(false).create(); tileDimOpt = optionBuilder.withShortName("t").withLongName( "tiled_dimension").withArgument( argumentBuilder.withName("t").withMinimum(1).withMaximum(1) .create()).withDescription( "tile dimensions as a couple width,height in pixels") .withRequired(true).create(); scaleFactorOpt = optionBuilder .withShortName("f") .withLongName("scale_factor") .withArgument( argumentBuilder.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 StringBuilder( "The provided scale factor is negative! ") .toString()); if (factor == 1) { LOGGER .warning("The scale factor is 1!"); System.exit(0); } } }).create()).withDescription( "integer scale factor") .withRequired(true).create(); scaleAlgorithmOpt = optionBuilder .withShortName("a") .withLongName("scaling_algorithm") .withArgument( argumentBuilder.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 StringBuilder( "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(); outFormatOpt = optionBuilder .withShortName("o") .withLongName("out_format") .withArgument( argumentBuilder.withName("o").withMinimum(0) .withMaximum(1) .withDescription("output format") // .withDefault("gtiff") .withValidator(new Validator() { public void validate(List args) throws InvalidArgumentException { final int size = args.size(); if (size > 1) throw new InvalidArgumentException( "Only one output format at a time can be specified"); if (!outputFormats .contains(args.get(0))) throw new InvalidArgumentException( new StringBuilder( "The output format ") .append(args.get(0)) .append( " is not permitted") .toString()); } }).create()).withDescription("output format") .withRequired(false).create(); compressionTypeOpt = optionBuilder .withShortName("z") .withLongName("compressionType") .withDescription("compression type.") .withArgument( argumentBuilder.withName("compressionType") .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"); } }).create()).withRequired(false) .create(); compressionRatioOpt = optionBuilder .withShortName("r") .withLongName("compressionRatio") .withDescription("compression ratio.") .withArgument( argumentBuilder.withName("compressionRatio") .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"); final String val = (String) args .get(0); final double value = Double .parseDouble(val); if (value <= 0 || value > 1) throw new InvalidArgumentException( "Invalid compressio ratio"); } }).create()).withRequired(false) .create(); internalTileDimOpt = optionBuilder.withShortName("it").withLongName( "internal_tile_dimension").withArgument( argumentBuilder.withName("it").withMinimum(0).withMaximum(1) .create()).withDescription( "Internal width and height of each tile we generate") .withRequired(false).create(); addOption(compressionTypeOpt); addOption(compressionRatioOpt); addOption(inputLocationOpt); addOption(tileDimOpt); addOption(scaleFactorOpt); addOption(scaleAlgorithmOpt); addOption(internalTileDimOpt); // ///////////////////////////////////////////////////////////////////// // // Help Formatter // // ///////////////////////////////////////////////////////////////////// finishInitialization(); } /** * * @param args * @throws IOException * @throws IllegalArgumentException * @throws InterruptedException */ public static void main(String[] args) throws IllegalArgumentException, IOException, InterruptedException { final PyramidLayerBuilder pyramidBuilder = new PyramidLayerBuilder(); pyramidBuilder.addProcessingEventListener(pyramidBuilder); if (pyramidBuilder.parseArgs(args)) { final Thread t = new Thread(pyramidBuilder, "PyramidBuilder"); t.setPriority(pyramidBuilder.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 // // //////////////////////////////////////////////////////////////// inputLocation = new File((String) getOptionValue(inputLocationOpt)); // tile dim final String tileDim = (String) getOptionValue(tileDimOpt); final String[] pairs = tileDim.split(","); tileW = Integer.parseInt(pairs[0]); tileH = Integer.parseInt(pairs[1]); // // // // scale factor // // // final String scaleF = (String) getOptionValue(scaleFactorOpt); scaleFactor = Integer.parseInt(scaleF); // output files' directory if (hasOption(outputLocationOpt)) outputLocation = new File( (String) getOptionValue(outputLocationOpt)); else outputLocation = new File(inputLocation.getParentFile(), String .valueOf(scaleFactor)); // // // // scaling algorithm // // // scaleAlgorithm = (String) getOptionValue(scaleAlgorithmOpt); if (scaleAlgorithm == null) scaleAlgorithm = "nn"; // // // // // // output format // // // outputFormat = (String) getOptionValue(outFormatOpt); return true; } public void run() { // ///////////////////////////////////////////////////////////////////// // // // PARSING INPUT PARAMETERS // // // ///////////////////////////////////////////////////////////////////// StringBuilder message = new StringBuilder("Requested scale factor is ").append(scaleFactor); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(message.toString()); fireEvent(message.toString(), 0); // ///////////////////////////////////////////////////////////////////// // // // Opening the base mosaic // // // ///////////////////////////////////////////////////////////////////// // mosaic reader message = new StringBuilder("Acquiring a mosaic reader to mosaic ") .append(inputLocation); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(message.toString()); fireEvent(message.toString(), 0); ImageMosaicReader inReader = null; try { inReader = new ImageMosaicReader(inputLocation, new Hints(Hints.OVERVIEW_POLICY, OverviewPolicy.IGNORE)); } catch (IOException e) { LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); fireException(e); return; } // ///////////////////////////////////////////////////////////////////// // // // Preparing all the params // // // ///////////////////////////////////////////////////////////////////// // output files' directory if (!outputLocation.exists()) outputLocation.mkdir(); // getting envelope and other information about dimension final GeneralEnvelope envelope = inReader.getOriginalEnvelope(); message = new StringBuilder("Original envelope is ").append(envelope.toString()); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(message.toString()); fireEvent(message.toString(), 0); final GridEnvelope range = inReader.getOriginalGridRange(); message = new StringBuilder("Original range is ").append(range .toString()); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(message.toString()); fireEvent(message.toString(), 0); // new number of rows and columns final double newWidth = (range.getSpan(0) * 1.0) / scaleFactor; final double newHeight = (range.getSpan(1) * 1.0) / scaleFactor; if (tileW > newWidth) tileW = newWidth; if (tileH > newHeight) tileH = newHeight; message = new StringBuilder("New dimension is (W,H)==(").append(newWidth).append(",").append(newHeight).append(")"); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(message.toString()); fireEvent(message.toString(), 0); int newCols = (int) (newWidth / tileW); int newRows = (int) (newHeight / tileH); final boolean hasRemainingColum = (newWidth % tileW) != 0; final boolean hasRemainingRow = (newHeight % tileH) != 0; message = new StringBuilder("New matrix dimension is (cols,rows)==(").append(newCols).append(",").append(newRows).append(")"); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(message.toString()); fireEvent(message.toString(), 0); final double minx = envelope.getMinimum(0); final double miny = envelope.getMinimum(1); final double maxx = envelope.getMaximum(0); final double maxy = envelope.getMaximum(1); double _maxx = 0.0; double _maxy = 0.0; double _minx = 0.0; double _miny = 0.0; // /////////////////////////////////////////////////////////////////// // // MAIN LOOP // // // /////////////////////////////////////////////////////////////////// newRows += hasRemainingRow ? 1 : 0; newCols += hasRemainingColum ? 1 : 0; final double totalNumberOfFile = newRows * newCols; // getting resolution of each tile final double tileGeoWidth = envelope.getSpan(0) / newCols; final double tileGeoHeight = envelope.getSpan(1) / newRows; final int uppers[] = range.getHigh().getCoordinateValues(); uppers[0] ++; uppers[1] ++; final double newRange[] = new double[] { uppers[0] / newCols,uppers[1] / newRows }; final CoverageProcessor processor = CoverageProcessor.getInstance(); for (int i = 0; i < newRows; i++) for (int j = 0; j < newCols; j++) { // // // // computing the bbox for this tile // // // _maxx = minx + (j + 1) * tileGeoWidth; _minx = minx + (j) * tileGeoWidth; _maxy = miny + (i + 1) * tileGeoHeight; _miny = miny + (i) * tileGeoHeight; if (_maxx > maxx) _maxx = maxx; if (_maxy > maxy) _maxy = maxy; // // // // creating the output file // // // final File fileOut = new File(outputLocation, new StringBuilder( "mosaic").append("_").append( Integer.toString(i * newCols + j)).append(".").append( "tiff").toString()); if (fileOut.exists()) fileOut.delete(); message = new StringBuilder("Preparing tile (col,row)==(") .append(j) .append(",") .append(i) .append(") to file ") .append(fileOut); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(message.toString()); fireEvent(message.toString(), (j + i * newCols)/ totalNumberOfFile); // // // // building gridgeometry for the read operation // // // final ParameterValue<GridGeometry2D> gg = ImageMosaicFormat.READ_GRIDGEOMETRY2D.createValue(); final GeneralEnvelope cropEnvelope = new GeneralEnvelope(new double[] { _minx, _miny }, new double[] { _maxx,_maxy }); cropEnvelope.setCoordinateReferenceSystem(inReader.getCrs()); //we need to supply the requeste grid range but we use a fake one since we are using the ignore overviews switch gg.setValue(new GridGeometry2D(new GridEnvelope2D(new Rectangle(0, 0, 800, 800)), cropEnvelope)); message = new StringBuilder("Reading with grid envelope ").append(cropEnvelope.toString()); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(message.toString()); fireEvent(message.toString(), (j + i * newCols) / totalNumberOfFile); // // // // read the needed part and then crop to be sure that we have what we need // // // GridCoverage2D gc; try { gc = (GridCoverage2D) inReader .read(new GeneralParameterValue[] { gg }); } catch (IOException e) { LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); fireEvent(e.getLocalizedMessage(), 0); return; } ParameterValueGroup param = processor.getOperation("CoverageCrop").getParameters(); param.parameter("Source").setValue(gc); param.parameter("Envelope").setValue(cropEnvelope); param.parameter("ConserveEnvelope").setValue(Boolean.TRUE); final GridCoverage2D cropped = (GridCoverage2D) processor.doOperation(param); // // // // Adjusting the resolution in order to be the same as for all the others coverage // // // final GridEnvelope2D newGridrange = new GridEnvelope2D(new Rectangle2D.Double(0.0, 0.0, newRange[0],newRange[1]).getBounds()); final GridGeometry2D scaledGridGeometry = new GridGeometry2D(newGridrange, cropEnvelope); param = processor.getOperation("Resample").getParameters(); param.parameter("Source").setValue(cropped); param.parameter("CoordinateReferenceSystem").setValue(inReader.getCrs()); param.parameter("GridGeometry").setValue(scaledGridGeometry); param.parameter("InterpolationType").setValue(Interpolation.getInstance(Interpolation.INTERP_NEAREST)); gc = (GridCoverage2D) processor.doOperation(param); message = new StringBuilder("Scaling..."); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(message.toString()); fireEvent(message.toString(), 0); if (scaleAlgorithm.equalsIgnoreCase("nn")) { param = processor.getOperation("Scale").getParameters(); param.parameter("Source").setValue(gc); param.parameter("xScale").setValue(new Float(1.0 / scaleFactor)); param.parameter("yScale").setValue(new Float(1.0 / scaleFactor)); param.parameter("xTrans").setValue(new Float(0)); param.parameter("yTrans").setValue(new Float(0)); param.parameter("Interpolation").setValue(Interpolation.getInstance(Interpolation.INTERP_BILINEAR)); gc = (GridCoverage2D) CoverageToolsConstants.SCALE_FACTORY.doOperation(param, new Hints()); } else if (scaleAlgorithm.equalsIgnoreCase("filt")) { // scaling param = CoverageToolsConstants.FILTERED_SUBSAMPLE_FACTORY.getParameters(); param.parameter("source").setValue(gc); param.parameter("scaleX").setValue(new Integer((int) scaleFactor)); param.parameter("scaleY").setValue(new Integer((int) scaleFactor)); param.parameter("qsFilterArray").setValue(new float[] { 0.5F, 1.0F / 3.0F, 0.0F,-1.0F / 12.0F }); param.parameter("Interpolation").setValue(new InterpolationNearest()); gc = (GridCoverage2D) CoverageToolsConstants.FILTERED_SUBSAMPLE_FACTORY.doOperation(param, new Hints()); } else if (scaleAlgorithm.equalsIgnoreCase("bil")) { param = processor.getOperation("Scale").getParameters(); param.parameter("Source").setValue(gc); param.parameter("xScale").setValue( new Float(1.0 / scaleFactor)); param.parameter("yScale").setValue( new Float(1.0 / scaleFactor)); param.parameter("xTrans").setValue(new Float(0)); param.parameter("yTrans").setValue(new Float(0)); param.parameter("Interpolation").setValue(Interpolation.getInstance(Interpolation.INTERP_BILINEAR)); gc = (GridCoverage2D) CoverageToolsConstants.SCALE_FACTORY.doOperation(param, new Hints()); } else if (scaleAlgorithm.equalsIgnoreCase("avg")) { param = processor.getOperation("SubsampleAverage").getParameters(); param.parameter("Source").setValue(gc); param.parameter("scaleX").setValue(new Double(1.0 / scaleFactor)); param.parameter("scaleY").setValue(new Double(1.0 / scaleFactor)); param.parameter("Interpolation").setValue(scaleFactor); gc = (GridCoverage2D) CoverageToolsConstants.SUBSAMPLE_AVERAGE_FACTORY.doOperation(param, new Hints()); } else throw new IllegalArgumentException( "The provided scale algorithm is not availaible"); // // // // Writing out this coverage // // // message = new StringBuilder("Writing out..."); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(message.toString()); fireEvent(message.toString(), (j + i * newCols) / totalNumberOfFile); try { final GeoTiffWriter writerWI = new GeoTiffWriter(fileOut); final GeoToolsWriteParams wp = ((AbstractGridFormat) writerWI .getFormat()).getDefaultImageIOWriteParameters(); if (this.compressionScheme != null) { wp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); wp.setCompressionType(this.compressionScheme); wp.setCompressionQuality((float) this.compressionRatio); } wp.setTilingMode(ImageWriteParam.MODE_EXPLICIT); wp.setTiling(internalTileWidth, internalTileHeight, 0, 0); writerWI.write(gc, null); writerWI.dispose(); } catch (IOException e) { LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); fireEvent(e.getLocalizedMessage(), 0); return; } } message = new StringBuilder("Done..."); if (LOGGER.isLoggable(Level.FINE)) LOGGER.fine(message.toString()); fireEvent(message.toString(), 100); } public void getNotification(ProcessingEvent event) { LOGGER.info(new StringBuilder("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()); } public void setInputLocation(File inputLocation) { this.inputLocation = inputLocation; } public void setOutputLocation(File outputLocation) { this.outputLocation = outputLocation; } /** * @return the outputFormat */ public String getOutputFormat() { return outputFormat; } /** * @param outputFormat * the outputFormat to set */ public void setOutputFormat(String outputFormat) { this.outputFormat = outputFormat; } /** * @return the scaleAlgorithm */ public String getScaleAlgorithm() { return scaleAlgorithm; } /** * @param scaleAlgorithm * the scaleAlgorithm to set */ public void setScaleAlgorithm(String scaleAlgorithm) { this.scaleAlgorithm = scaleAlgorithm; } /** * @return the scaleFactor */ public int getScaleFactor() { return scaleFactor; } /** * @param scaleFactor * the scaleFactor to set */ public void setScaleFactor(int scaleFactor) { this.scaleFactor = scaleFactor; } public File getInputLocation() { return inputLocation; } public File getOutputLocation() { return outputLocation; } /** * @return the tileH */ public double getTileHeight() { return tileH; } /** * @param tileH * the tileH to set */ public void setTileHeight(int tileH) { this.tileH = tileH; } /** * @return the tileW */ public double getTileWidth() { return tileW; } /** * @param tileW * the tileW to set */ public void setTileWidth(int tileW) { this.tileW = tileW; } public double getCompressionRatio() { return compressionRatio; } public String getCompressionScheme() { return compressionScheme; } public void setCompressionRatio(double compressionRatio) { this.compressionRatio = compressionRatio; } public void setCompressionScheme(String compressionScheme) { this.compressionScheme = compressionScheme; } public int getInternalTileHeight() { return internalTileHeight; } public int getInternalTileWidth() { return internalTileWidth; } public void setInternalTileHeight(int internalTileHeight) { this.internalTileHeight = internalTileHeight; } public void setInternalTileWidth(int internalTileWidth) { this.internalTileWidth = internalTileWidth; } }