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!");
}
}