All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tinfour.svm.properties.SvmProperties Maven / Gradle / Ivy

Go to download

The Simple Volumetric Model (SVM) uses bathymetry to estimate the capacity of lakes and reservoirs

There is a newer version: 2.1.7
Show newest version
/* --------------------------------------------------------------------
 * Copyright (C) 2019  Gary W. Lucas.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ---------------------------------------------------------------------
 */

 /*
 * -----------------------------------------------------------------------
 *
 * Revision History:
 * Date     Name         Description
 * ------   ---------    -------------------------------------------------
 * 04/2019  G. Lucas     Created  
 *
 * Notes:
 *
 * -----------------------------------------------------------------------
 */
package org.tinfour.svm.properties;

import java.awt.Dimension;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.Set;

/**
 * Provides parameter and resource specifications for a SVM analysis run.
 */
public class SvmProperties {

  private final static String unitOfDistanceKey = "unitOfDistance";
  private final static String unitOfAreaKey = "unitOfArea";
  private final static String unitOfVolumeKey = "unitOfVolume";
  private final static String reportKey = "report";
  private final static String shorelineReferenceElevationKey = "shorelineReferenceElevation";
  private final static String tableKey = "table";
  private final static String tableIntervalKey = "tableInterval";
  private final static String flatFixerKey = "remediateFlatTriangles";
  private final static String soundingSpacingKey = "computeSoundingSpacing";
  private final static String inputFolderKey = "inputFolder";
  private final static String outputFolderKey = "outputFolder";
  
  private final static String gridFileName = "rasterFileName";
  private final static String gridCellSize = "rasterCellSize";
  
  private final static String capacityGraphFileKey  = "capacityGraphFileName";
  private final static String capacityGraphSizeKey  = "capacityGraphSize";
  private final static String capacityGraphTitleKey = "capacityGraphTitle";
  
  private final static String contourGraphFileKey = "contourGraphFileName";
  private final static String contourGraphSizeKey = "contourGraphSize";
  private final static String contourGraphLegendTextKey = "contourGraphLegendText";
  private final static String contourGraphIntervalKey = "contourGraphInterval";

  final Properties properties = new Properties();
  final List keyList = new ArrayList<>();
  private File specificationFile;
  private SvmUnitSpecification unitOfDistance;
  private SvmUnitSpecification unitOfArea;
  private SvmUnitSpecification unitOfVolume;

  /**
   * Standard constructor
   */
  public SvmProperties() {
    unitOfDistance = new SvmUnitSpecification("Distance", "m", 1.0);
    unitOfArea = new SvmUnitSpecification("Area", "m^2", 1.0);
    unitOfVolume = new SvmUnitSpecification("Volume", "m^3", 1.0);
  }

  /**
   * Checks to see if an argument list includes the specified target and
   * returns its index within the argument array.
   * @param args a valid list of strings
   * @param target the desired target string
   * @param valueRequired indicates that the target argument requires
   * a value specification.
   * @return if found, a value of zero or greater; otherwise, -1.
   */
  static public int indexArg(String[] args, String target, boolean valueRequired) {
    if(args == null || target==null || target.isEmpty()){
      return -1;
    }
    for (int i = 0; i < args.length; i++) {
      if (target.equalsIgnoreCase(args[i])) {
        if (valueRequired) {
          if (i == args.length - 1 || args[i + 1].isEmpty()) {
            throw new IllegalArgumentException(
                    "Missing value for option " + target);
          }
          String s = args[i + 1];
          if (s.charAt(0) == '-') {
            // this could be an option or a negative number
            if (s.length() > 1 && !Character.isDigit(s.charAt(1))) {
              throw new IllegalArgumentException(
                      "Missing value for option " + target);
            }
          }
        }
        return i;
      }
    }
    return -1;
  }
 
  
  private static String findArgString(String[] args, String target) {
    int index = indexArg(args, target, true);
    if (index >= 0) {
      return args[index + 1];
    }
    return null;
  }

