Skip to content

Commit

Permalink
Issue 102: SVM support for non-English Locale
Browse files Browse the repository at this point in the history
  • Loading branch information
gwlucastrig committed Dec 19, 2023
1 parent 42a73ad commit 44a31d8
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 33 deletions.
5 changes: 5 additions & 0 deletions svm/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
<description>The Simple Volumetric Model (SVM) uses bathymetry to
estimate the capacity of lakes and reservoirs</description>
<dependencies>
<dependency>
<groupId>com.github.mreutegg</groupId>
<artifactId>laszip4j</artifactId>
<version>0.11</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
4 changes: 4 additions & 0 deletions svm/src/main/java/org/tinfour/svm/SvmBathymetryData.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.tinfour.common.Vertex;
import org.tinfour.gis.utils.ConstraintReaderShapefile;
import org.tinfour.gis.utils.IVerticalCoordinateTransform;
import org.tinfour.gis.utils.VertexReaderLas;
import org.tinfour.gis.utils.VertexReaderShapefile;
import org.tinfour.utils.HilbertSort;
import org.tinfour.utils.Tincalc;
Expand Down Expand Up @@ -127,6 +128,9 @@ private List<Vertex> loadVertices(
vls.setDbfFieldForZ(dbfBathymetryField);
vls.setVerticalCoordinateTransform(verticalTransform);
list = vls.read(null);
} else if("las".equalsIgnoreCase(extension) || "laz".equalsIgnoreCase("laz")){
VertexReaderLas reader = new VertexReaderLas(vertexFile);
list = reader.read(null);
} else {
throw new IllegalArgumentException("Unsupported file format "
+ extension
Expand Down
75 changes: 55 additions & 20 deletions svm/src/main/java/org/tinfour/svm/SvmComputation.java
Original file line number Diff line number Diff line change
Expand Up @@ -435,25 +435,34 @@ public void processVolume(
Rectangle2D bounds = data.getBounds();

ps.format("%nData from Shapefiles --------------------------------------------------------------%n");
ps.format(" Lake area %,18.2f %s%n", lakeArea, areaUnits);
ps.format(" Island area %,18.2f %s%n", islandArea, areaUnits);
ps.format(" Net area (water) %,18.2f %s%n", netArea, areaUnits);
ps.format(" Lake shoreline %,18.2f %s%n", lakePerimeter, lengthUnits);
ps.format(" Island shoreline %,18.2f %s%n", islandPerimeter, lengthUnits);
ps.format(" Total shoreline %,18.2f %s%n", totalShore, lengthUnits);
if (properties.doesLocaleUseCommaForDecimal()) {
ps.format(" Lake area %18.2f %s%n", lakeArea, areaUnits);
ps.format(" Island area %18.2f %s%n", islandArea, areaUnits);
ps.format(" Net area (water) %18.2f %s%n", netArea, areaUnits);
ps.format(" Lake shoreline %18.2f %s%n", lakePerimeter, lengthUnits);
ps.format(" Island shoreline %18.2f %s%n", islandPerimeter, lengthUnits);
ps.format(" Total shoreline %18.2f %s%n", totalShore, lengthUnits);
} else {
ps.format(" Lake area %,18.2f %s%n", lakeArea, areaUnits);
ps.format(" Island area %,18.2f %s%n", islandArea, areaUnits);
ps.format(" Net area (water) %,18.2f %s%n", netArea, areaUnits);
ps.format(" Lake shoreline %,18.2f %s%n", lakePerimeter, lengthUnits);
ps.format(" Island shoreline %,18.2f %s%n", islandPerimeter, lengthUnits);
ps.format(" Total shoreline %,18.2f %s%n", totalShore, lengthUnits);
}
ps.format(" N Islands %18d%n", islandConstraints.size());
ps.format(" N Soundings %18d%n", data.getSoundings().size());
ps.format(" N Supplements %18d%n", data.getSupplements().size());
ps.format(" Bounds%n");
ps.format(" x: %12.3f, %12.3f, (%5.3f)%n",
ps.format(" x: %12.3f to %12.3f, (%5.3f)%n",
bounds.getMinX() / lengthFactor,
bounds.getMaxX() / lengthFactor,
bounds.getWidth() / lengthFactor);
ps.format(" y: %12.3f, %12.3f, (%5.3f)%n",
ps.format(" y: %12.3f to %12.3f, (%5.3f)%n",
bounds.getMinY() / lengthFactor,
bounds.getMaxY() / lengthFactor,
bounds.getHeight() / lengthFactor);
ps.format(" z: %12.3f, %12.3f, (%5.3f)%n",
ps.format(" z: %12.3f to %12.3f, (%5.3f)%n",
data.getMinZ() / lengthFactor,
data.getMaxZ() / lengthFactor,
(data.getMaxZ() - data.getMinZ()) / lengthFactor);
Expand Down Expand Up @@ -496,16 +505,29 @@ public void processVolume(
double rawFlatArea = lakeConsumer.getFlatArea();
double flatArea = lakeConsumer.getFlatArea() / areaFactor;


ps.format("%nComputations from Constrained Delaunay Triangulation -----------------------------%n");
ps.format(" Volume %,18.2f %s %,28.1f %s^3%n",
volume, volumeUnits, rawVolume, lengthUnits);
ps.format(" Surface Area %,18.2f %s %,28.1f %s^2%n",
surfArea, areaUnits, rawSurfArea, lengthUnits);
ps.format(" Flat Area %,18.2f %s %,28.1f %s^2%n",
flatArea, areaUnits, rawFlatArea, lengthUnits);
ps.format(" Avg depth %,18.2f %s%n", avgDepth, lengthUnits);
ps.format(" Adj mean depth %,18.2f %s%n", adjMeanDepth, lengthUnits);
ps.format(" Mean Vertex Spacing %,18.2f %s%n", vertexSpacing, lengthUnits);
if (properties.doesLocaleUseCommaForDecimal()) {
ps.format(" Volume %18.2f %s %28.1f %s^3%n",
volume, volumeUnits, rawVolume, lengthUnits);
ps.format(" Surface Area %18.2f %s %28.1f %s^2%n",
surfArea, areaUnits, rawSurfArea, lengthUnits);
ps.format(" Flat Area %18.2f %s %28.1f %s^2%n",
flatArea, areaUnits, rawFlatArea, lengthUnits);
ps.format(" Avg depth %18.2f %s%n", avgDepth, lengthUnits);
ps.format(" Adj mean depth %18.2f %s%n", adjMeanDepth, lengthUnits);
ps.format(" Mean Vertex Spacing %18.2f %s%n", vertexSpacing, lengthUnits);
} else {
ps.format(" Volume %,18.2f %s %,28.1f %s^3%n",
volume, volumeUnits, rawVolume, lengthUnits);
ps.format(" Surface Area %,18.2f %s %,28.1f %s^2%n",
surfArea, areaUnits, rawSurfArea, lengthUnits);
ps.format(" Flat Area %,18.2f %s %,28.1f %s^2%n",
flatArea, areaUnits, rawFlatArea, lengthUnits);
ps.format(" Avg depth %,18.2f %s%n", avgDepth, lengthUnits);
ps.format(" Adj mean depth %,18.2f %s%n", adjMeanDepth, lengthUnits);
ps.format(" Mean Vertex Spacing %,18.2f %s%n", vertexSpacing, lengthUnits);
}
ps.format(" N Triangles %15d%n", lakeConsumer.nTriangles);
ps.format(" N Flat Triangles %15d%n", lakeConsumer.nFlatTriangles);

Expand Down Expand Up @@ -534,12 +556,25 @@ public void processVolume(

File tableFile = properties.getTableFile();
if (tableFile != null) {
String tableFileName = tableFile.getName();
boolean csvFlag = tableFileName.toLowerCase().endsWith(".csv");
if(csvFlag && properties.doesLocaleUseCommaForDecimal()){
System.out.println("\nNote: Using CSV file for table output may conflict with formatting specified by Locale\n");
}

try (FileOutputStream tableOutputStream = new FileOutputStream(tableFile);
BufferedOutputStream bos = new BufferedOutputStream(tableOutputStream);
PrintStream ts = new PrintStream(bos, true, "UTF-8");) {
ts.println("Elevation, Area, Volume, Percent_Capacity");
String lineFormat;
if (csvFlag) {
ts.println("Elevation, Area, Volume, Percent_Capacity");
lineFormat = "%12.3f, %12.3f, %12.3f, %6.2f%n";
} else {
ts.println("Elevation\tArea\tVolume\tPercent_Capacity");
lineFormat = "%12.3f\t%12.3f\t%12.3f\t%6.2f%n";
}
for (AreaVolumeResult result : resultList) {
ts.format("%12.3f, %12.3f, %12.3f, %6.2f%n",
ts.format(lineFormat,
result.level,
result.area / areaFactor,
result.volume / volumeFactor,
Expand Down
11 changes: 8 additions & 3 deletions svm/src/main/java/org/tinfour/svm/SvmMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ public boolean accept(File file, String name) {

private static void printLicenseAndDisclaimer(PrintStream ps){
try (
InputStream ins
= SvmMain.class.getResourceAsStream("LicenseAndDisclaimer.txt");) {
InputStream ins = SvmMain.class.getResourceAsStream("LicenseAndDisclaimer.txt");)
{
int c;
while ((c = ins.read()) >= 0) {
ps.append((char) c);
Expand All @@ -182,14 +182,19 @@ private SvmMain() {
* @throws IOException in the event of an unrecoverable I/O condition.
*/
public static void main(String[] args) throws IOException {

checkForUsage(args);
checkForTemplate(args);
checkForInspection(args);

printLicenseAndDisclaimer(System.out);

// When the properties are loaded, they will set the default Locale
// if it is explicitly included in the properties file.
SvmProperties prop = SvmProperties.load(args);

Date dateOfAnalysis = new Date(); // set to clock time
writeIntroduction(System.out, dateOfAnalysis);
SvmProperties prop = SvmProperties.load(args);
prop.writeSummary(System.out);

SvmBathymetryModel bathymetryModel = null;
Expand Down
15 changes: 11 additions & 4 deletions svm/src/main/java/org/tinfour/svm/SvmRaster.java
Original file line number Diff line number Diff line change
Expand Up @@ -355,10 +355,17 @@ void buildAndWriteRaster(
double volume = rawVolume / volumeFactor;

ps.format("%nComputations from Raster Methods%n");
ps.format(" Volume %,18.2f %s %,28.1f %s^3%n",
volume, volumeUnits, rawVolume, lengthUnits);
ps.format(" Surface Area %,18.2f %s %,28.1f %s^2%n",
surfArea, areaUnits, rawSurfArea, lengthUnits);
if (properties.doesLocaleUseCommaForDecimal()) {
ps.format(" Volume %18.2f %s %28.1f %s^3%n",
volume, volumeUnits, rawVolume, lengthUnits);
ps.format(" Surface Area %18.2f %s %28.1f %s^2%n",
surfArea, areaUnits, rawSurfArea, lengthUnits);
} else {
ps.format(" Volume %,18.2f %s %,28.1f %s^3%n",
volume, volumeUnits, rawVolume, lengthUnits);
ps.format(" Surface Area %,18.2f %s %,28.1f %s^2%n",
surfArea, areaUnits, rawSurfArea, lengthUnits);
}
ps.format(" Percent Covered %4.1f%%%n", 100.0 * nCovered / (double) nCells);
ps.format(" Percent Uncovered %4.1f%%%n", 100.0 * nUncovered / (double) nCells);
ps.println("");
Expand Down
75 changes: 71 additions & 4 deletions svm/src/main/java/org/tinfour/svm/properties/SvmProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.Set;
import org.tinfour.svm.SvmBathymetryModel;
Expand Down Expand Up @@ -88,13 +89,22 @@ public class SvmProperties {
private SvmUnitSpecification unitOfArea;
private SvmUnitSpecification unitOfVolume;

private Locale explicitLocale;
private boolean usesCommaForDecimal;

/**
* 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);

// see how the Locale formats a string
String qf = String.format("%f", 3.14);
if (qf.indexOf(',') > 0) {
usesCommaForDecimal = true;
}
}

/**
Expand All @@ -118,13 +128,16 @@ static public int indexArg(String[] args, String target, boolean valueRequired)
throw new IllegalArgumentException(
"Missing value for option " + target);
}
// there is another string in the args array, and it may be
// a candidate for the required value. If it starts with a hyphen
// it could be a negative numeric value (which might be
// wanted by the application), but it could also be another
// option specification (which would mean that the parameter
// for the target option is missing). So we need to check.
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))) {
if (s.charAt(0) == '-' && s.length() > 1 && !Character.isDigit(s.charAt(1))) {
throw new IllegalArgumentException(
"Missing value for option " + target);
}
}
}
return i;
Expand Down Expand Up @@ -170,6 +183,40 @@ static public SvmProperties load(String[] args) throws IOException {
return p;
}

/**
* Checks to see if the properties object contains an explicit setting
* for Locale and, if so, puts it into effect.
* @throws IOException in the event of an ill-formatted or unknown setting.
*/
private void checkForLocaleSettings() throws IOException {
String q = properties.getProperty("Locale");
if(q==null){
q = properties.getProperty("locale");
}

if(q!=null){
// a specific Locale was provided. See if it works
// in this code, we check for an unchecked
// Note that we use an UNCHECKED exception here. When this code was
// developed under Java 8, it was unclear what Java would do in the
// future for handling ill-formed locales.
try{
Locale locale = Locale.forLanguageTag(q);
if(locale == null){
throw new IOException("Unrecognized specification for Locale, \""+q+"\"");
}
Locale.setDefault(locale);
explicitLocale = locale;
String qf = String.format("%f", 3.14);
if(qf.indexOf(',')>0){
usesCommaForDecimal = true;
}
}catch(RuntimeException rex){
throw new IOException("Invalid specification for Locale, \""+q+"\"");
}
}
}

/**
* Loads a properties instance using values and paths specified in the
* indicated file.
Expand All @@ -184,6 +231,8 @@ public void load(File file) throws IOException {
properties.load(bins);
}

checkForLocaleSettings();

Set<String> nset = properties.stringPropertyNames();
for (String s : nset) {
keyList.add(s);
Expand All @@ -193,9 +242,27 @@ public void load(File file) throws IOException {
unitOfDistance = extractUnit("Distance", unitOfDistanceKey, unitOfDistance);
unitOfArea = extractUnit("Area", unitOfAreaKey, getUnitOfArea());
unitOfVolume = extractUnit("Volume", unitOfVolumeKey, getUnitOfVolume());
}


/**
* Gets the explicit value for the locale, if it was set.
* @return if explicitly set, a valid instance; otherwise, a null
*/
public Locale getExplicitLocale(){
return explicitLocale;
}

/**
* Indicates whether the Locale uses the comma character for the
* decimal part of floating-point values.
* This method is used for formatting numeric strings in SVM. Many non-English
* speaking conventions use the comma rather than the period.
* @return true if the Locale uses periods for decimals; otherwise, false.
*/
public boolean doesLocaleUseCommaForDecimal(){
return usesCommaForDecimal;
}
/**
* Get a list of the SVM file specifications for input samples. The
* SvmFileSpecification may include metadata such as DBF file field name and
Expand Down
19 changes: 17 additions & 2 deletions svm/src/main/resources/org/tinfour/svm/SvmTemplate.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
# may be subject to change as the model matures.
#

# SVM was developed in the US. By default, it file output follows the numeric
# formatting conventions specified by the Locale parameters available from the
# Java Runtime Environment (JRE). For example, if the environment is configured to use
# non-English conventions, SVM will use those. If desired, you may explicitly set
# the Java Locale for the current application instance by using the settings below.
#Locale = en-US

bathymetryModel = Elevation

inputFolder = C:\\Users\\Public\\SimpleVolumetricModel\\AlanHenry\\Shapefiles
Expand All @@ -30,8 +37,14 @@ unitOfArea = acres | 43560
unitOfVolume = acre-ft | 43560

report = SVM_AlanHenry_Report.txt
table = SVM_AlanHenry_Table.csv
table = SVM_AlanHenry_Table.txt
tableInterval = 0.1
#table = SVM_AlanHenry_Table.csv
# If the file extension for table is .csv, the output will be written
# as a comma-separated-value file. If it is .txt, the output
# will be written as a tab-separated-value file.
# Both formats are suitable for input into spreadsheets.



capacityGraphFileName = SvmCapacityGraphAlanHenry.png
Expand Down Expand Up @@ -95,6 +108,7 @@ capacityGraphTitle = SVM Analysis for Alan Henry Reservoir
# specifying file names. If they are provided, SVM will seek
# the indicated input files in the inputFolder and will store
# the indicated output files (report and table) to the outputFolder.
#
# Note that if any of the input or output file settings are given
# as absolute paths, the folder specifications will not be applied to them.
#
Expand All @@ -108,7 +122,7 @@ capacityGraphTitle = SVM Analysis for Alan Henry Reservoir
# However, when the output paths for analysis products
# (contours, rasters, and text reports) include sub-folders
# the sub-folders will be automatically created as needed.
#
#
# Input File Settings ----------------------------------------
# The input source for SVM is usually Shapefiles, though the
Expand All @@ -118,6 +132,7 @@ capacityGraphTitle = SVM Analysis for Alan Henry Reservoir
# associated DBF file to be used as an elevation source. The field
# name is given by a "pipe" character folloed by the name of the field.
# The following settings are supported:
#
# samples the primary set of bathymetry (bottom elevation) data points
# supplement the secondary set of bathymetry data points
# bounds the bounding polygons (shoreline) for the body of water
Expand Down

0 comments on commit 44a31d8

Please sign in to comment.