package com.rgi.suite.cli; import com.rgi.suite.cli.tilestoreadapter.HeadlessTileStoreAdapter; import org.gdal.osr.SpatialReference; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Option; import javax.activation.MimeType; import javax.activation.MimeTypeParseException; import java.io.File; import java.util.logging.Level; import java.util.logging.Logger; /** * Business Logic for Running SWAGD in a headless environment. contains logic to * support Args4J command line arguments * -h : show help * --inputsrs <epsg int> : Input Spatial reference system EPSG identifier (ie 4326) * --outputsrs <epsg int value> : Desired output SRS EPSG identifier,eg 3857 * -c (--compression) <jpeg> : Compression type for image tiling,default is jpeg * -d (--description) <text description> : Tile set description * -f (--format) <image/png or image/jpg> : Image format for tiling operations,default is png (options are png,jpeg,etc.) * -H (--height) <Height: 1-10000> : Tile height in pixels; default is 256 * -i (-in) <Input File Path> : REQUIRED! Input source for tiling/Packaging operation * -ti (--intileset, --intilesetname) : Tile Set name for GeoPackages (input), default is short name of output geopackage. * -to (--outtileset, --outtilesetname) :Tile set name for geopackage output * -o (-out) <Output File Path> : Full output path for tiling/Packaging operation * -q (--compressionQuality) <1-100> : Compression compressionQuality for jpeg compression, between 0-100 * -W (--width) <1-10000> : Tile width in pixels; default is 256 * * @author matthew.moran */ public class HeadlessOptions { public static final int MAGIC_MAX_VALUE = 10000; private static final int DEFAULT_DIMENSION = 256; public static final int GLOBAL_WEB_MERCATOR = 3857; public static final int GLOBAL_GEODETIC = 4326; // set to true when isValid is called private HeadlessOptionsValidator validator; private boolean showHelp; private boolean isValid; // variables to hold command line arguments private File inputFile; private File outputFile; private int outputSrs = HeadlessOptions.GLOBAL_WEB_MERCATOR; private int inputSrs = HeadlessOptions.GLOBAL_GEODETIC; private String tileSetNameIn = ""; private String tileSetNameOut = ""; private String tileSetDescription = ""; private MimeType imageFormat; private int tileWidth = HeadlessOptions.DEFAULT_DIMENSION; private int tileHeight = HeadlessOptions.DEFAULT_DIMENSION; private String compressionType = "jpeg"; @SuppressWarnings("MagicNumber") private int compressionQuality = 75; private final Logger logger; /** * Constructor * * @param logger - logger to write progress out to */ public HeadlessOptions(final Logger logger) { this.logger = logger; try { this.imageFormat = new MimeType("image/jpeg"); } catch(final MimeTypeParseException error) { this.logger.log(Level.SEVERE, "Error creating default image format: " + error.getMessage()); } } /** * path to the input file/directory for packaging/tiling. * * @param filePath - path to the input file or directory to read from * @throws IllegalArgumentException if filePath leads to a non-existent file, is null, or is empty. */ @Option(required = true, name = "-i", aliases = "-in", metaVar = "<Input File Path>", usage = "Input source for tiling orPackaging operation") public void setInputFile(final String filePath) { final String path = HeadlessOptions.getFullPath(filePath); final File file = new File(path); if(file.exists()) { this.inputFile = new File(path); } else { throw new IllegalArgumentException(String.format("FilePath: %s does not exist, input files must exist!", filePath)); } } @Option(required = false, help = true, name = "-h", aliases = "--help", usage = "Show Help") public void showHelp(final boolean show) throws CmdLineException { this.showHelp = show; if(show) { final CmdLineParser parser = new CmdLineParser(this); parser.printUsage(System.out); } } /** * path to the output file/directory for packaging/tiling * * @param filePath - output file path * @throws IllegalArgumentException - if output path is null or empty */ @Option(required = true, name = "-o", aliases = "-out", metaVar = "<Output File Path>", usage = "Full output path for tiling or Packaging operation") public void setOutputFile(final String filePath) { final String path = HeadlessOptions.getFullPath(filePath); this.outputFile = new File(path); } /** * EPSG output SRS number * * @param outSrs - input SRS * @throws IllegalArgumentException if unrecognized spatial reference. */ @Option(name = "--outputsrs", metaVar = "<epsg srs int>", usage = "Desired output SRS EPSG identifier, eg 3857") public void setOutputSrs(final int outSrs) { // special case for TMS. final SpatialReference srs = new SpatialReference(); try { if(srs.ImportFromEPSG(outSrs) == 0) { this.outputSrs = outSrs; } } catch(final RuntimeException ignored) { throw new IllegalArgumentException(String.format("Error: Output SRS %d is not a GDAL supported EPSG value.", outSrs)); } } /** * EPSG input SRS number, required if input is a tms cache * * @param inSrs - input SRS * @throws IllegalArgumentException if unrecognized spatial reference. */ @Option(name = "--inputsrs", metaVar = "<epsg srs int>", usage = "Input Spatial reference system EPSG identifier (ie 4326)") public void setInputSRS(final int inSrs) { // special case for TMS. final SpatialReference srs = new SpatialReference(); try { if(srs.ImportFromEPSG(inSrs) == 0) { this.inputSrs = inSrs; } } catch(final RuntimeException ignored) { throw new IllegalArgumentException(String.format("Error: input SRS %d is not a GDAL supported EPSG value.", inSrs)); } } /** * Tile set Name * * @param name - name of the tileset to read from * @throws IllegalArgumentException - name is null, or longer than 10000 characters */ @Option(name = "-ti", metaVar = "<Tile Set Name>", aliases = {"--intileset", "--intilesetname"}, usage = "Input Tile Set for GeoPackages, default is short name of output geopackage.") public void setTileSetNameIn(final String name) { if(name != null && name.length() <= HeadlessOptions.MAGIC_MAX_VALUE) { this.tileSetNameIn = name; } else { throw new IllegalArgumentException("Provided Name is invalid, must be a " + "non-null string shorter than MAGIC_MAX_VALUE characters"); } } /** * Tile set Name * * @param name - tile set name to write out to * @throws IllegalArgumentException - name is null, or longer than 10000 characters */ @Option(name = "-to", metaVar = "<Tile Set Name>", aliases = {"--outtileset", "--outtilesetname"}, usage = "Input Tile Set for GeoPackages, default is short name of output geopackage.") public void setTileSetNameOut(final String name) { if(name != null && name.length() <= HeadlessOptions.MAGIC_MAX_VALUE) { this.tileSetNameOut = name; } else { throw new IllegalArgumentException("Provided Name is invalid, must be a " + "non-null string shorter than MAGIC_MAX_VALUE characters"); } } /** * Tile Set Description * * @param description - test description of tileset */ @Option(name = "-d", aliases = "--description", metaVar = "<text tile set description>", usage = "Tile set description") public void setTileSetDescription(final String description) { if(description != null && description.length() <= HeadlessOptions.MAGIC_MAX_VALUE) { this.tileSetDescription = description; } else { throw new IllegalArgumentException("Provided Description is invalid, must be a " + "non-null string shorter than MAGIC_MAX_VALUE characters"); } } /** * tile width * * @param width - tile width * @throws IllegalArgumentException value must be between 1 - MAGIC_MAX_VALUE */ @Option(name = "-W", aliases = "--width", metaVar = "<1-9999>", usage = "Tile width in pixels; default is 256") public void setTileWidth(final int width) { if(width > 0 && width < HeadlessOptions.MAGIC_MAX_VALUE) { this.tileWidth = width; } else { throw new IllegalArgumentException(String.format("error setting tile Width to %d, " + "value must be greater than 0 and less than MAGIC_MAX_VALUE", width)); } } /** * Tile Height * * @param height - tile height * @throws IllegalArgumentException - value must be between 1 and MAGIC_MAX_VALUE */ @Option(name = "-H", aliases = "--height", metaVar = "<1-9999>", usage = "Tile height in pixels; default is 256") public void setTileHeight(final int height) { if(height > 0 && height < HeadlessOptions.MAGIC_MAX_VALUE) { this.tileHeight = height; } else { throw new IllegalArgumentException(String.format("error setting tile height to %d, " + "value must be greater than 0 and less than MAGIC_MAX_VALUE", height)); } } // image settings @SuppressWarnings("HardcodedFileSeparator") @Option(name = "-f", aliases = "--format", metaVar = "image/png or image/jpeg", usage = "Image format for tiling operations, default is png (options are png, jpeg,etc.)") private void setImageFormat(final String formatString) throws MimeTypeParseException { if(formatString.equalsIgnoreCase("image/jpeg") || formatString.equalsIgnoreCase("image/png")) { this.imageFormat = new MimeType(formatString.toLowerCase()); } else { //noinspection HardcodedFileSeparator throw new IllegalArgumentException(String.format("error setting image format to %s! must be 'image/jpeg' or 'image/png'", formatString)); } } @Option(name = "-c", aliases = "--compression", metaVar = "<jpeg>", usage = "Compression type for image tiling, default is jpeg") public void setCompressionType(final String compressionType) { if(compressionType.equalsIgnoreCase("jpeg")) { this.compressionType = compressionType.toLowerCase(); } else { throw new IllegalArgumentException(String.format("error setting image compression to %s! must be jpeg", compressionType)); } } /** * quality of compression as a percentage (1-100%) * * @param compressionQuality - quality of compression as a percentage */ @Option(name = "-q", aliases = "--quality", metaVar = "<1-100>", usage = "Compression quality for jpeg compression, between 0-100") public void setCompressionQuality(final int compressionQuality) { if(compressionQuality > 0 && compressionQuality <= 100) { this.compressionQuality = compressionQuality; } else { throw new IllegalArgumentException("Error: Compression Quality must be between 1-100"); } } //Getters public int getTileWidth() { return this.tileWidth; } public int getTileHeight() { return this.tileHeight; } public int getOutputSrs() { return this.outputSrs; } public int getInputSrs() { return this.inputSrs; } public File getInputFile() { return this.inputFile; } public MimeType getImageFormat() { return this.imageFormat; } public boolean getShowHelp() { return this.showHelp; } /** * returns the tilestore adaptor for input * * @return - adaptor, or null if validation has not yet occured, OR if an error was thrown. * @throws IllegalArgumentException */ public HeadlessTileStoreAdapter getInputAdapter() { if(this.validator != null) { return this.validator.getInputAdapter(); } throw new IllegalArgumentException("Validation has not occurred"); } /** * returns the tilestore adaptor for output * * @return - adaptor, or null if validation has not yet occured, OR if an error was thrown. * @throws IllegalArgumentException - if validation failed or has not yet occured */ public HeadlessTileStoreAdapter getOutputAdapter() { if(this.validator != null) { return this.validator.getOutputAdapter(); } throw new IllegalArgumentException("Validation has not occurred"); } public File getOutputFile() { return this.outputFile; } public String getTileSetNameIn() { return this.tileSetNameIn; } public String getTileSetNameOut() { return this.tileSetNameOut; } public String getTileSetDescription() { return this.tileSetDescription; } public String getCompressionType() { return this.compressionType; } public int getCompressionQuality() { return this.compressionQuality; } public boolean isValid() { if(this.validator == null) { this.validator = new HeadlessOptionsValidator(this, this.logger); } return this.validator.isValid(); } /** * returns a full path to the specified file, replacing cmdline based shortcuts * * @param filePath - filepath to replace local charaters in * @return absolute path of string * @throws IllegalArgumentException */ @SuppressWarnings("AccessOfSystemProperties") private static String getFullPath(final String filePath) { if(filePath != null && !filePath.isEmpty()) { String path = filePath; if(path.startsWith('~' + File.separator)) { path = String.format("%s%s", System.getProperty("user.home"), path.substring(1)); } else if(path.startsWith('.' + File.separator)) { path = String.format("%s%s", System.getProperty("user.dir"), path.substring(1)); } return path; } throw new IllegalArgumentException("File Path provided is null or empty!"); } }