  /**
   * Loads a properties instance using values and paths specified through the
   * command-line arguments.
   *
   * @param args a valid, potentially zero-length array of strings
   * @return if successful, a populated instance
   * @throws IOException in the event of a unrecoverable I/O condition.
   */
  static public SvmProperties load(String[] args) throws IOException {
    String s = findArgString(args, "-properties");
    if (s == null) {
      if (args.length == 1) {
        String test = args[0].toLowerCase();
        int i = test.lastIndexOf(".properties");
        if (i == test.length() - 11) {
          s = args[0];
        }
      }
      if (s == null) {
        throw new IllegalArgumentException(
                "Missing properties file specification");
      }
    }
    File file = new File(s);
    SvmProperties p = new SvmProperties();
    p.load(file);
    return p;
  }

  /**
   * Loads a properties instance using values and paths specified in the
   * indicated file.
   *
   * @param file a valid file instance
   * @throws IOException in the event of an unrecoverable I/O condition.
   */
  public void load(File file) throws IOException {
    specificationFile = file;
    try (FileInputStream fins = new FileInputStream(file);
            BufferedInputStream bins = new BufferedInputStream(fins)) {
      properties.load(bins);
    }

    Set nset = properties.stringPropertyNames();
    for (String s : nset) {
      keyList.add(s);
    }
    Collections.sort(keyList);

    unitOfDistance = extractUnit("Distance", unitOfDistanceKey, unitOfDistance);
    unitOfArea = extractUnit("Area", unitOfAreaKey, getUnitOfArea());
    unitOfVolume = extractUnit("Volume", unitOfVolumeKey, getUnitOfVolume());

  }

  /**
   * Get a list of the SVM file specifications for input samples. The
   * SvmFileSpecification may include metadata such as DBF file field name and
   * unit conversion values.
   *
   * @return a valid, potentially empty list
   */
  public List getSampleSpecifications() {
    return getTargetSpecifications("samples");
  }

  /**
   * Get a list of the SVM file specifications for supplemental samples.
   * Typically these are samples derived for shallow-water areas. The
   * SvmFileSpecification may include metadata such as DBF file field name and
   * unit conversion values.
   *
   * @return a valid, potentially empty list
   */
  public List getSupplementSpecifications() {
    return getTargetSpecifications("supplement");
  }

  /**
   * Get a list of the SVM file specifications for teh polygon bounding
   * constraints of the body of water. The SvmFileSpecification may include
   * metadata such as DBF file field name and unit conversion values.
   *
   * @return a valid, potentially empty list
   */
  public List getBoundarySpecifications() {
    return getTargetSpecifications("bounds");
  }

  /**
   * Get the a description and potential conversion factor unit to be used for
   * specifying horizontal and vertical distances. Note that the SVM model
   * assumes uniform metrics in all directions.
   *
   * @return a valid unit of distance instance.
   */
  public SvmUnitSpecification getUnitOfDistance() {
    return unitOfDistance;
  }

  private SvmUnitSpecification extractUnit(
          String name,
          String key,
          SvmUnitSpecification defaultUnit) {
    String value = properties.getProperty(key);
    if (value == null) {
      return defaultUnit;
    }
    List splitList = split(value);
    if (splitList.isEmpty()) {
      throw new IllegalArgumentException("Missing specification for " + key);
    }
    String label = splitList.get(0);
    double scaleFactor = 1.0;
    if (splitList.size() > 1) {

      try {
        scaleFactor = Double.parseDouble(splitList.get(1));
      } catch (NumberFormatException nex) {
        throw new IllegalArgumentException(
                "Invalid specification for " + key);
      }
      if (scaleFactor == 0) {
        throw new IllegalArgumentException(
                "Zero scaling factor not allowed for " + key);
      }
    }
    return new SvmUnitSpecification(name, label, scaleFactor);
  }

  private File extractFile(String folderKey, String property) {
    if (property == null || property.isEmpty()) {
      return null;
    }
    File f = new File(property);
    File folder = getFolderForKey(folderKey);
    if (folder != null && !f.isAbsolute()) {
      return new File(folder, property);
    }
    return f;
  }

  private List getTargetSpecifications(String target) {
    File folder = getInputFolder();
    List specList = new ArrayList<>();
    for (String key : keyList) {
      if (key.startsWith(target)) {
        String value = properties.getProperty(key);
        List splitList = split(value);
        specList.add(new SvmFileSpecification(key, splitList, folder));
      }
    }
    return specList;
  }

