guiDC_Model.java [src/java/cfa] Revision: ed4e4a960882eb4a0da28bca0df90ba790549b15 Date: Fri Oct 18 10:18:06 MDT 2013
package cfa;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
/**'
* Last Updated: 26-August-2013
* @author Tyler Wible
* @since 12-June-2011
*/
public class guiDC_Model {
//Inputs
String mainFolder = "C:/Projects/TylerWible/CodeDirectories/NetBeans/CSIP/data/CFA";
String modelType = "FDC";//"LDC";//
String organizationName = "USGS";//"Colorado Dept. of Public Health & Environment";//
String stationID = "06752280";//"000028";//
String stationName = "CACHE LA POUDRE RIV AB BOXELDER CRK NR TIMNATH, CO";
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 = "April";
String seasonEnd = "October";
String mQnPeriod = "false";//"7Q10-year";//
String userData = "";//"Date\tFlow\n1999-04-29\t8.3\n1999-05-09\t60.2\n1999-05-29\t20.1$$Date\tname\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 lowFlowErrorMessage = "";
double mQnVal = -1;
//Gets
public File getDurationCurve_summary() {
return new File(mainFolder, "duration_curve_summary.txt");
}
public File getDurationCurve_results() {
return new File(mainFolder, "duration_curve_results.txt");
}
public String getGraph() {
return "duration_curve_graph.jpg";
}
public String getFlowLen() {
return flowLen;
}
public String getWQLen() {
return wqLen;
}
public String getWQUnits() {
return wqUnits;
}
public String getLowFlowErrorMessage(){
return lowFlowErrorMessage;
}
public double getMQNval(){
return mQnVal;
}
public String getStart() {
return start;
}
public String getEnd() {
return end;
}
//Sets
public void setMainFolder(String mainFolder) {
this.mainFolder = mainFolder;
}
public void setModelType(String modelType) {
this.modelType = modelType;
}
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 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
*/
public String[] createFDC() throws IOException, InterruptedException{
//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 low_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
//Initialize the DurationCurve class for subfunctions
DurationCurve durationCurve = new DurationCurve();
DoubleArray doubleArray = new DoubleArray();
mQnFlow mqn = new mQnFlow();
//Check if user wants mQnFlow calculated and displayed
boolean mQnHide = mQnPeriod.contains("false");
String[][] sortableData = new String[0][2];
if(organizationName.equalsIgnoreCase("USGS")){
//Search for USGS flow data
USGS_Data usgs_Data = new USGS_Data();
sortableData = usgs_Data.USGS_read_FDC(stationID, beginDate, endDate);
//Don't get try getting WQ-flow data unless there is minimal flow data for curve
if(sortableData.length < 10){
//Retrieve WQ data from USGS website
String[][] WQData = usgs_Data.USGS_read_LDC(stationID);
//Extract USGS water quality code 00061 for dischage in cfs
String[][] WQFlow1 = usgs_Data.minimizeUSGSWQdata(WQData, "00061", beginDate, endDate);
//Extract USGS water quality code 30209 for discharge test in m^3/s (cms)
String[][] WQFlow2 = usgs_Data.minimizeUSGSWQdata(WQData, "30209", beginDate, endDate);
//Convert the m^3 to ft^3/s
for(int i=0; i<WQFlow2.length; i++){
WQFlow2[i][1] = Double.toString((Double.parseDouble(WQFlow2[i][1])*(3.2808399*3.2808399*3.2808399)));
}
//combine the WQ flows (cfs and converted cms) into a single variable to be used with the Flowdata
String[][] WQDataflows = usgs_Data.mergeMinimizedWQdata(WQFlow1, WQFlow2);
//Combine flow data and WQ flow data into a variable of dates and flow values to be sorted
sortableData = usgs_Data.mergeMinimizedWQdata(sortableData, WQDataflows);
}
}else if(organizationName.equalsIgnoreCase("UserData")){
//Find the user uploaded data file and uses this for a timeseries graph
User_Data user_Data = new User_Data();
sortableData = user_Data.readUserFile(userData, beginDate, endDate);
}else{
//Search for STORET peak flow data
STORET_Data storet_Data = new STORET_Data();
String zip_location = storet_Data.downloadSTORET(mainFolder, organizationName, stationID, "flow", beginDate, endDate);
//Unzip results file and extract all flow data
sortableData = storet_Data.Unzip_STORETDownloadFiles(zip_location, "flow", true);
}
//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, beginDate, endDate);
if(sortableData_user.length==0){
String[] errorMessage = {"There is no available uploaded data for station '" + stationID + "' and the specified date range"};
writeError(errorMessage);
}
}
//Check if any data exists
if (sortableData.length==0){
String[] errorMessageArray = {"There is no available flow data for station '" + stationID + "' and the specified date range. Error: ", "USGSFDC0002"};
writeError(errorMessageArray);
}
//Sort the Data by date to remove duplicate date entries
String[][] sortedData = durationCurve.removeDuplicateDates(sortableData);
String[][] sortedData_user = durationCurve.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);
//While the dataset is still sorted by date, pull out the mQn value and start/end dates for analysis summary
int low_m=0, low_n=0;
String low_period = "";
if(mQnHide == false){
//Pull out mQn-period variables
String mString = mQnPeriod.substring(0,mQnPeriod.indexOf("Q"));
String nString = mQnPeriod.substring(mQnPeriod.indexOf("Q")+1,mQnPeriod.indexOf("-"));
low_period = mQnPeriod.substring(mQnPeriod.indexOf("-")+1,mQnPeriod.length());
low_m = Integer.parseInt(mString);
low_n = Integer.parseInt(nString);
}
String[] low_returnValue = mqn.main(low_m,low_n,sortedData_combined,low_period,"minimum");
String low_MQNmessage = low_returnValue[0];
double low_mQn = Double.parseDouble(low_returnValue[1]);
this.start = sortedData_combined[0][0];
this.end = sortedData_combined[sortedData_combined.length - 1][0];
this.flowLen = String.valueOf(sortedData_combined.length);
//To prep. for graphing sort userdata by flow
Arrays.sort(sortedData_user, new FlowComparator());
//Create Custom Flow Duration Curve graph
durationCurve.graphFDC(mainFolder, stationID + " - " + stationName, start, end, sortedData_combined, sortedData_user, low_mQn, low_m, low_n, mQnHide);
//Attach analysis summary to return object
DoubleMath doubleMath = new DoubleMath();
if(!mQnHide){
if(!low_MQNmessage.equalsIgnoreCase("")){//if the MQNmessage is not blank add it to the dynamic summary
this.lowFlowErrorMessage = low_MQNmessage;
String[] errorParagraph = {low_MQNmessage};
writeError(errorParagraph);
}else{
this.mQnVal = doubleMath.round(low_mQn, 2);
}
}
//Create Display Paragraph
String[] partial_Paragraph = durationCurve.dynamicParagraph("Flow Duration Curve Overview: ","USGS");
return partial_Paragraph;
}
/**
* 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
*/
public String[] createLDC() throws IOException, InterruptedException {
// 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
//Initialize the DurationCurve class for subfunctions
DurationCurve durationCurve = new DurationCurve();
DoubleArray doubleArray = new DoubleArray();
mQnFlow mqn = new mQnFlow();
//Correct Inputs
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
wqTest = wqTest.substring(0,5);//pull just the 5 digit USGS WQ code
//Get Units and conversion for current WQ test
String units = durationCurve.USGSwqUnits(wqTest);
double conversion = durationCurve.USGSwqConversion(units);
String endUnits = durationCurve.USGSwqEndUnits(units);
//Check if user wants mQnFlow calculated and displayed
boolean mQnHide = mQnPeriod.contains("false");
String[][] sortableData = new String[0][2];
String[][] WQdata = new String[0][2];
if(organizationName.equalsIgnoreCase("USGS")){
//Search for USGS flow data
USGS_Data usgs_Data = new USGS_Data();
sortableData = usgs_Data.USGS_read_FDC(stationID, beginDate, endDate);
//Retrieve WQ data from USGS website
USGS_Data usgs_data = new USGS_Data();
WQdata = usgs_data.USGS_read_LDC(stationID);
//If there is minimal flow data try getting WQ-flow data
if(sortableData.length < 10){
//Extract USGS water quality code 00061 for dischage in cfs
String[][] WQFlow1 = usgs_data.minimizeUSGSWQdata(WQdata, "00061", beginDate, endDate);
//Extract USGS water quality code 30209 for discharge test in m^3/s (cms)
String[][] WQFlow2 = usgs_data.minimizeUSGSWQdata(WQdata, "30209", beginDate, endDate);
//Convert the m^3 to ft^3/s
for(int i=0; i<WQFlow2.length; i++){
WQFlow2[i][1] = Double.toString((Double.parseDouble(WQFlow2[i][1])*(3.2808399*3.2808399*3.2808399)));
}
//combine the WQ flows (cfs and converted cms) into a single variable to be used with the Flowdata
String[][] WQDataflows = usgs_data.mergeMinimizedWQdata(WQFlow1, WQFlow2);
//Combine flow data and WQ flow data into a variable of dates and flow values to be sorted
sortableData = usgs_data.mergeMinimizedWQdata(sortableData, WQDataflows);
}
//Extract USGS water quality code for current wqTest only
WQdata = usgs_data.minimizeUSGSWQdata(WQdata, wqTest, beginDate, endDate);
}else if(organizationName.equalsIgnoreCase("UserData")){
//Find the user uploaded data file and uses this for a timeseries graph
User_Data user_Data = new User_Data();
Object[] returnArray = user_Data.readUserFileLDC(userData, stationID, beginDate, endDate);
sortableData = (String[][]) returnArray[0];
WQdata = (String[][]) returnArray[1];
//Use the header to get the WQ test name
String[] headers = user_Data.getHeadersLDC(userData);
WQlabel = headers[1];
endUnits = "mg/L";
}else{
//Search for STORET flow data
STORET_Data storet_Data = new STORET_Data();
String zipLocation = storet_Data.downloadSTORET(mainFolder, organizationName, stationID, "flow", beginDate, endDate);
//Unzip results file and extract all flow and WQ results
sortableData = storet_Data.Unzip_STORETDownloadFiles(zipLocation, "flow", false);
WQdata = storet_Data.Unzip_STORETDownloadFiles(zipLocation, wqTest, true);
endUnits = "mg/L";
}
//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[] returnArray = user_Data.readUserFileLDC(userData, stationID, beginDate, endDate);
sortableData_user = (String[][]) returnArray[0];
WQdata_user = (String[][]) returnArray[1];
if(sortableData_user.length==0){
String[] errorMessage = {"There is no available uploaded data for station '" + stationID + "' and the specified date range"};
writeError(errorMessage);
}
if(WQdata_user.length==0){
String[] errorMessage = {"There is no available uploaded data for station '" + stationID + "' and the specified date range"};
writeError(errorMessage);
}
}
//Check if any data exists
if(sortableData.length==0){
String[] errorMessageArray = {"There is no available flow data for station '" + stationID + "' and the specified date range."};
writeError(errorMessageArray);
}
if(WQdata.length==0){
String[] errorMessageArray = {"There are no available '" + wqTest + "' water quality tests for station " + stationID+ " by: " +
organizationName + " and the specified date range"};
writeError(errorMessageArray);
}
//Sort the Data by date to remove duplicate date entries
String[][] sortedData = durationCurve.removeDuplicateDates(sortableData);
String[][] sortedData_user = durationCurve.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);
//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;
String period = "";
if(mQnHide == false){
//Pull out mQn-period variables
String mString = mQnPeriod.substring(0,mQnPeriod.indexOf("Q"));
String nString = mQnPeriod.substring(mQnPeriod.indexOf("Q")+1,mQnPeriod.indexOf("-"));
period = mQnPeriod.substring(mQnPeriod.indexOf("-")+1,mQnPeriod.length());
m = Integer.parseInt(mString);
n = Integer.parseInt(nString);
}
String[] returnValue = mqn.main(m,n,sortedData_combined,period,"minimum");
String MQNmessage = returnValue[0];
double mQn = Double.parseDouble(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);
//To prep. for graphing sort user data by flow
Arrays.sort(sortedData_user, new FlowComparator());
//Convert combined data and user data from flows to load values as well (for graphing)
for(int i=0; i<sortedData_combined.length; i++){
double value = Double.parseDouble(sortedData_combined[i][1]);
sortedData_combined[i][1] = String.valueOf(value * conversion);
}
for(int i=0; i<sortedData_user.length; i++){
double userValue = Double.parseDouble(sortedData_user[i][1]);
sortedData_user[i][1] = String.valueOf(userValue * conversion);
}
String[][] seasonalWQ_combined = durationCurve.getSeasonalWQData(WQdata_combined, seasonBegin, seasonEnd);
String[][] seasonalWQ_user = durationCurve.getSeasonalWQData(WQdata_user, seasonBegin, seasonEnd);
//Graph resulting LDC data
String graphTitle = "Load Duration Curve for Station No. " + stationID + " - " + stationName;
String yaxisTitle = WQlabel + " [" + endUnits + "]";
Object[] returnArray = durationCurve.graphLDC(mainFolder, stationName, start, end, graphTitle, yaxisTitle, wqTest,
conversion, wqTarget, 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
DoubleMath doubleMath = new DoubleMath();
if(mQnHide){
this.wqLen = String.valueOf(totalCount);
}else{
if(!MQNmessage.equalsIgnoreCase("")){//if the MQNmessage is not blank add it to the dynamic summary
this.wqLen = String.valueOf(totalCount);
this.wqUnits = units;
this.lowFlowErrorMessage = MQNmessage;
}else{
this.wqLen = String.valueOf(totalCount);
this.wqUnits = units;
this.mQnVal = doubleMath.round(mQn, 2);
}
}
//Create display paragraph based on the above summary (firstLine) and the provided paragraphTitle for the LDC
String[] displayParagraph = durationCurve.dynamicParagraph(paragraphTitle, organizationName);
return displayParagraph;
}
/**
* Writes out the dynamically created paragraph to be displayed to the user along with the LDC graph
* @param dynamicParagraph string array to be written as each line of the text file
* @param partialpath the partial folder path of the file to be written
* @throws IOException
*/
public void writeParagraph(String[] dynamicParagraph, String partialpath) throws IOException{
String path = partialpath + File.separator + "duration_curve_summary.txt";
FileWriter writer = new FileWriter(path, false);
PrintWriter print_line = new PrintWriter(writer);
//Output data to text file
for(int i = 0; i < dynamicParagraph.length; i++) {
print_line.printf("%s" + "%n", dynamicParagraph[i]);
System.out.println(dynamicParagraph[i]);
}
System.out.println("Text File located at:\t" + path);
print_line.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(String[] error) throws IOException{
//Output data to text file
String errorContents = error[0];
for(int i=1; i<error.length; i++){
errorContents = errorContents + "\n" + error[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 {
//If no date input, make it the maximum of available data
if(beginDate == null || beginDate.equalsIgnoreCase("")){
beginDate = "1900-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
String[] dynamicParagraph = new String[9];
if(modelType.equalsIgnoreCase("FDC")){
dynamicParagraph = createFDC();
}else if(modelType.equalsIgnoreCase("LDC")){
dynamicParagraph = createLDC();
}
//Write dynamic Paragraph to text file
writeParagraph(dynamicParagraph, mainFolder);
}
public static void main(String[] args) throws IOException, InterruptedException, Exception {
//Define and run model
guiDC_Model dc_Model = new guiDC_Model();
dc_Model.run();
}
}