guiDC_Model.java [src/java/cfa] Revision: 71034d718741493d9f7090ceb551e70bb1094a06 Date: Fri Sep 05 10:37:45 MDT 2014
package cfa;
import java.awt.Color;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
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: 3-September-2014
* @author Tyler Wible
* @since 12-June-2011
*/
public class guiDC_Model {
//Inputs
String mainFolder = "C:/Projects/TylerWible/CodeDirectories/NetBeans/CSIP/data/CFA/DurationCurve";
String database = "USGS";//"CDWR";//"STORET";//"UserData";//
String organizationName = "USGS";//"Co. Division of Water Resources";//"Colorado Dept. of Public Health & Environment";//
String stationID = "06752280";//"CLAGRECO";//"000028";//
String stationName = "CACHE LA POUDRE RIV AB BOXELDER CRK NR TIMNATH, CO";//"Cache La Poudre Near Greeley";//"BIG THOMPSON R NEAR MOUTH";//
String modelType = "FDC";//"LDC";//
String wqTest = "flow";//"00600 Total nitrogen, water, unfiltered, milligrams per liter -- mg/l";//"Nitrogen, Nitrate (NO3) as NO3";//
double wqTarget = 10;//in units of current test
String beginDate = "";
String endDate = "";
String seasonBegin = "04-01";//"MM-dd"
String seasonEnd = "09-30";//"MM-dd"
double highPercentile = 0.75;
double lowPercentile = 0.25;
String mQnPeriod = "false";//"7Q10";//
String userData = "";//"Date\tFlow\n1999-04-29\t8.3\n1999-05-09\t60.2\n1999-05-29\t20.1$$Date\t00600\n1999-04-29\t8.3\n1999-05-09\t60.2\n1999-05-29\t20.1";//"Date\tFlow\n1999-04-29\t8.3\n1999-05-09\t60.2\n1999-05-29\t20.1";//
boolean mergeDatasets = false;//true;//
String mergeMethod = "user";//"public";//"max";//"average";//"min";//
//Outputs
String flowLen = "-1";
String wqLen = "-1";
String wqUnits = "?";
String start = "?";
String end = "?";
String summaryParagraph = "?";
String summaryTable = "?";
String dataSource = "?";
String lowFlowErrorMessage = "";
double mQnVal = -1;
//Gets
public File getDurationCurve_results() {
return new File(mainFolder, "duration_curve_results.txt");
}
public File getFlowStatistics_summary() {
FlowStatistics flowStats = new FlowStatistics();
return new File(mainFolder, flowStats.getFlowStatistics_summary());
}
public String getGraph() {
return "duration_curve_graph.jpg";
}
public File getDCgraphOutput(){
//This output file is for use with JSHighCharts
return new File(mainFolder, "duration_curve_graph.out");
}
public String getFlowLen() {
return flowLen;
}
public String getWQLen() {
return wqLen;
}
public String getWQUnits() {
return wqUnits;
}
public String getStart() {
return start;
}
public String getEnd() {
return end;
}
public String getSummaryParagraph(){
return summaryParagraph;
}
public String getSummaryTable(){
return summaryTable;
}
public String getDataSource(){
return dataSource;
}
public String getLowFlowErrorMessage(){
return lowFlowErrorMessage;
}
public double getMQNval(){
return mQnVal;
}
//Sets
public void setMainFolder(String mainFolder) {
this.mainFolder = mainFolder;
}
public void setModelType(String modelType) {
this.modelType = modelType;
}
public void setDatabase(String database) {
this.database = database;
}
public void setOrganizationName(String organizationName) {
this.organizationName = organizationName;
}
public void setStationID(String stationID) {
this.stationID = stationID;
}
public void setStationName(String stationName) {
this.stationName = stationName;
}
public void setWaterQualityTest(String wqTest) {
this.wqTest = wqTest;
}
public void setWaterQualityTarget(double wqTarget) {
this.wqTarget = wqTarget;
}
public void setBeginDate(String beginDate) {
this.beginDate = beginDate;
}
public void setEndDate(String endDate) {
this.endDate = endDate;
}
public void setSeasonBegin(String seasonBegin) {
this.seasonBegin = seasonBegin;
}
public void setSeasonEnd(String seasonEnd) {
this.seasonEnd = seasonEnd;
}
public void setHighPercentile(double highPercentile) {
this.highPercentile = highPercentile;
}
public void setLowPercentile(double lowPercentile) {
this.lowPercentile = lowPercentile;
}
public void setMQNperiod(String mQnPeriod) {
this.mQnPeriod = mQnPeriod;
}
public void setUserData(String userData) {
this.userData = userData;
}
public void setMergeDatasets(boolean mergeDatasets) {
this.mergeDatasets = mergeDatasets;
}
public void setMergeMethod(String mergeMethod) {
this.mergeMethod = mergeMethod;
}
/**
* Main Flow Duration Curve (FDC) function
* Creates a graph and dynamic summary for the graph, returns an image and text file in
* location "mainFolder"/"fileName"graph.jpg and "mainFolder"/"fileName"paragraph.txt
* @return returns a String[] containing the dynamically created summary paragraph to accompany the graph created
* @throws IOException
* @throws InterruptedException
*/
private void createFDC() throws IOException, InterruptedException, ParseException, Exception{
//Inputs
// String mainFolder = the file location where the resulting load duration curve graph will be saved and the dynamic paragraph text file will be saved
// String fileName = the name of the graph and text file to be saved (the graph created will be fileNamegraph.txt and the txt file will be fileNameparagraph.txt)
// String organizationName = the name of the supervising agency of the current station, used to determine if data should be extracted from USGS or STORET
// String stationID = the station ID for the current station, used to extract data
// String stationName = the station name for the current station, used to label the graph
// String beginDate = user specified begin date for analysis (yyyy-mm-dd)
// String endDate = user specified end date for analysis (yyyy-mm-dd)
// String mQnPeriod = contains mQn-period or false, if false no low flow mQn analysis will be performed, if not false should contain ex. 7Q10-year for a 7-day, 10-year low flow analysis to be performed
//Check if user wants mQnFlow calculated and displayed
boolean mQnHide = mQnPeriod.contains("false");
//Check if any flow data exists
Data data = new Data();
String[][] sortableData = data.extractFlowData(mainFolder, database, organizationName, stationID, beginDate, endDate, userData);
//If the user wants the datasets (public and user) merged then retrieve the second dataset (user)
String[][] sortableData_user = new String[0][0];
if(mergeDatasets){
User_Data user_Data = new User_Data();
sortableData_user = user_Data.readUserFile(userData, "flow", beginDate, endDate);
}
//Sort the Data by date to remove duplicate date entries
DoubleArray doubleArray = new DoubleArray();
String[][] sortedData = doubleArray.removeDuplicateDates(sortableData);
String[][] sortedData_user = doubleArray.removeDuplicateDates(sortableData_user);
//Merge the two datasets (if user data is empty nothing will be merged)
String[][] sortedData_combined = doubleArray.mergeData(sortedData, sortedData_user, mergeMethod);
if(sortedData_combined.length == 0){
ArrayList<String> errorMessage = new ArrayList<String>();
if(sortedData.length == 0){
errorMessage.add("There is no available flow data in the " + database + " database for station '" + stationID + "' and the specified date range.");
if(database.equalsIgnoreCase("CDWR")){
errorMessage.add("The CDWR database is sensitive to the begin date used, try specifying a later begin date");
}
}
if(sortedData_user.length == 0){
errorMessage.add("There is no available uploaded data for station '" + stationID + "' and the specified date range");
}
writeError(errorMessage);
}
//While the dataset is still sorted by date, pull out the mQn value and start/end dates for analysis summary
int m=0, n=0;
if(mQnHide == false){
//Pull out mQn-period variables
String mString = mQnPeriod.substring(0,mQnPeriod.indexOf("Q"));
String nString = mQnPeriod.substring(mQnPeriod.indexOf("Q")+1);
m = Integer.parseInt(mString);
n = Integer.parseInt(nString);
}
FlowStatistics flowStats = new FlowStatistics();
Object[] returnValue = flowStats.calculateAllStatisticsSummaries(mainFolder, stationID, stationName, sortedData_combined, highPercentile, lowPercentile, m, n, true,
seasonBegin, seasonEnd, "", "", "", "", "", "");
double mQn = (Double) returnValue[0];
String MQNmessage = (String) returnValue[1];
this.start = sortedData_combined[0][0];
this.end = sortedData_combined[sortedData_combined.length - 1][0];
this.flowLen = String.valueOf(sortedData_combined.length);
//Create Custom Flow Duration Curve graph
graphFDC(sortedData_combined, sortedData_user, mQn, m, n, mQnHide);
//Attach analysis summary to return object
this.summaryParagraph = dynamicParagraph("Flow Duration Curve Overview: ");
DoubleMath doubleMath = new DoubleMath();
if(!mQnHide){
if(!MQNmessage.equalsIgnoreCase("")){//if the MQNmessage is not blank add it to the dynamic summary
ArrayList<String> errorMessage = new ArrayList<String>();
errorMessage.add(this.lowFlowErrorMessage);
writeError(errorMessage);
}else{
this.mQnVal = doubleMath.round(mQn, 2);
}
}
}
/**
* Main graphing function for FDC analysis, plots the provided xyRanks as x,y points on a graph
* with or without low flow analysis (mQn) added depending if it is non-zero or not
* @param sortedData_combined String[][] containing 2 columns, first column dates (yyyy-mm-dd) second column y values of flow of all the data
* @param sortedData_user String[][] containing 2 columns, first column dates (yyyy-mm-dd) second column y values of flow of the user data only
* @param mQn the low flow analysis value, if it is not zero it will be graphed, otherwise it will not be graphed
* @param m the "m" value of an mQn flow analysis (like 7Q10)
* @param n the "n" value of an mQn flow analysis (like 7Q10)
* @param mQnHide if true then mQn wil be not graphed, otherwise it will be shown in the legend
* @throws IOException
*/
private void graphFDC(String[][] sortedData_combined,
String[][] sortedData_user,
double mQn,
double m,
double n,
boolean mQnHide) throws IOException{
//Perform Weibull Plotting Position Ranking for the Flow Duration Curve Method
DoubleArray doubleArray = new DoubleArray();
double[][] xyRanks = doubleArray.weibullPlottingPosition(sortedData_combined);
//Determine summary table from xyRanks for duration curve
calculatePercentileSummmary(xyRanks, "Flow (cfs)");
//Graph the complete flow duration curve for the time period
XYSeries FDC_xy = new XYSeries("FDC");
XYSeries mQn_xy = new XYSeries(String.valueOf(m) + "Q" + String.valueOf(n) + " low flow");
XYSeries FDC_user = new XYSeries("User Data");
String[][] graphData = new String[xyRanks.length][2];
int ctr = 0, seriesIndex = 0;
for(int i=0; i<xyRanks.length; i++){
double x = xyRanks[i][0];
double y = xyRanks[i][1];
FDC_xy.add(x, y);
graphData[i][0] = String.valueOf(x);
graphData[i][1] = String.valueOf(y);
if(i != 0){//get low flow intersection point for mQn flow
if(xyRanks[i][1] < mQn && xyRanks[i-1][1] >= mQn){
mQn_xy.add(x,mQn);
}
}
//Create an XYSeries only if userdata is not empty
if(sortedData_user.length > 0 && sortedData_user.length > ctr){
double userValue = Double.parseDouble(sortedData_user[ctr][1]);
if(Double.compare(xyRanks[i][1], userValue) == 0){
FDC_user.add(x, y);
ctr++;
}
}
}
//Graph resulting FDC and flow value data
XYPlot plot = new XYPlot();
boolean showLegend = false;
//Create X Axis
ValueAxis xAxis = new NumberAxis("Flow Duration Interval [%]");
xAxis.setRange(0,100);
plot.setDomainAxis(0, xAxis);
//Set log-scale y axis
LogarithmicAxis yAxis = new LogarithmicAxis("Discharge [cfs]");
yAxis.setAllowNegativesFlag(true);
plot.setRangeAxis(0, yAxis);
//Create a graph with the FDC (line)
XYDataset FDC_data = new XYSeriesCollection(FDC_xy);
XYItemRenderer renderer1 = new XYLineAndShapeRenderer(true, false);
renderer1.setSeriesPaint(0, Color.black);
//Set the FDC line data, renderer, and axis into plot
plot.setDataset(seriesIndex, FDC_data);
plot.setRenderer(seriesIndex, renderer1);
//Map the line to the first Domain and first Range
plot.mapDatasetToDomainAxis(seriesIndex, 0);
plot.mapDatasetToRangeAxis(seriesIndex, 0);
seriesIndex++;
//Create low mQn point
if(Double.compare(mQn, 0) != 0){//only show mQn if it is not zero
XYDataset mQn_data = new XYSeriesCollection(mQn_xy);
XYItemRenderer renderer = new XYLineAndShapeRenderer(false, true);
renderer.setSeriesShape(0, new Ellipse2D.Double(-3.0, 3.0, 6.0, 6.0));
renderer.setSeriesPaint(0, Color.red);
plot.setDataset(seriesIndex,mQn_data);
plot.setRenderer(seriesIndex,renderer);
plot.mapDatasetToDomainAxis(seriesIndex,0);
plot.mapDatasetToRangeAxis(seriesIndex,0);
seriesIndex++;
showLegend = true;
}
//Create user data points
if(sortedData_user.length != 0){//only show user points if it is not zero
XYDataset user_data = new XYSeriesCollection(FDC_user);
XYItemRenderer renderer = new XYLineAndShapeRenderer(false, true);
renderer.setSeriesPaint(0, Color.black);
plot.setDataset(seriesIndex,user_data);
plot.setRenderer(seriesIndex,renderer);
plot.mapDatasetToDomainAxis(seriesIndex, 0);
plot.mapDatasetToRangeAxis(seriesIndex, 0);
seriesIndex++;
showLegend = true;
}
//Graph a FDC for each year in time period
Graphing graphing = new Graphing();
String currentYear = start.substring(0,4);
String finalYear = end.substring(0,4);
boolean moreYears = xyRanks.length > 0;
while(moreYears){
//Get current year's data and graph it
String[][] partialData = doubleArray.getYearsData(sortedData_combined, currentYear);
double[][] partialRanks = doubleArray.weibullPlottingPosition(partialData);
graphing.graphSeries(plot, partialRanks, Color.lightGray, seriesIndex);
seriesIndex++;
//Save results for output for JHighCharts
String[] partialFDCData_x = new String[xyRanks.length];
String[] partialFDCData_y = new String[xyRanks.length];
for(int i=0; i<xyRanks.length; i++){
partialFDCData_x[i] = "-1";//value
partialFDCData_y[i] = "-1";//value
try{
partialFDCData_x[i] = String.valueOf(partialRanks[i][0]);//x
partialFDCData_y[i] = String.valueOf(partialRanks[i][1]);//y
}catch(IndexOutOfBoundsException e){
//do nothing as it already has a -1 value
}
}
graphData = doubleArray.appendcolumn_Matrix(graphData, partialFDCData_x);//Add x points
graphData = doubleArray.appendcolumn_Matrix(graphData, partialFDCData_y);//Add y points
//Determine the next data year to continue looping over
int nextYear = Integer.parseInt(currentYear) + 1;
if(finalYear.compareToIgnoreCase(String.valueOf(nextYear)) >= 0){
currentYear = String.valueOf(nextYear);
}else{
moreYears = false;
}
}
//Output XY data for use with JHighCharts
doubleArray.writeXYseries(mainFolder, graphData, getDCgraphOutput().getName());
//Set extra graphing preferences
plot = graphing.setLogYaxisPreferences(plot);
//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);
//Graph plot onto JfreeChart
String graphTitle = "Flow Duration Curve for " + database + " Station " + stationID + "; " + stationName;
JFreeChart chart = new JFreeChart(graphTitle, graphing.titleFont, plot, showLegend);
//Write a results file containing the flow duration curve interval (exceedance values) and their corresponding discharge values
writeResults(xyRanks, mainFolder, "Discharge (cfs)");
//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 station '" + stationName + "'. Error: USGSFDC0003");
System.out.println(e.toString());
}
}
/**
* Main Load Duration Curve (LDC) function
* Creates a graph and dynamic summary for the graph, returns an image and text file in
* location "mainFolder"/"fileName"graph.jpg and "mainFolder"/"fileName"paragraph.txt
* @return returns a String[] containing the dynamically created summary paragraph to accompany the graph created
* @throws IOException
* @throws InterruptedException
*/
private void createLDC() throws IOException, InterruptedException, ParseException {
// String mainFolder the file location where the resulting load duration curve graph will be saved and the dynamic paragraph text file will be saved
// String fileName the name of the graph and text file to be saved (the graph created will be fileNamegraph.txt and the txt file will be fileNameparagraph.txt)
// String organizationName the name of the supervising agency of the current station, used to determine if data should be extracted from USGS or STORET
// String stationID the station ID for the current station, used to extract data
// String stationName the station name for the current station, used to label the graph
// String wqTest the current user selected water quality (wq) test
// String beginDate user specified begin date for analysis (yyyy-mm-dd)
// String endDate user specified end date for analysis (yyyy-mm-dd)
// double wqTarget user specified water quality target for water quality standards for the current wqTest
// String seasonBegin user specified begin month for the "season" of analysis (ex. "April")
// String seasonEnd user specified end month of the "season" of analysis (ex. "August")
// String mQnPeriod contains mQn-period or false, if false no low flow mQn analysis will be performed, if not false should contain ex. 7Q10-year for a 7-day, 10-year low flow analysis to be performed
//Check if any flow and water quality data exists
Data data = new Data();
Object[] returnArray1 = data.extractFlow_and_WQdata(mainFolder, database, organizationName, stationID, beginDate, endDate, userData, wqTest);
String[][] sortableData = (String[][]) returnArray1[0];
String[][] WQdata = (String[][]) returnArray1[1];
//Find the user uploaded data file and uses this for a timeseries graph
String[][] sortableData_user = new String[0][0];
String[][] WQdata_user = new String[0][0];
if(mergeDatasets){
User_Data user_Data = new User_Data();
Object[] returnArray2 = user_Data.readUserFileLDC(userData, wqTest, beginDate, endDate);
sortableData_user = (String[][]) returnArray2[0];
WQdata_user = (String[][]) returnArray2[1];
}
//Sort the Data by date to remove duplicate date entries
DoubleArray doubleArray = new DoubleArray();
String[][] sortedData = doubleArray.removeDuplicateDates(sortableData);
String[][] sortedData_user = doubleArray.removeDuplicateDates(sortableData_user);
//Merge the two datasets (if user data is empty nothing will be merged)
String[][] sortedData_combined = doubleArray.mergeData(sortedData, sortedData_user, mergeMethod);
String[][] WQdata_combined = doubleArray.mergeData(WQdata, WQdata_user, mergeMethod);
//Check if any data exists
ArrayList<String> errorMessage = new ArrayList<String>();
if(sortedData_combined.length == 0){
if(sortedData.length == 0){
errorMessage.add("There is no available flow data in the " + database + " database for station '" + stationID + "' and the specified date range.");
if(database.equalsIgnoreCase("CDWR")){
errorMessage.add("The CDWR database is sensitive to the begin date used, try specifying a later begin date");
}
}
if(sortedData_user.length==0){
errorMessage.add("There is no uploaded flow data for station '" + stationID + "' and the specified date range.");
}
}
if(WQdata_combined.length == 0){
if(WQdata.length == 0){
errorMessage.add("There are no available '" + wqTest + "' water quality tests for station " + stationID+ " by: " +
organizationName + " and the specified date range");
}
if(WQdata_user.length==0){
errorMessage.add("There are no uploaded '" + wqTest + "' water quality tests for station " + stationID+ " and the specified date range");
}
}
if(errorMessage.size() > 0){
writeError(errorMessage);
}
//Determine additional information for the model conversions and labels
int endIndex = wqTest.lastIndexOf(", ");//"00597 Dissolved nitrogen gas, water, unfiltered, milligrams per liter -- mg/l"
if(endIndex == -1){
endIndex = wqTest.lastIndexOf("--");
}
String WQlabel = wqTest.substring(11,endIndex);//cut off the "98335 " part before the test name and the units after the name
//Get Units and conversion for current WQ test
USGS_Data usgs_Data = new USGS_Data();
String units = usgs_Data.getUSGSwqUnits(wqTest.substring(0,5));//pull just the 5 digit USGS WQ code
double conversion = usgs_Data.getUSGSwqConversion(units);
String endUnits = usgs_Data.getUSGSwqEndUnits(units);
//Check if user wants mQnFlow calculated and displayed
boolean mQnHide = mQnPeriod.contains("false");
//While the dataset is still sorted by date, pull out the mQn value and start/end dates for analysis summary
int m=0, n=0;
if(mQnHide == false){
//Pull out mQn-period variables
String mString = mQnPeriod.substring(0,mQnPeriod.indexOf("Q"));
String nString = mQnPeriod.substring(mQnPeriod.indexOf("Q")+1);
m = Integer.parseInt(mString);
n = Integer.parseInt(nString);
}
FlowStatistics flowStats = new FlowStatistics();
Object[] returnValue = flowStats.calculateAllStatisticsSummaries(mainFolder, stationID, stationName, sortedData_combined, highPercentile, lowPercentile, m, n, true,
seasonBegin, seasonEnd, "", "", "", "", "", "");
double mQn = (Double) returnValue[0];
String MQNmessage = (String) returnValue[1];
//Also multiply the mQn flow value(cfs) by the conversion and water quality target
mQn = mQn*conversion*wqTarget;
this.start = sortedData_combined[0][0];
this.end = sortedData_combined[sortedData_combined.length - 1][0];
this.flowLen = String.valueOf(sortedData_combined.length);
String[][] seasonalWQ_combined = doubleArray.getSeasonalData(WQdata_combined, seasonBegin, seasonEnd);
String[][] seasonalWQ_user = doubleArray.getSeasonalData(WQdata_user, seasonBegin, seasonEnd);
//Graph resulting LDC data
Object[] returnArray = graphLDC(WQlabel, endUnits, conversion, sortedData_combined, sortedData_user, WQdata_combined, WQdata_user, seasonalWQ_combined, seasonalWQ_user, mQn, m, n);
String paragraphTitle = (String) returnArray[0];
double totalCount = (Double) returnArray[1];
//Attach analysis summary to begin of dynamic paragraph
this.wqLen = String.valueOf(totalCount);
this.wqUnits = endUnits;
this.summaryParagraph = dynamicParagraph(paragraphTitle);
DoubleMath doubleMath = new DoubleMath();
if(!mQnHide){
if(!MQNmessage.equalsIgnoreCase("")){//if the MQNmessage is not blank add it to the dynamic summary
this.lowFlowErrorMessage = MQNmessage;
}else{
this.mQnVal = doubleMath.round(mQn, 2);
}
}
}
/**
* Sub-graphing function to create combined-range graph
* @param WQlabel the title of the water quality test being graphed
* @param endUnits the final load units of the water quality test * cfs * conversion = load/day
* @param conversion
* @param sortedData_combined the load duration curve XY line data of combined data
* @param sortedData_user the load duration curve XY line data of user data only
* @param WQdata_combined the observed water quality points XY scatter of combined data
* @param WQdata_user the user data water quality points XY scatter of user data only
* @param seasonalWQ_combined the observed water quality points within the "season" XY scatter of combined data
* @param seasonalWQ_user the user data water quality points within the "season" XY scatter of user data only
* @param mQn the value of the mQn flow (if it is zero then don't show this line in the graph)
* @param m the value of m from mQn flow calculations
* @param n the value of n from mqn flow calculations
* @return the name/title of the dynamic paragraph to be created to accompany the graph created during this function
* @throws IOException
*/
private Object[] graphLDC(String WQlabel,
String endUnits,
double conversion,
String[][] sortedData_combined,
String[][] sortedData_user,
String[][] WQdata_combined,
String[][] WQdata_user,
String[][] seasonalWQ_combined,
String[][] seasonalWQ_user,
double mQn,
double m,
double n) throws IOException {
//Perform Weibull Plotting Position Ranking for the Load Duration Curve Method
//Note: this data is already converted to loads, see "guiDC_Model.createLDC"
DoubleArray doubleArray = new DoubleArray();
double[][] xyRanks = doubleArray.weibullPlottingPosition(sortedData_combined);
//Determine summary table from xyRanks for duration curve
double[][] loadRanks = new double[xyRanks.length][2];
for(int i=0; i<xyRanks.length; i++){
loadRanks[i][0] = xyRanks[i][0];
loadRanks[i][1] = xyRanks[i][1] * conversion * wqTarget;
}
calculatePercentileSummmary(loadRanks, "Load (" + endUnits +")");
//Break scattered WQ points data into the 5 flow sub classifications 0-10, 10-40, 40-60, 60-90, 90-100
//which are High flows, Moist conditions, Mid-range flows, Dry conditions, and low flows respectively Assimilative
String yaxis_title = WQlabel + " [" + endUnits + "]";
XYSeries LDC_xy = new XYSeries("LDC");
XYSeries LDC_user = new XYSeries("LDC User Data");
XYSeries WQ_pts = new XYSeries("Water Quality Obs.");
XYSeries WQ_pts_user = new XYSeries("Water Quality Obs. User Data");
XYSeries Seasonal_WQ_pts = new XYSeries("Seasonal Water Quality Obs.");
XYSeries Seasonal_WQ_pts_user = new XYSeries("Seasonal Water Quality Obs. User Data");
XYSeries mQn_xy = new XYSeries("Current " + String.valueOf(m) + "Q" + String.valueOf(n) + " water quality standard");
String[][] graphData = new String[xyRanks.length][2];
int ctr = 0;
for (int i=0; i<xyRanks.length; i++){
double y = xyRanks[i][1] * conversion * wqTarget;
double x = xyRanks[i][0];
LDC_xy.add(x, y);
graphData[i][0] = String.valueOf(x);
graphData[i][1] = String.valueOf(y);
if(y > mQn){
mQn_xy.add(x,mQn);
}else{
mQn_xy.add(x, y);
}
//Create an XYSeries only if userdata is not empty
if(sortedData_user.length > 0 && sortedData_user.length > ctr){
double userValue = Double.parseDouble(sortedData_user[ctr][1]);
if(Double.compare(xyRanks[i][1], userValue) == 0){
LDC_user.add(x, y);
ctr++;
}
}
}
//Add WQ test data to graph series and generate box plot data
ArrayList<Double> high_data = new ArrayList<Double>();
ArrayList<Double> moist_data = new ArrayList<Double>();
ArrayList<Double> mid_data = new ArrayList<Double>();
ArrayList<Double> dry_data = new ArrayList<Double>();
ArrayList<Double> low_data = new ArrayList<Double>();
double highexceed_count = 0, moistexceed_count =0, midexceed_count =0, dryexceed_count =0,
lowexceed_count =0, totalexceed_count =1, totalCount = 0;
double WQ_load = 0, Seasonal_WQ_load=0;
ctr = 0;
for(int i=0;i<WQdata_combined.length;i++){
for(int j=0; j<sortedData_combined.length;j++){
if(WQdata_combined[i][0].equalsIgnoreCase(sortedData_combined[j][0])){
totalCount = totalCount+1;
//Create an XYSeries for WQ points
double tempWQValue = Double.parseDouble(WQdata_combined[i][1]);
WQ_load = (Double.parseDouble(sortedData_combined[j][1]))*conversion*tempWQValue;
WQ_pts.add(xyRanks[j][0],WQ_load);
//Create an XYSeries only if user WQ data is not empty
if(WQdata_user.length > 0 && WQdata_user.length > ctr){
if(WQdata_user[ctr][0].equalsIgnoreCase(sortedData_combined[j][0])){
WQ_pts_user.add(xyRanks[j][0],WQ_load);
ctr++;
}
}
//Also add to category box plot data
if((xyRanks[j][0] <= 10) & (xyRanks[j][0] >= 0)){
high_data.add(WQ_load);
if(WQ_load > xyRanks[j][1]){
highexceed_count++;
}
}
if((xyRanks[j][0] <= 40) & (xyRanks[j][0] > 10)){
moist_data.add(WQ_load);
if(WQ_load > xyRanks[j][1]){
moistexceed_count++;
}
}
if((xyRanks[j][0] <= 60) & (xyRanks[j][0] > 40)){
mid_data.add(WQ_load);
if(WQ_load > xyRanks[j][1]){
midexceed_count++;
}
}
if((xyRanks[j][0] <= 90) & (xyRanks[j][0] > 60)){
dry_data.add(WQ_load);
if(WQ_load > xyRanks[j][1]){
dryexceed_count++;
}
}
if((xyRanks[j][0] <= 100) & (xyRanks[j][0] > 90)){
low_data.add(WQ_load);
if(WQ_load > xyRanks[j][1]){
lowexceed_count++;
}
}
for(int k=0;k<seasonalWQ_combined.length;k++){
if(seasonalWQ_combined[k][0].equalsIgnoreCase(sortedData_combined[j][0])){
double tempSeasonalWQValue = Double.parseDouble(seasonalWQ_combined[k][1]);
Seasonal_WQ_load = (Double.parseDouble(sortedData_combined[j][1]))*conversion*tempSeasonalWQValue;
Seasonal_WQ_pts.add(xyRanks[j][0],Seasonal_WQ_load);
}
}
for(int k=0;k<seasonalWQ_user.length;k++){
if(seasonalWQ_user[k][0].equalsIgnoreCase(sortedData_combined[j][0])){
double tempSeasonalWQValue = Double.parseDouble(seasonalWQ_user[k][1]);
Seasonal_WQ_load = (Double.parseDouble(sortedData_combined[j][1]))*conversion*tempSeasonalWQValue;
Seasonal_WQ_pts_user.add(xyRanks[j][0],Seasonal_WQ_load);
}
}
break;
}
}
}
//Determine which pollution source is likely based on location of exceeded water quality tests.
totalexceed_count = highexceed_count + moistexceed_count + midexceed_count + dryexceed_count + lowexceed_count;
if (totalexceed_count == 0){
totalexceed_count = 1;
}
//Takes the counts of exceedance points and makes them relative to the total count of exceedances.
highexceed_count = highexceed_count/totalexceed_count;
moistexceed_count = moistexceed_count/totalexceed_count;
midexceed_count = midexceed_count/totalexceed_count;
dryexceed_count = dryexceed_count/totalexceed_count;
lowexceed_count = lowexceed_count/totalexceed_count;
//Pollution Source paragraph is only for nutrient tests so check if the current test is for nutrients:
boolean nutrient = false;
if (wqTest.equalsIgnoreCase("00597") || wqTest.equalsIgnoreCase("00600") || wqTest.equalsIgnoreCase("00601") || wqTest.equalsIgnoreCase("00602") ||
wqTest.equalsIgnoreCase("00604") || wqTest.equalsIgnoreCase("00605") || wqTest.equalsIgnoreCase("00606") || wqTest.equalsIgnoreCase("00607") ||
wqTest.equalsIgnoreCase("00608") || wqTest.equalsIgnoreCase("00610") || wqTest.equalsIgnoreCase("00613") || wqTest.equalsIgnoreCase("00615") ||
wqTest.equalsIgnoreCase("00618") || wqTest.equalsIgnoreCase("00619") || wqTest.equalsIgnoreCase("00620") || wqTest.equalsIgnoreCase("00623") ||
wqTest.equalsIgnoreCase("00624") || wqTest.equalsIgnoreCase("00625") || wqTest.equalsIgnoreCase("00628") || wqTest.equalsIgnoreCase("00630") ||
wqTest.equalsIgnoreCase("00631") || wqTest.equalsIgnoreCase("00635") || wqTest.equalsIgnoreCase("00636") || wqTest.equalsIgnoreCase("00639") ||
wqTest.equalsIgnoreCase("00650") || wqTest.equalsIgnoreCase("00653") || wqTest.equalsIgnoreCase("00660") || wqTest.equalsIgnoreCase("00665") ||
wqTest.equalsIgnoreCase("00666") || wqTest.equalsIgnoreCase("00667") || wqTest.equalsIgnoreCase("00669") || wqTest.equalsIgnoreCase("00670") ||
wqTest.equalsIgnoreCase("00671") || wqTest.equalsIgnoreCase("00672") || wqTest.equalsIgnoreCase("00673") || wqTest.equalsIgnoreCase("00674") ||
wqTest.equalsIgnoreCase("00675") || wqTest.equalsIgnoreCase("00676") || wqTest.equalsIgnoreCase("00677") || wqTest.equalsIgnoreCase("00678") ||
wqTest.equalsIgnoreCase("01425") || wqTest.equalsIgnoreCase("01465") || wqTest.equalsIgnoreCase("49567") || wqTest.equalsIgnoreCase("49570") ||
wqTest.equalsIgnoreCase("62854") || wqTest.equalsIgnoreCase("62855") || wqTest.equalsIgnoreCase("64832") || wqTest.equalsIgnoreCase("70507") ||
wqTest.equalsIgnoreCase("71845") || wqTest.equalsIgnoreCase("71846") || wqTest.equalsIgnoreCase("71850") || wqTest.equalsIgnoreCase("71851") ||
wqTest.equalsIgnoreCase("71855") || wqTest.equalsIgnoreCase("71856") || wqTest.equalsIgnoreCase("71886") || wqTest.equalsIgnoreCase("71887") ||
wqTest.equalsIgnoreCase("71888") || wqTest.equalsIgnoreCase("76008") || wqTest.equalsIgnoreCase("76009") || wqTest.equalsIgnoreCase("76010") ||
wqTest.equalsIgnoreCase("82046") || wqTest.equalsIgnoreCase("83044") || wqTest.equalsIgnoreCase("83047") || wqTest.equalsIgnoreCase("83050") ||
wqTest.equalsIgnoreCase("83053") || wqTest.equalsIgnoreCase("83056") || wqTest.equalsIgnoreCase("83059") || wqTest.equalsIgnoreCase("83062") ||
wqTest.equalsIgnoreCase("83065") || wqTest.equalsIgnoreCase("83068") || wqTest.equalsIgnoreCase("83071") || wqTest.equalsIgnoreCase("83074") ||
wqTest.equalsIgnoreCase("83077") || wqTest.equalsIgnoreCase("83080") || wqTest.equalsIgnoreCase("83083") || wqTest.equalsIgnoreCase("83086") ||
wqTest.equalsIgnoreCase("83089") || wqTest.equalsIgnoreCase("83092") || wqTest.equalsIgnoreCase("83095") || wqTest.equalsIgnoreCase("83098") ||
wqTest.equalsIgnoreCase("83101") || wqTest.equalsIgnoreCase("83108") || wqTest.equalsIgnoreCase("83111") || wqTest.equalsIgnoreCase("83114") ||
wqTest.equalsIgnoreCase("83117") || wqTest.equalsIgnoreCase("83326") || wqTest.equalsIgnoreCase("83329") || wqTest.equalsIgnoreCase("83332") ||
wqTest.equalsIgnoreCase("83335") || wqTest.equalsIgnoreCase("83338") || wqTest.equalsIgnoreCase("83341") || wqTest.equalsIgnoreCase("83344") ||
wqTest.equalsIgnoreCase("83347") || wqTest.equalsIgnoreCase("83350") || wqTest.equalsIgnoreCase("83353") || wqTest.equalsIgnoreCase("83356") ||
wqTest.equalsIgnoreCase("83359") || wqTest.equalsIgnoreCase("83362") || wqTest.equalsIgnoreCase("83365") || wqTest.equalsIgnoreCase("83368") ||
wqTest.equalsIgnoreCase("83371") || wqTest.equalsIgnoreCase("83374") || wqTest.equalsIgnoreCase("83377") || wqTest.equalsIgnoreCase("83380") ||
wqTest.equalsIgnoreCase("83383") || wqTest.equalsIgnoreCase("83390") || wqTest.equalsIgnoreCase("83393") || wqTest.equalsIgnoreCase("83396") ||
wqTest.equalsIgnoreCase("83399") || wqTest.equalsIgnoreCase("90859") || wqTest.equalsIgnoreCase("91003") || wqTest.equalsIgnoreCase("91004") ||
wqTest.equalsIgnoreCase("99116") || wqTest.equalsIgnoreCase("99120") || wqTest.equalsIgnoreCase("99121") || wqTest.equalsIgnoreCase("99122") ||
wqTest.equalsIgnoreCase("99123") || wqTest.equalsIgnoreCase("99124") || wqTest.equalsIgnoreCase("99125") || wqTest.equalsIgnoreCase("99126") ||
wqTest.equalsIgnoreCase("99133") || wqTest.equalsIgnoreCase("99410") || wqTest.equalsIgnoreCase("99411") || wqTest.equalsIgnoreCase("99412") ||
wqTest.equalsIgnoreCase("99413") || wqTest.equalsIgnoreCase("99414") || wqTest.equalsIgnoreCase("99415") || wqTest.equalsIgnoreCase("99416") ||
wqTest.equalsIgnoreCase("99889") || wqTest.equalsIgnoreCase("99891") || wqTest.equalsIgnoreCase("99892") || wqTest.equalsIgnoreCase("99893") ||
wqTest.equalsIgnoreCase("99894")){
nutrient = true;
}
//Decide based on properties of the data which pollution source is likely and display the paragraph
//about that pollution source.
String paragraphTitle = "";
if (nutrient == true && (lowexceed_count > 0 || dryexceed_count > 0) &&
(midexceed_count < 0.1 && moistexceed_count < 0.075 && highexceed_count < 0.05) &&
((totalexceed_count/totalCount) > 0.15 || totalexceed_count > 5)){
paragraphTitle = "Point Sources and Wastewater Sources: ";
//If the pollutant target is exceeded under low flows, it is probably a "point source".
}else if (nutrient == true && (moistexceed_count > 0 && midexceed_count > 0) &&
(dryexceed_count < 0.1 && dryexceed_count > 0 && lowexceed_count < 0.05) &&
((totalexceed_count/totalCount) > 0.15 || totalexceed_count > 5)){
paragraphTitle = "Upper Flow Sources: ";
//If the pollutant target is exceeded primarily beginning around 55% flow interval,
//it is probably an "upper-flow" driven non-point-source pollution.
}else if (nutrient == true && (moistexceed_count > 0 && midexceed_count > 0 && dryexceed_count > 0 && lowexceed_count < 0.12) &&
(moistexceed_count < 0.12 && midexceed_count < 0.12 && dryexceed_count < 0.1) &&
((totalexceed_count/totalCount) > 0.15 || totalexceed_count > 5)){
paragraphTitle = "Wet-Weather Sources: ";
//If the pollutant target is exceeded primarily beginning around 70% flow interval,
//it is probably a "wet-weather" driven non-point-source pollution.
}else if (nutrient == true && (highexceed_count > 0 || (highexceed_count > 0 && moistexceed_count > 0)) &&
midexceed_count < 0.2 &&
dryexceed_count < 0.1 &&
lowexceed_count < 0.1 &&
((totalexceed_count/totalCount) > 0.15 || totalexceed_count > 5)){
paragraphTitle = "Erosion Sources: ";
//If the pollutant target is exceeded primarily in the high flow interval,
//"erosion" processes are the likely pollution source.
}else if (nutrient == true && (dryexceed_count > 0.1 && midexceed_count > 0.1 && moistexceed_count > 0.1) ||
(totalexceed_count/totalCount) > 0.5){
paragraphTitle = "Multiple Pollution Sources: ";
//if the target is almost always exceeded in most of the flow intervals,
//display that multiple sources are likely.
}else{
paragraphTitle = "No Apparent Pollution Source: ";
//If there is no easily identified pollution source, display that information along
//with instructions to get to more information.
}
//Create a graph with the LDC (line) and WQ points (scatter)
XYPlot plot = new XYPlot();
XYDataset LDC_data = new XYSeriesCollection(LDC_xy);
XYItemRenderer renderer1 = new XYLineAndShapeRenderer(true, false);
//Create X Axis
ValueAxis xAxis = new NumberAxis("Duration Curve Interval [%]");
xAxis.setRange(0,100);
plot.setDomainAxis(0, xAxis);
//Set log-scale Y axis with scientific notation
LogarithmicAxis yAxis = new LogarithmicAxis(yaxis_title);
yAxis.setAllowNegativesFlag(true);
yAxis.setLog10TickLabelsFlag(true);
plot.setRangeAxis(0, yAxis);
//Put the LDC line data, renderer, and axis into plot
plot.setDataset(0, LDC_data);
plot.setRenderer(0, renderer1);
//Put the line on the first Domain and first Range
plot.mapDatasetToDomainAxis(0, 0);
plot.mapDatasetToRangeAxis(0, 0);
//Create mQn Line
if(mQn != 0){//only show mQn if it is not zero
XYDataset mQn_data = new XYSeriesCollection(mQn_xy);
XYItemRenderer renderer2 = new XYLineAndShapeRenderer(true, false);
renderer2.setSeriesPaint(0, Color.black);
plot.setDataset(1,mQn_data);
plot.setRenderer(1,renderer2);
plot.mapDatasetToDomainAxis(1,0);
plot.mapDatasetToRangeAxis(1,0);
}
//Create user data points
if(sortedData_user.length != 0){//only show user points if it is not zero
//User LDC line
XYDataset user_data = new XYSeriesCollection(LDC_user);
XYItemRenderer renderer = new XYLineAndShapeRenderer(false, true);
renderer.setSeriesPaint(0, Color.red);
plot.setDataset(2,user_data);
plot.setRenderer(2,renderer);
plot.mapDatasetToDomainAxis(2,0);
plot.mapDatasetToRangeAxis(2,0);
}
if(WQdata_user.length != 0){//only show user points if it is not zero
//User WQ points
XYDataset user_wq_data = new XYSeriesCollection(WQ_pts_user);
XYItemRenderer renderer = new XYLineAndShapeRenderer(false, true);
renderer.setSeriesShape(0, new Rectangle2D.Double(-3.0, 3.0, 6.0, 6.0));
renderer.setSeriesPaint(0, new Color(105, 0, 255));
plot.setDataset(4,user_wq_data);
plot.setRenderer(4,renderer);
plot.mapDatasetToDomainAxis(4,0);
plot.mapDatasetToRangeAxis(4,0);
}
if(seasonalWQ_user.length != 0){//only show user points if it is not zero
//User seasonal WQ points
XYDataset user_seasonal_wq_data = new XYSeriesCollection(Seasonal_WQ_pts_user);
XYItemRenderer renderer = new XYLineAndShapeRenderer(false, true);
renderer.setSeriesShape(0, new Rectangle2D.Double(-1.5, 4.5, 3.0, 3.0));
renderer.setSeriesPaint(0, Color.gray);
plot.setDataset(3,user_seasonal_wq_data);
plot.setRenderer(3,renderer);
plot.mapDatasetToDomainAxis(3,0);
plot.mapDatasetToRangeAxis(3,0);
}
//Create the scatter data and renderer
XYDataset scatter_data3 = new XYSeriesCollection(WQ_pts);
XYDataset scatter_data4 = new XYSeriesCollection(Seasonal_WQ_pts);
XYItemRenderer renderer3 = new XYLineAndShapeRenderer(false, true);
XYItemRenderer renderer4 = new XYLineAndShapeRenderer(false, true);
renderer3.setSeriesShape(0, new Rectangle2D.Double(-3.0, 3.0, 6.0, 6.0));
renderer4.setSeriesShape(0, new Rectangle2D.Double(-1.5, 4.5, 3.0, 3.0));
renderer3.setSeriesPaint(0, Color.green);
renderer4.setSeriesPaint(0, Color.black);
//Put the scatter data and renderer into plot
plot.setDataset(7, scatter_data3);
plot.setDataset(6, scatter_data4);
plot.setRenderer(7, renderer3);
plot.setRenderer(6, renderer4);
//Put the scatters on the first Domain and first Range
plot.mapDatasetToDomainAxis(6, 0);
plot.mapDatasetToDomainAxis(7, 0);
plot.mapDatasetToRangeAxis(6, 0);
plot.mapDatasetToRangeAxis(7, 0);
//Create box plot of WQ points
Graphing graphing = new Graphing();
boolean showOutliers = false, showExtremeOutliers = false;
int seriesIndex = 8;
if(high_data.size() > 4){
//Create quartile rectangle, min-max line, and median line
Object[] returnArray = graphing.boxplot_shapes(plot, 5, high_data, seriesIndex, showOutliers, showExtremeOutliers);
plot = (XYPlot) returnArray[0];
showOutliers = (Boolean) returnArray[1];
showExtremeOutliers = (Boolean) returnArray[2];
seriesIndex = (Integer) returnArray[3];
}
if(moist_data.size() > 4){
//Create quartile rectangle, min-max line, and median line
Object[] returnArray = graphing.boxplot_shapes(plot, 25, moist_data, seriesIndex, showOutliers, showExtremeOutliers);
plot = (XYPlot) returnArray[0];
showOutliers = (Boolean) returnArray[1];
showExtremeOutliers = (Boolean) returnArray[2];
seriesIndex = (Integer) returnArray[3];
}
if(mid_data.size() > 4){
//Create quartile rectangle, min-max line, and median line
Object[] returnArray = graphing.boxplot_shapes(plot, 50, mid_data, seriesIndex, showOutliers, showExtremeOutliers);
plot = (XYPlot) returnArray[0];
showOutliers = (Boolean) returnArray[1];
showExtremeOutliers = (Boolean) returnArray[2];
seriesIndex = (Integer) returnArray[3];
}
if(dry_data.size() > 4){
//Create quartile rectangle, min-max line, and median line
Object[] returnArray = graphing.boxplot_shapes(plot, 75, dry_data, seriesIndex, showOutliers, showExtremeOutliers);
plot = (XYPlot) returnArray[0];
showOutliers = (Boolean) returnArray[1];
showExtremeOutliers = (Boolean) returnArray[2];
seriesIndex = (Integer) returnArray[3];
}
if(low_data.size() > 4){
//Create quartile rectangle, min-max line, and median line
Object[] returnArray = graphing.boxplot_shapes(plot, 95, low_data, seriesIndex, showOutliers, showExtremeOutliers);
plot = (XYPlot) returnArray[0];
showOutliers = (Boolean) returnArray[1];
showExtremeOutliers = (Boolean) returnArray[2];
seriesIndex = (Integer) returnArray[3];
}
//Graph a LDC for each year in time period
String currentYear = start.substring(0,4);
String finalYear = end.substring(0,4);
boolean moreYears = xyRanks.length > 0;
while(moreYears){
//Get current year's data and graph it
String[][] partialData = doubleArray.getYearsData(sortedData_combined, currentYear);
double[][] partialRanks = doubleArray.weibullPlottingPosition(partialData);
for(int i=0; i<partialRanks.length; i++){
partialRanks[i][1] = partialRanks[i][1] * conversion * wqTarget;
}
graphing.graphSeries(plot, partialRanks, Color.lightGray, seriesIndex);
seriesIndex++;
//Save results for output for JHighCharts
String[] partialLDCData_x = new String[xyRanks.length];
String[] partialLDCData_y = new String[xyRanks.length];
for(int i=0; i<xyRanks.length; i++){
partialLDCData_x[i] = "-1";//value
partialLDCData_y[i] = "-1";//value
try{
partialLDCData_x[i] = String.valueOf(partialRanks[i][0]);//x
partialLDCData_y[i] = String.valueOf(partialRanks[i][1]);//y
}catch(IndexOutOfBoundsException e){
//do nothing as it already has a -1 value
}
}
graphData = doubleArray.appendcolumn_Matrix(graphData, partialLDCData_x);//Add x points
graphData = doubleArray.appendcolumn_Matrix(graphData, partialLDCData_y);//Add y points
//Determine the next data year to continue looping over
int nextYear = Integer.parseInt(currentYear) + 1;
if(finalYear.compareToIgnoreCase(String.valueOf(nextYear)) >= 0){
currentYear = String.valueOf(nextYear);
}else{
moreYears = false;
}
}
//Reformat WQ and Seasonal WQ data for use with JHighCharts
String[] partialWQdata_x = new String[xyRanks.length];
String[] partialWQdata_y = new String[xyRanks.length];
String[] partialSeasonalWQdata_x = new String[xyRanks.length];
String[] partialSeasonalWQdata_y = new String[xyRanks.length];
for(int i=0; i<xyRanks.length; i++){
//Try WQ data
partialWQdata_x[i] = "-1";//value
partialWQdata_y[i] = "-1";//value
partialSeasonalWQdata_x[i] = "-1";//value
partialSeasonalWQdata_y[i] = "-1";//value
try{
partialWQdata_x[i] = String.valueOf(WQ_pts.getX(i));//x
partialWQdata_y[i] = String.valueOf(WQ_pts.getY(i));//y
}catch(IndexOutOfBoundsException e){
//do nothing as it already has a -1 value
}
//Try Seasonal WQ data
try{
partialSeasonalWQdata_x[i] = String.valueOf(Seasonal_WQ_pts.getX(i));//x
partialSeasonalWQdata_y[i] = String.valueOf(Seasonal_WQ_pts.getY(i));//y
}catch(IndexOutOfBoundsException e){
//do nothing as it already has a -1 value
}
}
graphData = doubleArray.appendcolumn_Matrix(graphData, partialWQdata_x);//Add WQ x points
graphData = doubleArray.appendcolumn_Matrix(graphData, partialWQdata_y);//Add WQ y points
graphData = doubleArray.appendcolumn_Matrix(graphData, partialSeasonalWQdata_x);//Add Seasonal WQ x points
graphData = doubleArray.appendcolumn_Matrix(graphData, partialSeasonalWQdata_y);//Add Seasonal WQ y points
//Output XY data for use with JHighCharts
doubleArray.writeXYseries(mainFolder, graphData, getDCgraphOutput().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 = "Load Duration Curve for " + database + " Station " + stationID + "; " + stationName;
JFreeChart chart = new JFreeChart(graphTitle, graphing.titleFont, plot, true);
//Set legend Font
LegendTitle legendTitle = chart.getLegend();
legendTitle.setItemFont(graphing.masterFont);
//Write a results file containing the flow duration curve interval (exceedance values) and their corresponding discharge values
writeResults(xyRanks, mainFolder, yaxis_title);
//Save resulting graph
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 station '" + stationName + "'. Error: USGSLDC0003");
System.out.println(e.toString());
}
Object[] returnArray = {paragraphTitle, totalCount};
return returnArray;
}
private void calculatePercentileSummmary(double[][] xyRanks, String title){
DoubleMath doubleMath = new DoubleMath();
DoubleArray doubleArray = new DoubleArray();
//Hard coded percentile summary table
double[] frqDisp = {99, 95, 90, 75, 50, 25, 10, 5, 1};
String[] frqDispString = {"99", "95", "90", "75", "50", "25", "10", "5", "1"};
//Interpolate desired percentiles
double[] yFinal = doubleMath.linearInterpolation(doubleArray.getColumn(xyRanks,0), doubleArray.getColumn(xyRanks,1), frqDisp);
//Round results for summary table
yFinal = doubleMath.roundColumn(yFinal, 100);
//Create summary table in a single string
this.summaryTable = "Exceedance Percentile\t" + title; //row 1
for(int i=0; i<frqDisp.length; i++){
this.summaryTable = this.summaryTable + "\n" + frqDispString[i] + "\t" + String.valueOf(yFinal[i]);
}
}
/**
* 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
*/
public void writeResults(double[][] results, String partialpath, String resultType) throws IOException{
//Remove zero results
ArrayList<String> finalResults = new ArrayList<String>();
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 = partialpath + File.separator + getDurationCurve_results().getName();
FileWriter writer = new FileWriter(path, false);
PrintWriter printLine = new PrintWriter(writer);
//Add Headers to text file
printLine.printf("%s" + "%n", "Flow Duration Interval (Excedence Probability based on Weibull Plotting Position Ranking)\t" + resultType);
//Output data to text file
for(int i=0; i < finalResults.size(); i++) {
printLine.printf("%s" + "%n", finalResults.get(i));
}
System.out.println("Text File located at:\t" + path);
printLine.close();
writer.close();
}
/**
* Create the dynamic paragraph requested
* @param paragraphTitle title of the desired paragraph
* @return the dynamic paragraph to be displayed to the user
*/
public String dynamicParagraph(String paragraphTitle) {
String dynamic_paragraph = paragraphTitle + "\n\n";
//Get today's date for the source reference
Date currentDate = new Date();
SimpleDateFormat sourceDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String today = sourceDateFormat.format(currentDate);
String sourceText = "";
//Determine if current source is USGS or STORET to give credit for the data
if(database.equalsIgnoreCase("USGS")){
sourceText = "Stream flow data and water quality test data courtesy of the U.S. Geological Survey, National Water Information System: Web Interface. http://waterdata.usgs.gov/nwis, accessed: " + today;
}else if(database.equalsIgnoreCase("STORET")){
sourceText = "Stream flow data and water quality test data courtesy of the U.S. Environmental Protection Agency, STORET. http://www.epa.gov/storet/index.html accessed: " + today;
}else if(database.equalsIgnoreCase("CDWR")){
sourceText = "Stream flow data and water quality test data courtesy of the Colorado Division of Water Resources, CDWR. http://www.dwr.state.co.us accessed: " + today;
}
this.dataSource = sourceText;
//Create the correct dynamic paragraph
if(paragraphTitle.equals("Point Sources and Wastewater Sources: ")){
dynamic_paragraph = dynamic_paragraph + "When the target load duration curve is exceeded primarily under Low Flow and Dry Conditions flow intervals; point sources and wastewater source pollution are the likely cause. Note: in order to have wastewater pollution source there must be an upstream wastewater plant discharging into the watershed. Solutions to this problem may include but are not limited to: point source controls, septic system inspection programs, and sanitary sewer overflow repair (Cleland 2003). In urban areas solutions might involve detecting illicit connections from storm water programs (Cleland 2007). In agricultural settings, solutions may include fencing livestock from riparian areas along waterways, or other similar basic management practices (Cleland 2007).\n";
dynamic_paragraph = dynamic_paragraph + "The grey graphed lines are duration curves for each individual year within the analysis period.\n";
}else if(paragraphTitle.equals("Upper Flow Sources: ")){
dynamic_paragraph = dynamic_paragraph + "Upper flow sources are likely when the target is met except under the High Flows, Moist Conditions, and the upper end of Mid-Range Flows. The higher flows under these regions, primarily above 55 percent, are likely due to large amounts of storm runoff. These flows and sources are similar to those of wet-weather, but are due to larger runoff events and higher flows. The increased pollution may be due to combined sewer overflows, storm water runoff from both up river and nearby, and pasture runoff from agricultural areas (Cleland 2003). Solutions to this pollutant problem may include but are not limited to: combined sewer overflow repair, pasture management, and up river and nearby storm runoff solutions such as riparian buffers (Cleland 2003). In agricultural areas remediation efforts aimed towards grassed waterways, conservation tillage, pasture management practices, and contour strips may help alleviate these pollutions issues (Cleland 2007).\n";
dynamic_paragraph = dynamic_paragraph + "The grey graphed lines are duration curves for each individual year within the analysis period.\n";
}else if(paragraphTitle.equals("Wet-Weather Sources: ")){
dynamic_paragraph = dynamic_paragraph + "When the target load duration curve for pollution concentration is satisfied under the Low Flow and Dry Conditions flow intervals but becomes exceeded during Mid-Range Flows and Moist Conditions primarily above 70 percent flow interval, wet-weather related pollution sources are likely. These mid-size flow rates tend to be the result of light storm runoff with increased pollutant transportation through riparian areas and from impervious regions. Some solutions to these problems may include but are not limited to: riparian buffer zones, agricultural conservation tillage, contour strips, and grassed waterways (Cleland 2007). Combined sewer overflow repair, pet waste ordinances, and hobby farm livestock education may also alleviate the pollution problems due to these wet-weather sources (Cleland 2003).\n";
dynamic_paragraph = dynamic_paragraph + "The grey graphed lines are duration curves for each individual year within the analysis period.\n";
}else if(paragraphTitle.equals("Erosion Sources: ")){
dynamic_paragraph = dynamic_paragraph + "Erosion based pollutant sources are likely when the target is met under all the flow intervals except under the High Flows and upper end of the Moist Conditions. Due to the higher velocities of the water during these flows, bank erosion and channel scour are more likely to occur and release pollutants (particularly sediment) into the water. Solutions to this pollutant problem may include but are not limited to: river bank stabilization efforts and channel protection policies (Cleland 2007). Pollutants related to sediment concentration will be those most affected by these efforts.\n";
dynamic_paragraph = dynamic_paragraph + "The grey graphed lines are duration curves for each individual year within the analysis period.\n";
}else if(paragraphTitle.equals("Multiple Pollution Sources: ")){
dynamic_paragraph = dynamic_paragraph + "Most of the flow intervals contain many points which exceed the target. No single pollution source is likely. Please click 'Further Information' for more pollutant identification help.\n";
dynamic_paragraph = dynamic_paragraph + "The grey graphed lines are duration curves for each individual year within the analysis period.\n";
}else if(paragraphTitle.equals("Flow Duration Curve Overview: ")){
dynamic_paragraph = dynamic_paragraph + "A flow duration curve (FDC) is the ranked graphing of river flows on a scale of percent exceedance. For example a flow value associated with the flow interval of 15% means that particular flow value is met or exceeded only 15% of the time. This graph is meant to give a quick overview of the flow ranges, variability, and probability of flows of a river segment during the different flow periods of a river; which are High Flows from 0 to 10 percent flow interval, Moist Conditions 10-40, Mid-Range Flows 40-60, Dry Conditions 60-90, and Low Flows 90-100 (Cleland 2003).\n";
dynamic_paragraph = dynamic_paragraph + "The grey graphed lines are duration curves for each individual year within the analysis period.\n";
}else{
dynamic_paragraph = dynamic_paragraph + "Although some observed points may exceed the target curve's concentration there is no single apparent pollutant source.\n";
dynamic_paragraph = dynamic_paragraph + "Please click 'Further Model Information' for more pollutant source identification help.\n";
}
//Create references for paragraph
dynamic_paragraph = dynamic_paragraph + "References:\n";
dynamic_paragraph = dynamic_paragraph + sourceText + "\n";
dynamic_paragraph = dynamic_paragraph + "Cleland, B. R. November 2003. TMDL Development from the 'Bottom Up' Part III: Duration Curves and Wet-Weather Assessments. National TMDL Science and Policy 2003.\n";
dynamic_paragraph = dynamic_paragraph + "Cleland, B. R. August 2007. An Approach for Using Load Duration Curves in the Development of TMDLs. National TMDL Science and Policy 2007.";
return dynamic_paragraph;
}
/**
* 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 LDC model
* It calls the subfunctions based on user selection/inputs.
* Calls STORET or USGS database queries and their respective subfunctions
* @param args Input variables for the LDC model to run, specifically which model is desired to run, STORET vs. USGS, FDC vs. LDC.
* @throws IOException
* @throws InterruptedException
*/
public void run() throws IOException, InterruptedException, ParseException, Exception {
//If no date input, make it the maximum of available data
if(beginDate == null || beginDate.equalsIgnoreCase("")){
beginDate = "1850-01-01";
}
if(endDate == null || endDate.equalsIgnoreCase("")){
// Pull current date for upper limit of data search
DateFormat desiredDateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date currentDate = new Date();
endDate = desiredDateFormat.format(currentDate);
}
//If no specified season, create default
if(seasonBegin == null || seasonBegin.equalsIgnoreCase("") || seasonEnd == null || seasonEnd.equalsIgnoreCase("")){
seasonBegin = "April";
seasonEnd = "October";
}
//Determine which Duration Curve method to run, FDC or LDC
if(modelType.equalsIgnoreCase("FDC")){
createFDC();
}else if(modelType.equalsIgnoreCase("LDC")){
createLDC();
}
}
public static void main(String[] args) throws IOException, InterruptedException, Exception {
//Define and run model
guiDC_Model dc_Model = new guiDC_Model();
dc_Model.run();
}
}