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();
    }
}