EFH2RainfallCoefficients.java [src/java/m/efh2] Revision: default Date:
package m.efh2;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
/**
* Runoff coefficients used in calculation of qu (unit peak discharge),
* per EFH2 Hydrology practice
*/
public class EFH2RainfallCoefficients {
// NOTE: This is an literal implementation of the lookup table from
// Basic source code, type.rf (data file), now called RfC.txt for clarity
//
private static Map<String, RfCTable> rfcTables = new HashMap<String, RfCTable>(5);
static {
// init from default coefficients file, which is a resource in this package
String resourcePath = "/m/efh2/RfC.txt";
InputStream istream = EFH2RainfallCoefficients.class.getResourceAsStream(resourcePath);
loadTables(istream);
}
/**
* private constructor - use the static methods
*/
private EFH2RainfallCoefficients() {
}
/**
* lookup runoff coeffeicients by storm type and SAPG value
*
* @param stormType stormType, from {I, IA, II, III} or other adapted list
* @param targetSAPG Ia/P value, called SAPG after Basic implementation
* @return List<RfC> records. If size 1, use that record only
* to calc qu; if size 2, caller must use both records & interpolate.
*/
public static List<RfC> getRainfallCoeff(String stormType, double targetSAPG) {
RfCTable table = rfcTables.get(stormType);
if (table == null) {
System.err.println("Caller requested unknown storm type " + stormType);
return new ArrayList<RfC>();
} else {
return table.getRfC(targetSAPG);
}
}
/**
* load rainfall coefficient tables
*
* @param is input stream containing tables in expected format. See RfC.txt.
* @return true if successfully read. If false, this model is unusable.
*/
public static boolean loadTables(InputStream is) {
rfcTables.clear();
byte[] contentBytes = new byte[25000];
boolean readOK = true;
try {
int cc = is.read(contentBytes);
if (cc < 0) {
System.err.println("Failed to read RfC data");
readOK = false;
}
} catch (IOException ex) {
System.err.println("Failed to read RfC data " + ex);
readOK = false;
}
if (readOK) {
String totalContent = new String(contentBytes);
String[] lines = totalContent.split("\n");
int idx = 0;
while (idx < lines.length) {
String line = lines[idx++];
String[] splits = line.split(",");
if (splits.length == 2) {
// get stormType and row count
String stormType = splits[0].trim();
int nRows = Integer.valueOf(splits[1].trim());
RfCTable table = new RfCTable(stormType);
for (int i = 0; i < nRows; i++) {
line = lines[idx++];
splits = line.split(",");
if (splits.length == 4) {
double SAPG = Double.valueOf(splits[0].trim());
double RfC2 = Double.valueOf(splits[1].trim());
double RfC3 = Double.valueOf(splits[2].trim());
double RfC4 = Double.valueOf(splits[3].trim());
RfC rfc = new RfC(SAPG, RfC2, RfC3, RfC4);
table.addRfC(rfc);
} else {
throw new IllegalArgumentException("Improper RfC record: " + line);
}
}
rfcTables.put(stormType, table);
} else {
throw new IllegalArgumentException("Improper start of RfC Table: " + line);
}
}
}
return readOK;
}
/**
* get the list of known storm types
*/
public static List<String> getStormTypes() {
List<String> types = new ArrayList<String>();
types.addAll(rfcTables.keySet());
Collections.sort(types);
return types;
}
//---------------------------------------------------------------------
//
// Inner classes to implement the lookup tables
//
/**
* inner class - one table of Rainfall coefficients, for one storm type
*/
private static class RfCTable {
private String stormType;
private List<RfC> rfcList;
public RfCTable(String stormType) {
this.stormType = stormType;
rfcList = new ArrayList<RfC>();
}
/* package access */
void addRfC(RfC rfc) {
rfcList.add(rfc);
}
public String getStormType() {
return stormType;
}
/**
* get rainfall coefficients for calculating qu
*
* @param targetSAPG SAPG (Ia/P) value desired
* @return List<RfC> records. If size 1, use that record only
* to calc qu; if size 2, caller must use both records & interpolate.
*/
public List<RfC> getRfC(double targetSAPG) {
List<RfC> rfcs = new ArrayList<RfC>();
if (targetSAPG <= 0.1) {
rfcs.add(rfcList.get(0));
} else if (targetSAPG >= 0.5) {
rfcs.add(rfcList.get(rfcList.size() - 1));
} else {
// first scan for approximate match (binary vs decimal #s)
for (RfC rfC : rfcList) {
if (Math.abs(targetSAPG - rfC.getSAPG()) < 0.001) {
// approximately equal
rfcs.add(rfC);
return Collections.unmodifiableList(rfcs);
}
}
// then scan table for bracketing value pair
for (int j = 0, i = 1; i < rfcList.size(); j = i++) {
RfC lower = rfcList.get(j);
RfC upper = rfcList.get(i);
if (lower.getSAPG() < targetSAPG && targetSAPG <= upper.getSAPG()) {
rfcs.add(lower);
rfcs.add(upper);
break;
}
}
}
return Collections.unmodifiableList(rfcs);
}
}
/**
* inner class, representing one row of RFC table
*/
public static class RfC {
/** Ia/P value, called SAPG after the Basic source */
private double SAPG;
/** rainfall coefficients */
private double RfC2, RfC3, RfC4;
// NOTE: Sorry, there is no documented explanation of the rainfall
// coefficients 2, 3, 4
public RfC(double SAPG, double RfC2, double RfC3, double RfC4) {
this.SAPG = SAPG;
this.RfC2 = RfC2;
this.RfC3 = RfC3;
this.RfC4 = RfC4;
}
public double getSAPG() {
return SAPG;
}
public double getRfC2() {
return RfC2;
}
public double getRfC3() {
return RfC3;
}
public double getRfC4() {
return RfC4;
}
}
}