  private List split(String value) {
    List splitList = new ArrayList<>();
    if (value == null || value.isEmpty()) {
      return splitList;
    }
    StringBuilder sb = new StringBuilder();
    boolean spacePending = false;
    boolean spaceEnable = false;
    for (int i = 0; i < value.length(); i++) {
      char c = value.charAt(i);
      if (Character.isWhitespace(c)) {
        if (spaceEnable) {
          spacePending = true;
        }
        spaceEnable = false;
      } else if (c == '|') {
        // delimiter
        String s = sb.toString();
        sb.setLength(0);
        splitList.add(s);
        spaceEnable = false;
        spacePending = false;
      } else {
        if (spacePending) {
          sb.append(' ');
          spacePending = false;
        }
        spaceEnable = true;
        sb.append(c);
      }
    }
    if (sb.length() > 0) {
      splitList.add(sb.toString());
    }

    return splitList;
  }

  /**
   * Gets the input folder specification from the properties; 
   * if the properties do not  contain a key/value for inputFolder,
   * a null is returned.
   *
   * @return a valid instance; or a null if a folder is not specified.
   */
  public File getInputFolder() {
    return getFolderForKey(inputFolderKey);
  }
  
  /**
   * Gets the output folder specification from the properties; if the properties
   * do not contain a key/value for outputFolder, a null is returned.
   *
   * @return a valid instance; or a null if a folder is not specified.
   */
  public File getOutputFolder() {
    return getFolderForKey(outputFolderKey);
  }

  
    /**
   * Gets a folder specification from the properties for the named key;
   * if the properties do not contain the key,
   * a null is returned.
   *
   * @return a valid instance; or a null if a key is not specified.
   */
  private File getFolderForKey(String key) {
    String s = properties.getProperty(key);
    if (s == null) {
      return null;
    }
    return new File(s);
  }

  /**
   * Get the path to a file for writing an output report giving a record of the
   * computation parameters and results used for running the Simple Volumetric
   * Model. If not specified, a null value is returned and it is assumed that the
   * report should be written to standard output.
   *
   * @return a valid File instance or a null if not specified.
   */
  public File getReportFile() {
    return extractFile(outputFolderKey, properties.getProperty(reportKey));
  }

  /**
   * Get the path to a file for writing an output table giving volume and
   * elevation at fixed intervals (specified by getTableInterval()). If not
   * specified, a null value is returned and it is assumed that the table should
   * be written to standard output.
   *
   * @return a valid File instance or a null if not specified.
   */
  public File getTableFile() {
    return extractFile(outputFolderKey, properties.getProperty(tableKey));
  }

  /**
   * Get the interval for computing a series of surface elevation values to be
   * used for modeling. By default, the value for this specification is 1 unit
   * (1 foot, 1 meter, etc.), but fractional values are allowed.
   *
   * @return a floating point value greater than 0.
   */
  public double getTableInterval() {
    String s = properties.getProperty(tableIntervalKey, "1.0");

    try {
      double d = Double.parseDouble(s);
      if (d <= 0) {
        throw new IllegalArgumentException(
                "Invalid value for table interval: " + s);
      }
      return d;
    } catch (NumberFormatException nex) {
      throw new IllegalArgumentException(
              "Invalid numeric for table interval: " + s);
    }
  }

  /**
   * Indicates whether the near-shore, flat-triangle remediation is enabled.
   *
   * @return true if remediation is to be performed; otherwise, false.
   */
  public boolean isFlatFixerEnabled() {
    String s = properties.getProperty(flatFixerKey, "false");
    boolean test = Boolean.parseBoolean(s.trim());
    return test;
  }
  
   /**
   * Indicates whether the computation of sounding spacing is enabled.
   *
   * @return true if computation is to be performed; otherwise, false.
   */
  public boolean isSoundingSpacingEnabled() {
    String s = properties.getProperty(soundingSpacingKey, "false");
    boolean test = Boolean.parseBoolean(s.trim());
    return test;
  }


  private int findMaxNameLength(int m0, List samples) {
    int m = m0;
    for (SvmFileSpecification sample : samples) {
      String path = sample.file.getName();
      if (path.length() > m) {
        m = path.length();
      }
    }
    return m;
  }

  private void writeFileList(
          PrintStream ps,
          List samples,
          String nameFmt) {
    if (samples.size() == 0) {
      ps.println("   None");
    } else {
      for (int i = 0; i < samples.size(); i++) {
        SvmFileSpecification sample = samples.get(i);
        String name = sample.file.getName();
        ps.format(nameFmt, i + 1, name);
        if (sample.field == null) {
          ps.format("%n");
        } else {
          ps.format("   (%s)%n", sample.field);
        }
      }
    }
  }

