package com.rgi.suite.cli; import com.rgi.common.util.FileUtility; import com.rgi.geopackage.GeoPackage; import com.rgi.geopackage.verification.ConformanceException; import com.rgi.suite.cli.tilestoreadapter.GPKGTileStoreAdapter; import com.rgi.suite.cli.tilestoreadapter.HeadlessTileStoreAdapter; import com.rgi.suite.cli.tilestoreadapter.RawImageTileStoreAdapter; import com.rgi.suite.cli.tilestoreadapter.TMSTileStoreAdapter; import org.gdal.osr.SpatialReference; import utility.GdalUtility; import java.io.IOException; import java.sql.SQLException; import java.util.logging.Level; import java.util.logging.Logger; /** * Validates the provided headless options object to ensure that packaging or tiling won't break. * * @author Matthew.Moran */ public class HeadlessOptionsValidator { private final HeadlessOptions opts; private HeadlessTileStoreAdapter inputAdapter; private HeadlessTileStoreAdapter outputAdapter; private final Logger logger; HeadlessOptionsValidator(final HeadlessOptions options, final Logger logger) { this.logger = logger; this.opts = options; } public HeadlessTileStoreAdapter getInputAdapter() { return this.inputAdapter; } public HeadlessTileStoreAdapter getOutputAdapter() { return this.outputAdapter; } /** * Validates options parsed from the command line, returns * * @return - True/False on whether the given arguments are valid (ie, if * tiling and packaging are both flagged, that is an invalid * selection. */ public boolean isValid() { if(this.opts == null) { this.logger.log(Level.SEVERE, "No arguments provided"); return false; } if(this.opts.getShowHelp()) { return false; } this.logger.log(Level.INFO, "Validating arguments provided."); try { // if everything is kosher, we can run the tiling if(this.isinputFileValid() && this.isOutputFileValid() && this.isImageSettingsValid() && this.isSRSOptionsValid()) { this.logger.log(Level.INFO, "Options Provided passed Validation."); return true; } //if we get here something is extremely wrong, freak out this.logger.log(Level.SEVERE, "Error: Options Provided did not pass Validation."); return false; } catch(final RuntimeException error) { this.logger.log(Level.SEVERE, "Error: Exception thrown while validating." + error.getMessage()); return false; } } /** * Validates image settings for output. settings involved are: * <p> * TileWidth - width = 256 * <p> * TileHeight -height = 256 * <p> * Image Format - must be either jpeg, or png * <p> * Compression Type - only supports jpeg * <p> * Compression Quality - int between 0 and 100 * * @return boolean validity of inputs. */ private boolean isImageSettingsValid() { //isValid tile size, arbitrarily 10000 pixels because i needed a number if(this.opts.getTileWidth() <= 0 || this.opts.getTileWidth() > HeadlessOptions.MAGIC_MAX_VALUE || this.opts.getTileHeight() <= 0 || this.opts.getTileHeight() > HeadlessOptions.MAGIC_MAX_VALUE) { this.logger.log(Level.SEVERE, "Error: Tile Width and Height must be Greater than 0 and Less than 10000"); return false; } if(this.opts.getImageFormat() != null) { if(!this.opts.getCompressionType().equalsIgnoreCase("jpeg")) { this.logger.log(Level.SEVERE, "Error: jpeg compression type must be 'jpeg', and compression quality must be between 0 and 100"); return false; } return true; } this.logger.log(Level.SEVERE, "Error: Failed to isValid image settings, Image format is null"); return false; } /** * validates the input and output SRS's. Input SRS is only validated in the * case of a TMS store being the input.(ignored otherwise) output SRS it * validates vs 3857 * * @return true/false if SRS inputs are valid. */ private boolean isSRSOptionsValid() { if(this.opts.getOutputAdapter() instanceof GPKGTileStoreAdapter) { if(this.opts.getOutputSrs() == HeadlessOptions.GLOBAL_WEB_MERCATOR) { return true; } this.logger.log(Level.SEVERE, "Error: For tiling into geopackage, output SRS must be 3857."); return false; } return true; } private boolean isinputFileValid() { this.logger.log(Level.INFO, "Validating input Data."); if(!this.opts.getInputFile().exists()) { this.logger.log(Level.SEVERE, "Error: No input file provided."); return false; } //gpkg will handle the gpkg filetype, raw will accept all filetypes, tms will only take directories final boolean valid = this.isGPKGInputValid() || this.isRawInputValid() || this.isTMSInputValid(); if(!valid) { this.logger.log(Level.SEVERE, "Error: Failed to validate input File."); } return valid; } /** * validates input if its in RAW image format * * @return boolean if raw input is valid */ private boolean isRawInputValid() { // Validate input File settings. // verify existence, not a directory, then verify its a valid gdal // format if(this.opts.getInputFile().exists() && !this.opts.getInputFile().isDirectory() && !this.opts.getInputFile().getName().toLowerCase().endsWith("gpkg")) { // check gdal compatibility by grabbing the SRS final SpatialReference srs = GdalUtility .getSpatialReference(this.opts.getInputFile()); if(srs != null) { this.inputAdapter = new RawImageTileStoreAdapter(); return true; } else { this.logger.log(Level.SEVERE, "Error: Input Image File is not a valid GDAL supported format."); return false; } } return false; } /** * validates gpkg inputs * * @return true false if gpgk input is valid */ private boolean isGPKGInputValid() { if(this.opts.getInputFile().getName().toLowerCase() .endsWith("gpkg")) { if(this.opts.getTileSetNameIn().isEmpty()) { this.logger.log(Level.INFO, "Error: No input TileSet Name specified."); return false; } else { try(GeoPackage gpkg = new GeoPackage(this.opts.getInputFile())) { if(gpkg.tiles().getTileSets().stream() .anyMatch(tileSet -> tileSet.getTableName().equals(this.opts.getTileSetNameIn()))) { this.inputAdapter = new GPKGTileStoreAdapter(); return true; } else { this.logger.log(Level.INFO, "Error: Input Tileset not found."); } } catch(final SQLException | ClassNotFoundException | IOException | ConformanceException sqlError) { this.logger.log(Level.SEVERE, "Error: Exception while parsing gpkg input file." + sqlError.getMessage()); } } } return false; } /** * validates input file for tms structure * note: we dont actually read the images for TMS * * @return true false if tms input is valid */ private boolean isTMSInputValid() { if(this.opts.getInputFile().isDirectory() && this.opts.getInputFile().length() > 0L) //non-emtpy directory. { this.inputAdapter = new TMSTileStoreAdapter(); return true; } return false; } /** * Validates the output File Path to determine A) output type and B) verify * that a valid output file path has been supplied. Also sets the default * tileSet name if not set via command line. * * @return boolean validity of output file. */ private boolean isOutputFileValid() { if(this.opts.getOutputFile() == null) { this.logger.log(Level.SEVERE, "Error: No Output File Provided."); return false; } final boolean valid = this.isGPKGOutputValid() || this.isTMSOutputValid(); if(!valid) { this.logger.log(Level.SEVERE, "Error: Could not validate output File."); } return valid; } /** * checks if the output is a geopackage, and if so if its valid * * @return true false geopackage output valid */ private boolean isGPKGOutputValid() { if(this.opts.getOutputFile().getName().toLowerCase().endsWith(".gpkg")) { if(this.opts.getTileSetNameOut().isEmpty()) { this.logger.log(Level.INFO, "Warning: Tileset name not provided, defaulting to file short name."); this.opts.setTileSetNameOut(FileUtility .nameWithoutExtension(this.opts.getOutputFile())); } if(this.opts.getOutputFile().exists()) { try(GeoPackage gpkg = new GeoPackage(this.opts.getOutputFile())) { if(!gpkg.tiles().getTileSets().stream() .anyMatch(tileSet -> tileSet.getTableName().equals(this.opts.getTileSetNameOut()))) { this.outputAdapter = new GPKGTileStoreAdapter(); return true; } } catch(final SQLException | ClassNotFoundException | IOException | ConformanceException sqlError) { this.logger.log(Level.SEVERE, "Error: Exception while parsing gpkg output file." + sqlError.getMessage()); } } else //file doesnt exist, we can make a new one. { this.outputAdapter = new GPKGTileStoreAdapter(); return true; } } return false; } /** * checks if the output could be a valid TMS tile. * * @return true false tms output is valid */ private boolean isTMSOutputValid() { try { if(this.opts.getOutputFile().exists()) { if(this.opts.getOutputFile().isDirectory() && this.opts.getOutputFile().list().length == 0) // existing, empty directory { this.outputAdapter = new TMSTileStoreAdapter(); return true; } return false; } else { //non-existent, anything but .gpkg is a go if(!this.opts.getOutputFile().getName().toLowerCase().endsWith(".gpkg")) { this.outputAdapter = new TMSTileStoreAdapter(); return true; } return false; } } catch(final RuntimeException error) { this.logger.log(Level.SEVERE, "Error: Exception while validating TMS output. " + error.getMessage()); return false; } } }