guiRegionalFDC_Model.java [src/java/m/cfa/regionalfdc] Revision: af7abb54df0e36d1d6286c01ca7f49cc039b493d Date: Mon Apr 24 10:47:57 MDT 2017
package m.cfa.regionalfdc;
import m.cfa.DoubleArray;
import m.cfa.DoubleMath;
import m.cfa.Graphing;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Stroke;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.LogarithmicAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
/**
* Last Updated: 20-April-2017
* @author Tyler Wible
* @since 23-October-2011
*/
public class guiRegionalFDC_Model {
//Inputs
String mainFolder = "C:/Projects/TylerWible/CodeDirectories/NetBeans/data/CFA/DurationCurve";
String[] databases = {"Temp_1", "Temp_2"};
String[] stationIDs = {"RiverID_1", "RiverID_2"};
String[] stationNames = {"RiverName_1", "RiverName_2"};
String normalizingMetricName = "Long-Term Average Flow [cfs]";//"Drainage Area [Sq. Mi.]";//
double[] normalizingMetrics = {391.2, 3276.33}; //a list of normalizing metrics (drainage area, Q2, etc.; set by 'setNormalizingMetrics')
double[][] fdcData = {{0,550, 0,4052}, //a matrix of matched columns of flow duration curve exceedences and assosicated flows
{25,380, 20,3706}, //(set by 'setFlowDurationCurves')
{50,376, 40,3214},
{75,350, 60,3191},
{100,300, 80,2995},
{-1,-1, 100,2500}};
//Outputs
String summaryTable = "?";
String regionalFDC = "?";
String units = "?";
//Gets
public File getRegionalFDC_results(){ return new File(mainFolder, "regionalfdc_results.txt"); }
public String getGraph(){ return "regionalfdc_graph.jpg"; }
public File getRegionalFDCgraphOutput(){ return new File(mainFolder, "regionalfdc_graph.out"); }
public String getSummaryTable(){ return summaryTable; }
public String getRegionalFDC(){ return regionalFDC; }
public String getUnits(){ return units; }
//Sets
public void setMainFolder(String mainFolder_str){ mainFolder = mainFolder_str; }
public void setDatabases(String databaseList){ databases = databaseList.split("\t"); }//Break up the inputs per flow duration curve
public void setStationIDs(String stationIDlist){ stationIDs = stationIDlist.split("\t"); }//Break up the inputs per flow duration curve
public void setStationNames(String stationNameList){ stationNames = stationNameList.split("\t"); }//Break up the inputs per flow duration curve
public void setNormalizingMetricName(String metricName){ normalizingMetricName = metricName; }
public void setNormalizingMetrics(String normalizingMetrics_str) throws IOException{
//Break up formatted cross-section points and create a double array for easier use
String[] metricsList = normalizingMetrics_str.split("\t");
//If the list of points is of sufficient size and format, store it
double[] metricsList_new = new double[metricsList.length];
for(int i=0; i<metricsList.length; i++){
metricsList_new[i] = Double.parseDouble(metricsList[i]);
}
normalizingMetrics = metricsList_new;
}
public void setFlowDurationCurves(String flowDurationCurves) throws IOException{
//Break up formatted cross-section points and create a double array for easier use
String[] fdcDataList = flowDurationCurves.split("\n");
if(fdcDataList.length > 1){
//If the list of points is of sufficient size and format, store it
int columnSize = fdcDataList[0].split("\t").length;
double[][] fdcData_new = new double[fdcDataList.length - 1][columnSize];
for(int i=1; i<fdcDataList.length; i++){//Skip headder
String[] fdc_row = fdcDataList[i].split("\t");
for(int j=0; j<fdc_row.length; j++){
fdcData_new[i-1][j] = Double.parseDouble(fdc_row[j]);
}
}
fdcData = fdcData_new;
}else{
//if the list is empty or formatted wrong, thrown an error
ArrayList<String> errorMessage = new ArrayList<>();
errorMessage.add("The list of flow duration curves provided is not in the expected tab-delimited format like: 'fdc_1\tflow_1\tfdc_2\tflow_2\n0\t550\t0\t4052\n25\t380\t20\t3706\n50\t376\t40\t3214\n75\t350\t60\t3191\n100\t300\t80\t2995\n-1\t-1\t100\t2500\n'");
writeError(errorMessage);
}
}
/**
* Calculates a summary table of standardized percentiles based on the data,
* then takes the percentile summary and adds it to the summary table
* @param xyRanks a double[][] array with column1 = x (0-100 percentages), column2 = values
* @param firstEntry a boolean, if true it adds a header and extra column
* to the beginning of the summary table
* @param stationIndex the index of the current station, used to pull the
* proper stationID the header label
* @return a double[] of the flows associated with the current percentile
*/
private void calculatePercentileSummmary(double[][] xyRanks, boolean firstEntry, int stationIndex){
//Hard coded percentile summary table
double[] frqDisp = {99, 95, 90, 75, 50, 25, 10, 5, 1};
//Interpolate desired percentiles
double[] currentFDC = DoubleArray.getColumn(xyRanks, 0);
double[] currentFlows = DoubleArray.getColumn(xyRanks, 1);
double[] currentSummary = DoubleMath.linearInterpolation(currentFDC, currentFlows, frqDisp);
currentSummary = DoubleMath.roundColumn(currentSummary, 3);
//Determine if this is the first entry and should thus include the exceedance percentile summary values
if(firstEntry){
summaryTable = "Exceedance Percentile\t%\t99\t95\t90\t75\t50\t25\t10\t5\t1";
summaryTable = summaryTable + "\nRegional FDC\tDaily Average Flow [cfs]/" + normalizingMetricName;
}else{
summaryTable = summaryTable + databases[stationIndex] + "; " + stationIDs[stationIndex] + "\tDaily Average Flow [cfs]/" + normalizingMetricName;
}
//Append current summary to resultSummary
for(int j=0; j<currentSummary.length; j++){
summaryTable = summaryTable + "\t" + String.valueOf(currentSummary[j]);
}
summaryTable = summaryTable + "\n";
}
/**
* Take the current flow duration curve values and divide them by the 'normalizingMetric'
* and return the result as a double[][] with the second column as the associated flow values
* @param currentFDC a list of flow duration curve excedances (0-100), any "-1" will be ignored
* @param currentFlows a list of flow values associated with the duration curve excedances above
* @param normalizingMetric the value to normalize the flow duration curve by (i.e. drainage area, Q2, or others)
* @return a double[][] with the first column as the 'currentFDC' values divided by the 'normalizingMetric' and
* the second column as the associated flow values 'currentFlows'
*/
private double[][] normalizeFDCdata(double[] currentFDC, double[] currentFlows, double normalizingMetric){
//Divide FDC flows by the normalizingMetric (drainage area, Q2, long-term average, etc)
ArrayList<Double> modifiedFDC = new ArrayList<>();
ArrayList<Double> modifiedFlows = new ArrayList<>();
for(int i=0; i<currentFDC.length; i++){
if(currentFDC[i] != -1){
modifiedFDC.add(currentFDC[i]);
if(normalizingMetric > 0){
modifiedFlows.add(currentFlows[i] / normalizingMetric);
}else{
modifiedFlows.add(0.);
}
}
}
//Convert data back into a double[][] array
double[][] resultData = new double[modifiedFDC.size()][2];
for(int i=0; i<modifiedFDC.size(); i++){
resultData[i][0] = modifiedFDC.get(i);
resultData[i][1] = modifiedFlows.get(i);
}
return resultData;
}
/**
*
* @param currentFDCdata
* @return
*/
private double[] calculateWeightedFDC(double[][] currentFDCdata){
//Determine where to interpolate the FDC
double[] exceedances = new double[200];
for(int i=0; i<200; i++){
exceedances[i] = i * 0.5;
}
//Interpolate a FDC at a standard location so a weighted average of multiple of these can be made
double[] currentFDC = DoubleArray.getColumn(currentFDCdata, 0);
double[] currentFlows = DoubleArray.getColumn(currentFDCdata, 1);
double[] summaryFDC = DoubleMath.linearInterpolation(currentFDC, currentFlows, exceedances);
//Multiply the resulting interpolated FDC by the it's size for a weighted average later
double currentSize = currentFDCdata.length;
for(int i=0; i<summaryFDC.length; i++){
summaryFDC[i] = summaryFDC[i] * currentSize;
}
return summaryFDC;
}
/**
* Writes out the non-zero results of the duration curve analysis which are the flow duration curve intervale and their corresponding flow/load values
* @param results double[][] array to be written as each line of the text file
* @param partialpath the partial folder path of the file to be written
* @param resultType the second column of data header label
* @throws IOException
*/
private void writeResults(double[][] results) throws IOException{
//Remove zero results
ArrayList<String> finalResults = new ArrayList<>();
for(int i=0; i<results.length; i++){
//Only keep non-zero data
if(Double.compare(results[i][0],0) != 0){
finalResults.add(results[i][0] + "\t" + results[i][1]);
}
}
String path = mainFolder + File.separator + getRegionalFDC_results().getName();
FileWriter writer = new FileWriter(path, false);
PrintWriter printLine = new PrintWriter(writer);
//Add Headers to text file
printLine.printf("%s" + "\r\n", "Percent of Time Flow is Exceeded [%] (Excedence Probability based on Weibull Plotting Position Ranking)\tRegional FDC [" + units + "]");
//Output data to text file
for(int i=0; i < finalResults.size(); i++) {
printLine.printf("%s" + "\r\n", finalResults.get(i));
}
System.out.println("Text File located at:\t" + path);
printLine.close();
writer.close();
}
/**
* Writes out the error message, if any, for finding the file and then exits the program
* @param error string array to be written as each line of an error message
* @throws IOException
*/
public void writeError(ArrayList<String> error) throws IOException{
//Output data to text file
String errorContents = error.get(0);
for(int i=1; i<error.size(); i++){
errorContents = errorContents + "\n" + error.get(i);
}
throw new IOException("Error encountered. Please see the following message for details: \n" + errorContents);
}
/**
* Primary regional flow duration curve model
* It calls the subfunctions based on user selection/inputs.
* Calls STORET or USGS database queries and their respective subfunctions
* @throws IOException
*/
public void run() throws IOException {
//Graph the complete flow duration curve for the time period
XYPlot plot = new XYPlot();
//Create X Axis
ValueAxis xAxis = new NumberAxis("Percent of Time Flow is Exceeded [%]");
xAxis.setRange(0,100);
plot.setDomainAxis(0, xAxis);
//Set log-scale y axis
units = "Daily Average Flow [cfs] / " + normalizingMetricName;
LogarithmicAxis yAxis = new LogarithmicAxis(units);
yAxis.setAllowNegativesFlag(true);
plot.setRangeAxis(0, yAxis);
//Colors in the color matrix below are: purple, blue, cyan, green, gold, red, pink
Color[] colorMatrix = {new Color(105, 0, 255), Color.blue, Color.cyan, Color.green, new Color(255, 135, 0), new Color(255, 0, 50), new Color(255, 0, 255, 100)};
//Normalize each duration curve by it's provided index/metric
ArrayList<double[]> weightedFDC_all = new ArrayList<>();
String[][] graphData = new String[fdcData.length][fdcData[0].length];
int stationIndex = 0;
summaryTable = "";
double totalSize = 0;
for(int i=0; i<fdcData[0].length; i++){
double[] currentFDC = DoubleArray.getColumn(fdcData, i);
double[] currentFlows = DoubleArray.getColumn(fdcData, i+1);
double[][] normalizedFDC = normalizeFDCdata(currentFDC, currentFlows, normalizingMetrics[stationIndex]);
calculatePercentileSummmary(normalizedFDC, false, stationIndex);
totalSize = totalSize + normalizedFDC.length;
//Weight the current FDC by it's record length and save it to be added to the regional curve
double[] weightedFDC = calculateWeightedFDC(normalizedFDC);
weightedFDC_all.add(weightedFDC);
//Graph new normalized FDC, and save the same data as an output for JHighcharts
XYSeries current_FDC = new XYSeries("Normalized FDC, " + databases[stationIndex] + " Station: " + stationIDs[stationIndex] + "; " + stationNames[stationIndex]);
for(int j=0; j<fdcData.length; j++){
if(j < normalizedFDC.length){
current_FDC.add(normalizedFDC[j][0], normalizedFDC[j][1]);//x,y
graphData[j][i] = String.valueOf(normalizedFDC[j][0]);
graphData[j][i+1] = String.valueOf(normalizedFDC[j][1]);
}else{
graphData[j][i] = "-1";
graphData[j][i+1] = "-1";
}
}
//Create a graph with the FDC (line)
XYDataset current_FDCdata = new XYSeriesCollection(current_FDC);
XYItemRenderer renderer1 = new XYLineAndShapeRenderer(true, false);
if(stationIndex < colorMatrix.length){
renderer1.setSeriesPaint(0, colorMatrix[stationIndex]);
}else{
renderer1.setSeriesPaint(0, Color.lightGray);
}
//Set the FDC line data, renderer, and axis into plot
plot.setDataset(stationIndex + 1, current_FDCdata);
plot.setRenderer(stationIndex + 1, renderer1);
//Map the line to the first Domain and first Range
plot.mapDatasetToDomainAxis(stationIndex + 1, 0);
plot.mapDatasetToRangeAxis(stationIndex + 1, 0);
stationIndex++;
i++;//double increase 'i' because of 1 column for fdc and 1 column for flows per station
}
//Average all of the weighted FDCs into a regional flow duration curve
double[][] regionalXYranks = new double[200][2];
for(int i=0; i<regionalXYranks.length; i++){
double sum = 0;
for(int j=0; j<weightedFDC_all.size(); j++){
double[] currentWeightedFDC = weightedFDC_all.get(j);
sum = sum + currentWeightedFDC[i];
}
regionalXYranks[i][0] = i * 0.5; //use the same exceedance values that the interpolation used
regionalXYranks[i][1] = (sum / totalSize); //divide by total observations to get weighted average of all other fdcs base on their relative length
}
//Add the overall 'regional' summary to the beginning of the summary table
String partialTable = summaryTable;
calculatePercentileSummmary(regionalXYranks, true, -9999);
summaryTable = summaryTable + partialTable;
writeResults(regionalXYranks);
XYSeries regional_FDC = new XYSeries("Regional Flow Duration Curve");
regionalFDC = "";
for(int j=0; j<regionalXYranks.length; j++){
regional_FDC.add(regionalXYranks[j][0], regionalXYranks[j][1]);//x,y
if(j == 0){
regionalFDC = String.valueOf(regionalXYranks[j][0]) + "\t" + String.valueOf(regionalXYranks[j][1]);
}else{
regionalFDC = regionalFDC + "\n" + String.valueOf(regionalXYranks[j][0]) + "\t" + String.valueOf(regionalXYranks[j][1]);
}
}
//Create a graph with the FDC (line)
XYDataset current_FDCdata = new XYSeriesCollection(regional_FDC);
XYItemRenderer renderer1 = new XYLineAndShapeRenderer(true, false);
Stroke thickness = new BasicStroke(4);
renderer1.setSeriesStroke(0, thickness);
renderer1.setSeriesPaint(0, Color.black);
//Set the FDC line data, renderer, and axis into plot
plot.setDataset(0, current_FDCdata);
plot.setRenderer(0, renderer1);
//Map the line to the first Domain and first Range
plot.mapDatasetToDomainAxis(0, 0);
plot.mapDatasetToRangeAxis(0, 0);
//Output XY data for use with JHighCharts
DoubleArray.writeXYseries(mainFolder, graphData, getRegionalFDCgraphOutput().getName());
//Add Flow Range Labels:
plot = Graphing.addIntervalLabel(plot, "High Flow", 0, 10);
plot = Graphing.addIntervalLabel(plot, "Moist Conditions", 10, 40);
plot = Graphing.addIntervalLabel(plot, "Mid-Range Flows", 40, 60);
plot = Graphing.addIntervalLabel(plot, "Dry Conditions", 60, 90);
plot = Graphing.addIntervalLabel(plot, "Low Flow", 90, 100);
//Set extra plot preferences
plot = Graphing.setLogYaxisPreferences(plot);
//Graph plot onto JfreeChart
String graphTitle = "Regionalized Flow Duration Curve, Normalized by: " + normalizingMetricName;
JFreeChart chart = new JFreeChart(graphTitle, Graphing.titleFont, plot, true);
//Set legend Font
LegendTitle legendTitle = chart.getLegend();
legendTitle.setItemFont(Graphing.masterFont);
//Save resulting graph for proof it works
try{
String path = mainFolder + File.separator + getGraph();
ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
System.out.println("JFreeChart created properly at: " + path);
}catch(IOException e){
System.out.println("A problem occurred while trying to creating the chart for the regional flow duration curve. Error: USGSFDC0003");
System.out.println(e.toString());
}
}
public static void main(String[] args) throws IOException, Exception {
//Define and run model
guiRegionalFDC_Model fdc_Model = new guiRegionalFDC_Model();
fdc_Model.run();
}
}