  private void writeUnit(PrintStream ps, String name, SvmUnitSpecification s) {
    double f = s.getScaleFactor();
    String fStr = "";
    if (f != 1.0) {
      if (f - Math.floor(+1.0e-5) == 0) {
        // integral value
        fStr = String.format("   %8d", (long) f);
      } else {
        fStr = String.format("   %11.3f", f);
      }
    }
    ps.format("   %-12s%-12s%s%n", name, s.getLabel(), fStr);
  }

  /**
   * Writes summary data to the specified PrintStream instance. Note that the
   * input to this method is often, but not always, System.out.
   *
   * @param ps a valid instance
   */
  public void writeSummary(PrintStream ps) {
    ps.format("Specifications for processing%n");
    if (specificationFile != null) {
      ps.format("Properties file: %s%n", specificationFile.getPath());
    }
    File f = getInputFolder();
    ps.format("Input folder:   %s%n", f == null ? "Not specified" : f.getPath());
    f = getOutputFolder();
    ps.format("Output folder:  %s%n", f == null ? "Not specified" : f.getPath());
    ps.format("%n");
    List samples = getSampleSpecifications();
    List bounds = getBoundarySpecifications();
    List supplements = getSupplementSpecifications();
    int m = findMaxNameLength(0, samples);
    m = findMaxNameLength(m, bounds);
    String nameFmt = "   %2d. %-" + m + "s";
    ps.format("Sample Files:%n");
    writeFileList(ps, samples, nameFmt);
    ps.format("Boundry Files:%n");
    writeFileList(ps, bounds, nameFmt);
    ps.format("Supplemental Sample Files:%n");
    writeFileList(ps, supplements, nameFmt);
    ps.format("%nUnits of Measure%n");
    writeUnit(ps, "Distance", unitOfDistance);
    writeUnit(ps, "Area:", getUnitOfArea());
    writeUnit(ps, "Volume:", getUnitOfVolume());
    ps.println("");
    String s = properties.getProperty(reportKey);
    ps.format("Report:                   %s%n", s == null || s.isEmpty() ? "None" : s);
    s = properties.getProperty(tableKey);
    ps.format("Table output:             %s%n", s == null || s.isEmpty() ? "None" : s);
    if (s != null && !s.isEmpty()) {
      ps.format("Table interval:           %4.2f%n", getTableInterval());
    }
    s = properties.getProperty(shorelineReferenceElevationKey);
    ps.format("Shoreline Elevation:      ");
    if(s ==null || s.isEmpty()){
      ps.format("To be obtained from boundary data");
    }else{
      ps.format("Explicitly specified as "+s);
    }
    ps.format("%n");
    boolean fixFlats = isFlatFixerEnabled();
    ps.format("Remediate Flat Triangles: %s%n", Boolean.toString(fixFlats));
  }

  /**
   * Get the a description and potential conversion factor unit to be used for
   * specifying surface area.
   *
   * @return a valid unit specification instance.
   */
  public SvmUnitSpecification getUnitOfArea() {
    return unitOfArea;
  }

  /**
   * Get the a description and potential conversion factor unit to be used for
   * specifying volume.
   *
   * @return a valid unit specification instance.
   */
  public SvmUnitSpecification getUnitOfVolume() {
    return unitOfVolume;
  }
  
  
  
  /**
   * Get the shoreline reference elevation, if provided.
   * @return a valid floating point number or a NaN if undefined
   */
  public double getShorelineReferenceElevation( ) {
    String s = properties.getProperty(shorelineReferenceElevationKey);
    if (s == null) {
      return Double.NaN;
    }
    s = s.trim();
    if (s.isEmpty()) {
      return Double.NaN;
    }

    try {
      return Double.parseDouble(s);
    } catch (NumberFormatException nex) {
      throw new IllegalArgumentException(
              "Invalid numeric for shoreline reference elevation: " + s);
    }
  }
  
   /**
   * Get the grid cell size, if provided.
   * @return a valid floating point number or a NaN if undefined
   */
  public double getGridCellSize( ) {
    String s = properties.getProperty(gridCellSize);
    if (s == null) {
      return Double.NaN;
    }
    s = s.trim();
    if (s.isEmpty()) {
      return Double.NaN;
    }

    try {
      double d = Double.parseDouble(s);
      if (d <= 0) {
        throw new IllegalArgumentException(
                "Invalid value for grid cell size: " + s);
      }
      return d;
    } catch (NumberFormatException nex) {
      throw new IllegalArgumentException(
              "Invalid numeric for gridCellSize: " + s);
    }
  }
  
  /**
   * Get the path to a file for writing an output ASC file giving an
   * interpolated grid of water bottom elevations.
   *
   * @return a valid File instance or a null if not specified.
   */
  public File getGridFile() {
    return extractFile(outputFolderKey, properties.getProperty(gridFileName));
  }

  
  /**
   * Get the path to a file for writing a graph of the capacity as a function of
   * water level. .
   *
   * @return a valid File instance or a null if not specified.
   */
  public File getCapacityGraphFile() {
    if (properties.containsKey(capacityGraphFileKey)) {
      return extractFile(outputFolderKey, properties.getProperty(capacityGraphFileKey));
    }
    return null;
  }
  
  
  /**
   * Get the dimensions for the capacity graph image file.
   * @return a valid instance of non-trivial size.
   */
  public Dimension getCapacityGraphDimensions(){
     return extractDimension(capacityGraphSizeKey, 650,400);  
  }

  
  /**
   * Gets the title for the capacity graph image
   * @return if defined, a valid non-empty string instance;
   * if undefined, a null.
   */
  public String getCapacityGraphTitle(){
    String s = properties.getProperty(capacityGraphTitleKey);
    if(s==null || s.trim().isEmpty()){
        return null;
    }
    return s.trim();
  }
  
    
  /**
   * Get the path to a file for writing an image file showing a contour
   * plot of the reservoir data.
   * @return a valid File instance or a null if not specified.
   */
  public File getContourGraphFile() {
    if (properties.containsKey(contourGraphFileKey)) {
      return extractFile(outputFolderKey, properties.getProperty(contourGraphFileKey));
    }
    return null;
  }
  
  
  /**
   * Get the dimensions for the contour graph image file.
   * @return a valid instance of non-trivial size.
   */
  public Dimension getContourGraphDimensions(){
     return extractDimension(contourGraphSizeKey, 650, 650);  
  }
  
    /**
   * Gets the title for the capacity graph image
   * @return if defined, a valid non-empty string instance;
   * if undefined, a null.
   */
  public String getContourGraphLegendText(){
    String s = properties.getProperty(contourGraphLegendTextKey);
    if(s==null || s.trim().isEmpty()){
        return "Legend";
    }
    return s.trim();
  }
  
  /**
   * Gets the contour graph interval
   * @return if defined, a floating point value greater than zero;
   * if undefined, zero.
   */
  public double getContourGraphInterval() {
    String s = properties.getProperty(contourGraphIntervalKey);
    if (s == null || s.trim().isEmpty()) {
      return 0;
    }
    s = s.trim();
    if("automatic".equalsIgnoreCase(s)){
      return 0;
    }
    try {
      double d = Double.parseDouble(s);
      if (d <= 0) {
        return 0;
      }
      return d;
    } catch (NumberFormatException nfe) {
      return 0;
    }
  }

  
  
  
  private Dimension extractDimension(String key, int width, int height) {
    String s = properties.getProperty(key);
    if (s == null) {
      return new Dimension(width, height);
    }
 
    int[] values = new int[2];
    int nNumeric = 0;
    int value = 0;
    boolean numeric = false;
    for (int i = 0; i < s.length(); i++) {
      char c = s.charAt(i);
      if (Character.isDigit(c)) {
        if (!numeric) {
          value = 0;
          numeric = true;
        }
        value = value * 10 + ((int) c - 48);
      } else if (numeric) {
        numeric = false;
        values[nNumeric++] = value;
        if (nNumeric == 2) {
          // we've completed the second numeric
          return new Dimension(values[0], values[1]);
        }
      }
    }

    if (numeric) {
      values[nNumeric++] = value;
      if (nNumeric == 2) {
        // we've completed the second numeric
        return new Dimension(values[0], values[1]);
      }
    }
    throw new IllegalArgumentException(
            "Incomplete specification for dimension: " + key + "=" + s);
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy