guiDrought_Model.java [src/java/cfa] Revision: ffc412d47d72be5013c8a0a59c535082523410cb Date: Tue Dec 16 12:11:46 MST 2014
package cfa;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
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.annotations.XYAnnotation;
import org.jfree.chart.annotations.XYDrawableAnnotation;
import org.jfree.chart.annotations.XYPointerAnnotation;
import org.jfree.chart.annotations.XYTextAnnotation;
import org.jfree.chart.annotations.XYTitleAnnotation;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.DateTickMarkPosition;
import org.jfree.chart.axis.LogarithmicAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.axis.TickUnits;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StackedXYBarRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeTableXYDataset;
import org.jfree.data.time.Year;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.RectangleAnchor;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.TextAnchor;
/**
* Last Updated: 16-December-2014
* @author Tyler Wible
* @since 10-July-2012
*/
public class guiDrought_Model {
//Inputs
String mainFolder = "C:/Projects/TylerWible/CodeDirectories/NetBeans/CSIP/data/CFA/Drought";
String database = "USGS";//"CDWR";//"STORET";//"UserData";//
String organizationName = "USGS";//"Co. Division of Water Resources";//"Colorado Dept. of Public Health & Environment";//
String stationID = "06752000";//"CLAGRECO";//"000028";//
String stationName = "CACHE LA POUDRE RIV AT MO OF CN, NR FT COLLINS, CO";//"Cache La Poudre Near Greeley";//"BIG THOMPSON R NEAR MOUTH";//
String beginDate = "";//"1900-01-01";//
String endDate = "";//"2002-01-01";//
String lambdaString = "optimize";//"1";//
String action = "all";//"optimizeModel";//"optimizeParameters";//"useParameters";//
String phiValues = "1";
String thetaValues = "";
double droughtLimit = -1;
String userData = "";//"Date\tFlow\n1997-04-29\t8.3\n1998-05-09\t60.2\n1999-05-29\t20.1";
boolean mergeDatasets = false;//true;//
String mergeMethod = "user";//"public";//"max";//"average";//"min";//
//Outputs
String len = "-1";
String start = "?";
String end = "?";
String dataSource = "?";
//Gets
public File getResult() {
return new File(mainFolder, "drought_summary.txt");
}
public String getTimeseriesGraph(){
return "drought1_timeseries.jpg";
}
public String getColumnChart(){
return "drought2_column.jpg";
}
public String getFittedDataGraph(){
return "drought3_fitted.jpg";
}
public String getProjectedDataGraph(){
return "drought4_projected.jpg";
}
public String getDroughtRecurrenceGraph(){
return "drought5_recurrence.jpg";
}
public String[] get_optimizeModel_output() {
if(thetaValues.equalsIgnoreCase("")){
//The drought model used is AR(p) so only 1 graph
String[] graphs = new String[1];
graphs[0] = "optimize_drought_graph1.jpg";
return graphs;
}else{
//The drought model used is ARMA(p,q) so only 2 graph
String[] graphs = new String[2];
graphs[0] = "optimize_drought_graph3.jpg";
graphs[1] = "optimize_drought_graph4.jpg";
return graphs;
}
}
public File getTimeseriesOutput(){
//This output file is for use with JSHighCharts
return new File(mainFolder, "drought1_timeseries.out");
}
public File getColumnChartOutput(){
//This output file is for use with JSHighCharts
return new File(mainFolder, "drought2_column.outt");
//Note that this file has a different extension as a flag for eRAMS to parse the first column as strings not numbers
}
public File getFittedGraphOutput(){
//This output file is for use with JSHighCharts
return new File(mainFolder, "drought3_fitted.out");
}
public File getProjectedGraphOutput(){
//This output file is for use with JSHighCharts
return new File(mainFolder, "drought4_projected.out");
}
public File getRecurrenceGraphOutput(){
//This output file is for use with JSHighCharts
return new File(mainFolder, "drought5_recurrence.out");
}
public String getLen() {
return len;
}
public String getStart() {
return start;
}
public String getEnd() {
return end;
}
public String getDataSource(){
return dataSource;
}
public String getDroughtLimit() {
return String.valueOf(droughtLimit);
}
//These 'gets' are to help decide which return function to perform
public String getAction() {
return action;
}
public String getThetaValues() {
return thetaValues;
}
//Sets
public void setMainFolder(String mainFolder) {
this.mainFolder = mainFolder;
}
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 setBeginDate(String beginDate) {
this.beginDate = beginDate;
}
public void setEndDate(String endDate) {
this.endDate = endDate;
}
public void setLambdaString(String lambdaString) {
this.lambdaString = lambdaString;
}
public void setAction(String action) {
this.action = action;
}
public void setPhiValues(String phiValues) {
this.phiValues = phiValues;
}
public void setThetaValues(String thetaValues) {
this.thetaValues = thetaValues;
}
public void setDroughtLimit(double droughtLimit) {
this.droughtLimit = droughtLimit;
}
public void setUserData(String userData) {
this.userData = userData;
}
public void setMergeDatasets(boolean mergeDatasets) {
this.mergeDatasets = mergeDatasets;
}
public void setMergeMethod(String mergeMethod) {
this.mergeMethod = mergeMethod;
}
/**
* Performs a drought analysis on the provided sortedData (daily average stream flow value) and calculates the long-term
* average water supply to be taken as the drought threshold. First it calculates total annual river flows based on the
* provided daily stream flow averages, then calculates the long-term average of these annual flow values, then calculates
* the properties of the droughts (deficit, duration, intensity) compared against the drought threshold. Finally graphs a
* timeseries of the annual flow data against the water demand.
* @param sortedData a string[][] containing daily average stream flow values in the following format: column1 = dates (yyyy-mm-dd format),
* column2 = daily average stream flow values (cfs)
* @return a String[] containing a tab-delimited summary of the droughts (water supply < water demand)
* @throws IOException
*/
public String[] generalDroughtAnalysis(String[][] sortedData) throws IOException{
DoubleMath doubleMath = new DoubleMath();
DoubleArray doubleArray = new DoubleArray();
//Calculate total annual flows
double[][] annualData = sumAnnualFlows(sortedData);
//Get the flow values and calculate the long-term (total) average of them
droughtLimit = doubleMath.meanArithmetic(doubleArray.getColumn(annualData, 1));
//Call built in function to perform the drough analysis and graph the resulting data
String[] droughtInfo = generalDroughtAnalysis(sortedData, droughtLimit);
return droughtInfo;
}
/**
* Performs a drought analysis on the provided sortedData (daily average stream flow value) and the provided long-term
* average water demand specified by the user. First it calculated total annual river flows based on the provided daily
* stream flow averages, then calculates the properties of the droughts (deficit, duration, intensity) compared against
* the water demand. Finally graphs a timeseries of the annual flow data against the water demand.
* @param sortedData a string[][] containing daily average stream flow values in the following format: column1 = dates (yyyy-mm-dd format),
* column2 = daily average stream flow values (cfs)
* @param droughtLimit a double representing the annual drought limit (water demand) on the watershed as specified by the user (in units of acre-ft/year)
* @return a String[] containing a tab-delimited summary of the droughts (water supply < water demand)
* @throws IOException
*/
@SuppressWarnings("unchecked")
public String[] generalDroughtAnalysis(String[][] sortedData,
double droughtLimit) throws IOException{
// //Test AR(p) model on sample data
// double[][] annualData = sumAnnualFlows(sortedData);
// Object[] returnArray = autoRegression.AR(p, annualData);
// annualData = (double[][]) returnArray[0];
// double[][] generatedData = (double[][]) returnArray[1];
// autoRegression.graphAR1Double(annualData, generatedData);
DoubleMath doubleMath = new DoubleMath();
DoubleArray doubleArray = new DoubleArray();
AutoRegression autoRegression = new AutoRegression();
//Calculate total annual flows
double[][] annualData = sumAnnualFlows(sortedData);
//Convert the data to only the stoicastic portion to modeled using AR(1) or ARMA(1,1)
double average = doubleMath.meanArithmetic(doubleArray.getColumn(annualData, 1));
double stDev = doubleMath.StandardDeviationSample(doubleArray.getColumn(annualData, 1));
double[][] stocaisticData = autoRegression.StoicasticConversion(annualData, average, stDev, true);
//Perform a transformation on the stoicastic data to change its distribution to a normal distribution
double lambda = 1.0;
if(lambdaString.equalsIgnoreCase("optimize")){
lambda = autoRegression.BoxCox(stocaisticData);
}else{
lambda = Double.parseDouble(lambdaString);
}
double[][] transformedData = autoRegression.BoxCox(lambda, stocaisticData, true);
// double[][] transformedData = autoRegression.SalasExampleTransformation(stocaisticData, true);
//Run the user specified auto-regressive model on the data
double[][] fittedData = null;
double[][] predictedData = null;
String methodType = "";
Object[] returnArray = autoRegression.AutoregressiveModel(mainFolder, action, phiValues, thetaValues, transformedData);
if(action.equalsIgnoreCase("optimizeParameters")){
//Find the parameters of the regression only and report these back
String[] stringArray = (String[]) returnArray;
//Perform regression with data and graph the resulting data
Object[] dataArray = autoRegression.AutoregressiveModel(mainFolder, "useParameters", stringArray[0], stringArray[1], transformedData);
//Keep the data from the regression and graph it
methodType = (String) dataArray[0];
transformedData = (double[][]) dataArray[1];
fittedData = (double[][]) dataArray[2];
predictedData = (double[][]) dataArray[3];
}else if(action.equalsIgnoreCase("optimizeModel")){
if(thetaValues.equalsIgnoreCase("")){
//Calculate optimal AR(p) model and return the optimal phi values
ArrayList<Double> aic = (ArrayList<Double>) returnArray[0];
ArrayList<Double> bic = (ArrayList<Double>) returnArray[1];
String phi = (String) returnArray[2];
String theta = (String) returnArray[3];
//Graph the AIC/BIC curve for the user
graphAR_AIC_BIC(aic, bic);
String[] optimalValues = {phi + "$$", theta};
return optimalValues;
}else{
//Calculate optimal ARMA(p,q) model and return the optimal phi/theta values
double[][] results = (double[][]) returnArray[0];
String phi = (String) returnArray[1];
String theta = (String) returnArray[2];
//Graph the AIC/BIC curve for the user
graphARMA_AIC_BIC("AIC", results, 3);
graphARMA_AIC_BIC("BIC" , results, 4);
String[] optimalValues = {phi + "$$", theta};
return optimalValues;
}
}else{
//Keep the data from the regression and graph it
methodType = (String) returnArray[0];
transformedData = (double[][]) returnArray[1];
fittedData = (double[][]) returnArray[2];
predictedData = (double[][]) returnArray[3];
}
//Un-transform the stoicastic historic and stoicastic generated data so as to graph real stream flow values
transformedData = autoRegression.BoxCox(lambda, transformedData, false);
fittedData = autoRegression.BoxCox(lambda, fittedData, false);
predictedData = autoRegression.BoxCox(lambda, predictedData, false);
// transformedData = autoRegression.SalasExampleTransformation(transformedData, false);
// fittedData = autoRegression.SalasExampleTransformation(fittedData, false);
// predictedData = autoRegression.SalasExampleTransformation(predictedData, false);
//Convert back from the stoicastic component of the data to the entire original data
double[][] originalData = autoRegression.StoicasticConversion(transformedData, average, stDev, false);
fittedData = autoRegression.StoicasticConversion(fittedData, average, stDev, false);
predictedData = autoRegression.StoicasticConversion(predictedData, average, stDev, false);
//Calculate the droughts, their accumulated deficits, durations, and intensities
DroughtProperties historicDroughtProps = new DroughtProperties(annualData, droughtLimit);
DroughtProperties predictedDroughtProps = new DroughtProperties(predictedData, droughtLimit);
//Determine the return periods for the droughts on record
double[][] historicDroughts = recurrenceInterval(historicDroughtProps, droughtLimit, annualData);
double[][] predictedDroughts = recurrenceInterval(predictedDroughtProps, droughtLimit, predictedData);
//Graph the drought data
graphTimeseries(annualData, droughtLimit); //graph1
graphNegativeBarChart(annualData, droughtLimit); //graph2
graphFittedData(originalData, fittedData, methodType); //graph3
graphPredictedData(originalData, predictedData, methodType);//graph4
graphReturnPeriod(historicDroughts, predictedDroughts); //graph5
//Populate the return array with the information gained from the drought analysis
String[] droughtInfo = new String[historicDroughtProps.numberOfDroughts() + 1];
droughtInfo[0] = "Deficit [acre-ft]\tLambda\tDuration [years]\tIntensity\tFinal Year";
for(int i=0; i<historicDroughtProps.numberOfDroughts(); i++){
droughtInfo[i+1] = String.valueOf(historicDroughtProps.getDeficit(i)) + "\t" +
String.valueOf(historicDroughtProps.getDeficit(i)/droughtLimit) + "\t" +
String.valueOf(historicDroughtProps.getDuration(i)) + "\t" +
String.valueOf(historicDroughtProps.getIntensity(i)) + "\t" +
String.valueOf(historicDroughtProps.getEndYear(i));
}
if(action.equalsIgnoreCase("optimizeParameters")){
//Return only the parameters of the regression
String[] stringArray = (String[]) returnArray;
stringArray[0] = stringArray[0] + "$$";
return stringArray;
}else if(action.equalsIgnoreCase("updateParameters")){
//Return only the parameters of the regression
String[] stringArray = {phiValues + "$$", thetaValues};
return stringArray;
}
return droughtInfo;
}
/**
* Calculates the average recurrence interval for each drought on record and the lambda coefficient of each drought (deficit = lambda * droughtLimit)
* @param currentDroughtProps the DroughtProperties class containing the droughts to be analyzed
* @param droughtLimit the value of the water demand in acre-ft to calculate lambda from (lambda = deficit/water demand)
* @param flowData the double[][] array containing the annual flow data with column1 = years, column2 = flow values in acre-ft
* @return a double[][] array containing: column1 = lambda sets of eleven lambda values {0. 0.5, 0.75, 1.0, 2.0}, column2 = average recurrence interval (return period), column3 = length of drought (from 1 to 11
*/
private double[][] recurrenceInterval(DroughtProperties currentDroughtProps, double droughtLimit, double[][] flowData){
DoubleMath doubleMath = new DoubleMath();
double[] lambda = {0.0, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0};
ArrayList<Double> droughtLambda = new ArrayList<Double>();
ArrayList<Double> droughtRecurrence = new ArrayList<Double>();
ArrayList<Double> droughtLength = new ArrayList<Double>();
//Loop through lambda values
for(int i=0; i<lambda.length; i++){
//Loop drought year length
for(int j=1; j<11; j++){
ArrayList<Double> recurrence = new ArrayList<Double>();
int endYear = (int) flowData[0][0];
//Loop through droughts
for(int k=0; k<currentDroughtProps.numberOfDroughts(); k++){
//Compare the properties of the current drought to the provided
if(currentDroughtProps.getDeficit(k) > lambda[i]*droughtLimit && currentDroughtProps.getDuration(k) == j){
//Measure recurrence from start of drought to start of drought (Salas, 2005 pg 385)
recurrence.add((double) (currentDroughtProps.getEndYear(k) - endYear));
endYear = currentDroughtProps.getEndYear(k);
}
}
//There were observed droughts larger than this one, calculate the average recurrence interval
if(recurrence.size() != 0){
droughtLambda.add(lambda[i]);//lambda value
droughtRecurrence.add(doubleMath.meanArithmetic(recurrence));//average recurrence = return period
droughtLength.add((double) j);//length of drought
}
}
}
//Return the drought properties in a single double[][]
double[][] newDroughts = new double[droughtLambda.size()][3];
for(int i=0; i<droughtLambda.size(); i++){
newDroughts[i][0] = droughtLambda.get(i);//lambda value
newDroughts[i][1] = droughtRecurrence.get(i);//average recurrence = return period
newDroughts[i][2] = droughtLength.get(i);//length of drought
}
return newDroughts;
}
/**
* Calculated the annual flow values given the average daily data provided. This sums the daily cfs values and
* converts it to acre-ft/day adding each day's worth of flow to the annual total
* @param sortedData a String[][] containing the flow data in the following format: column1 = dates (yyyy-mm-dd format),
* column2 = average daily streamflow values (cubic feet per second, cfs)
* @return
*/
private double[][] sumAnnualFlows(String[][] sortedData){
//Check for size issues and return early
if(sortedData.length<=1){
return new double[0][2];
}
//Get a unique list of years along with their accumulated flow values
ArrayList<String> uniqueYears = new ArrayList<String>();
ArrayList<Double> uniqueFlows = new ArrayList<Double>();
ArrayList<Integer> uniqueDays = new ArrayList<Integer>();
uniqueYears.add(sortedData[0][0].substring(0,4));
uniqueFlows.add((Double.parseDouble(sortedData[0][1])*24*3600)/43560);
uniqueDays.add(1);
int ctr = 0;
for(int i=1; i<sortedData.length; i++){
String previousYear = sortedData[i-1][0].substring(0,4);
String currentYear = sortedData[i][0].substring(0,4);
if(previousYear.equalsIgnoreCase(currentYear)){
//If the current year matches the previous year then add its flow value to the current total and continue
double currentFlowTotal = uniqueFlows.remove(ctr);
int currentNumberOfDays = uniqueDays.remove(ctr);
//convert daily cfs into acre-ft contributed by one day
//acre-ft = cfs * 1day * 24hr/1day * 3600sec/1hr * 1acre-ft/43560 ft^3
double currentFlow = (Double.parseDouble(sortedData[i][1])*24*3600)/43560;
uniqueFlows.add(ctr, currentFlowTotal + currentFlow);
uniqueDays.add(ctr, currentNumberOfDays + 1);
}else{
//If the current year does not match the previous year, then we are in a new year so add the year to the
//uniqueYear list and reset the annual flow total
uniqueYears.add(currentYear);
uniqueDays.add(1);
//convert daily cfs into acre-ft for one day
//acre-ft = cfs * 1day * 24hr/1day * 3600sec/1hr * 1acre-ft/43560 ft^3
double currentFlow = (Double.parseDouble(sortedData[i][1])*24*3600)/43560;
uniqueFlows.add(currentFlow);
ctr++;
}
}
System.out.println("Total Years: " + uniqueYears.size() + "\tTotal Flows: " + uniqueFlows.size());
// //Compute average flow/day for each year in acre-ft/day and then annualize this value for a total flow per year value
// //This average allows some years to have minimal data but still be used in the drought analysis
// for(int i=0; i<uniqueYears.size(); i++){
// int totalDays = uniqueDays.remove(i);
// if(totalDays < 365){
// //If less than a full year's worth of flows has been added, annualize each year's flow based on an average of the available data
// double totalFlow = uniqueFlows.remove(i);
// String year = uniqueYears.get(i);
// System.out.println(year + "\tOld flow: " + totalFlow + "acre-ft/day\t" + totalDays + " days");
//
// double averageFlowDay = totalFlow/totalDays;
// double annualFlow = averageFlowDay*365;
//
// uniqueFlows.add(i, annualFlow);
// uniqueDays.add(i, 365);
// System.out.println(year + "\tNew flow: " + annualFlow + "acre-ft/day\t365 days");
//
// }else{
// //If it is a full year of data, then don't change anything
// uniqueDays.add(i, totalDays);
// }
// }
double[][] uniqueData = new double[uniqueYears.size()][2];
for(int i=0; i<uniqueYears.size(); i++){
uniqueData[i][0] = Double.parseDouble(uniqueYears.get(i));//years
uniqueData[i][1] = uniqueFlows.get(i);//annual flow value
}
return uniqueData;
}
/**
* Graph the timeseries data and save the resulting graph to the specified location
* @param sortedData the String[][] containing sorted data for the timeseries (column 1 = dates (yyyy-mm-dd) column 2 = values)
* @param droughtLimit the calculated drought limit (if supply < drought limit then that year is a drought)
*/
private void graphTimeseries(double[][] sortedData,
double droughtLimit) throws IOException{
Graphing graphing = new Graphing();
//Round the long term average to fit the data better
double tempRounding = droughtLimit*1000;
tempRounding = Math.round(tempRounding)/1000;
//Create TimeSeries graph
TimeSeries series = new TimeSeries("Annual Flow Rate");
TimeSeries averageSeries = new TimeSeries("Drought Limit: " + String.valueOf(tempRounding) + " [acre-ft]");
String[][] graphData = new String[sortedData.length][3];
for(int i=0; i < sortedData.length; i++) {
int year_int = (int) sortedData[i][0];
if(year_int >= 1900){//artificial limit for JFreeCharts' "Day" class
Year year = new Year(year_int);
series.add(year, sortedData[i][1]);
averageSeries.add(year,droughtLimit);
}
graphData[i][0] = String.valueOf(year_int);
graphData[i][1] = String.valueOf(sortedData[i][1]);
graphData[i][2] = String.valueOf(droughtLimit);
}
//Save results for graphing with JSHighcharts
DoubleArray doubleArray = new DoubleArray();
doubleArray.writeTimeSeries(mainFolder, graphData, "Yearly", getTimeseriesOutput().getName(), false);
//Graph data
XYPlot plotTime = new XYPlot();
plotTime = graphing.graphTimeData(plotTime, series, true, Color.blue, false, false, false, true, 0);
plotTime = graphing.graphTimeData(plotTime, averageSeries, true, Color.black, false, false, true, true, 1);
//Create Y axis
ValueAxis rangeTime = new NumberAxis("Annual Flow Rate [acre-ft]");
plotTime.setRangeAxis(0, rangeTime);
//Create X Axes
DateAxis domainTime = new DateAxis("Year");
domainTime.setLowerMargin(0.05);
domainTime.setUpperMargin(0.05);
plotTime.setDomainAxis(0, domainTime);
//Set extra plot preferences
graphing.setTimeAxisPreferences(plotTime);
//Create the chart with the plot and a legend
String graphTitle = "Annual Time Series for " + database + " Station: " + stationID + "; " + stationName;
JFreeChart chart = new JFreeChart(graphTitle, graphing.titleFont, plotTime, true);
//Set legend Font
LegendTitle legendTitle = chart.getLegend();
legendTitle.setItemFont(graphing.masterFont);
//Save resulting graph for use later
try{
String path = mainFolder + File.separator + getTimeseriesGraph();
ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
System.out.println("Graph located at:\t" + path);
}catch(IOException e){
System.err.println("A problem occurred while trying to creating the chart.");
}
}
/**
* Graph the bar chart of the timeseries data where the values graphed ar those in sortedData and save the resulting graph to the specified location
* @param sortedData the String[][] containing sorted data for the timeseries (column 1 = dates (yyyy-mm-dd) column 2 = values)
* @param droughtLimit the calculated drought limit (if supply < drought limit then that year is a drought)
*/
private void graphNegativeBarChart(double[][] sortedData,
double droughtLimit) throws IOException{
Graphing graphing = new Graphing();
//Round the long term average to fit the data better
double tempRounding = droughtLimit*1000;
tempRounding = Math.round(tempRounding)/1000;
//Create TimeSeries graph
TimeTableXYDataset dataset = new TimeTableXYDataset();
boolean deficitFirst = false;
String[][] graphData = new String[sortedData.length][2];
for(int i=0; i < sortedData.length; i++) {
Year year = new Year((int) sortedData[i][0]);
if(sortedData[i][1] < droughtLimit){
dataset.add(year, sortedData[i][1] - droughtLimit, "Deficit");
if(i == 0){
//If the first year is a deficit mark it as true to get the colors for the graph right
deficitFirst = true;
}
}else{
dataset.add(year, sortedData[i][1] - droughtLimit, "Surplus");
}
graphData[i][0] = String.valueOf((int) sortedData[i][0]);
graphData[i][1] = String.valueOf(sortedData[i][1] - droughtLimit);
}
//Save results for graphing with JSHighcharts
DoubleArray doubleArray = new DoubleArray();
doubleArray.writeXYseries(mainFolder, graphData, getColumnChartOutput().getName());
//Define axis and properties
NumberAxis numberaxis = new NumberAxis("Annual Deficit/Surplus of Drought Limit (" + tempRounding + " acre-ft)");
DateAxis dateaxis = new DateAxis("Year");
dateaxis.setTickMarkPosition(DateTickMarkPosition.MIDDLE);
dateaxis.setLowerMargin(0.01D);
dateaxis.setUpperMargin(0.01D);
//Define renderer properties for stacked graph
StackedXYBarRenderer stackedxybarrenderer = new StackedXYBarRenderer(0);
stackedxybarrenderer.setDrawBarOutline(false);
stackedxybarrenderer.setShadowVisible(false);
if(deficitFirst){
stackedxybarrenderer.setSeriesPaint(0, Color.red);
stackedxybarrenderer.setSeriesPaint(1, Color.blue);
}else{
stackedxybarrenderer.setSeriesPaint(0, Color.blue);
stackedxybarrenderer.setSeriesPaint(1, Color.red);
}
//Graph the dataset using the renderer and create a chart
XYPlot plot = new XYPlot(dataset, dateaxis, numberaxis, stackedxybarrenderer);
//Set extra plot preferences
graphing.setTimeAxisPreferences(plot);
//Create the chart with the plot
String graphTitle = "Annual Drought/Surplus for " + database + " Station: " + stationID + "; " + stationName;
JFreeChart chart = new JFreeChart(graphTitle, graphing.titleFont, plot, false);
//Save resulting graph for use later
try{
String path = mainFolder + File.separator + getColumnChart();
ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
System.out.println("Graph located at:\t" + path);
}catch(IOException e){
System.err.println("A problem occurred while trying to creating the chart.");
}
}
/**
* Graph the orignalData against the generatedData on a timeseries graph. Also graphs a scatter plot of the errors between the originalData and generatedData
* @param originalData double array of the original data. Column1 = years, column2 = annual flow values
* @param fittedData double array of the fitted data during the same time period as the original data. Column1 = years, column2 = annual flow values
* @param methodType the name of the method used to generate and project the data to be used in the legend of the graph
*/
private void graphFittedData(double[][] originalData,
double[][] fittedData,
String methodType) throws IOException{
Graphing graphing = new Graphing();
//Convert graph x = original data, y = fitted data
XYSeries series1 = new XYSeries("Regression of Data");
String[][] graphData = new String[originalData.length][4];
double max = 0;
for(int i=0; i < originalData.length; i++) {
series1.add(originalData[i][1], fittedData[i][1]);
graphData[i][0] = "-1";
graphData[i][1] = "-1";
graphData[i][2] = String.valueOf(originalData[i][1]);
graphData[i][3] = String.valueOf(fittedData[i][1]);
if(Double.compare(originalData[i][1], max) > 0){
max = originalData[i][1];
}
if(Double.compare(fittedData[i][1], max) > 0){
max = fittedData[i][1];
}
}
//Save info to graph 1-to-1 line of perfect simulation
max = 1.02*max;
if(graphData.length > 1){
graphData[0][0] = "0";//x
graphData[0][1] = "0";//y
graphData[1][0] = String.valueOf(max);//x
graphData[1][1] = String.valueOf(max);//y
}
//Save results for graphing with JSHighcharts
DoubleArray doubleArray = new DoubleArray();
doubleArray.writeXYseries(mainFolder, graphData, getFittedGraphOutput().getName());
//Add first series to graph
XYPlot plot = new XYPlot();
XYDataset dataset1 = new XYSeriesCollection(series1);
XYItemRenderer currentRenderer = new XYLineAndShapeRenderer(false, true);
currentRenderer.setSeriesPaint(0, Color.black);
plot.setDataset(0, dataset1);
plot.setRenderer(0, currentRenderer);
//Add line of slope 1 to graph
XYSeries series2 = new XYSeries("One to one line, Don't show in legend");
series2.add(0,0);
series2.add(max, max);
XYDataset dataset2 = new XYSeriesCollection(series2);
XYItemRenderer currentRenderer2 = new XYLineAndShapeRenderer(true, false);
currentRenderer2.setSeriesPaint(0, Color.lightGray);
plot.setDataset(1, dataset2);
plot.setRenderer(1, currentRenderer2);
//Create the X Axis
ValueAxis xAxis = new NumberAxis("Original Data [acre-ft/yr]");
xAxis.setRange(0, max);
xAxis.setVerticalTickLabels(true);
plot.setDomainAxis(0, xAxis);
//Create the Y Axis
ValueAxis yAxis = new NumberAxis(methodType + " Data [acre-ft/yr]");
yAxis.setRange(0, max);
plot.setRangeAxis(0, yAxis);
//Set extra plot preferences
graphing.setAxisPreferences(plot);
//Create the chart with the plot
String graphTitle = "Data Correlation for " + database + " Station: " + stationID + "; " + stationName;
JFreeChart chart = new JFreeChart(graphTitle, graphing.titleFont, plot, false);
try{
String path = mainFolder + File.separator + getFittedDataGraph();
ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
System.out.println("Graph located at:\t" + path);
}catch(IOException e){
System.err.println("A problem occurred while trying to creating the chart.");
}
}
/**
* Graph the orignalData against the future predictedData on a timeseries graph.
* @param originalData double array of the original data. Column1 = years, column2 = annual flow values
* @param predictedData double array of the future predicted data. Column1 = years, column2 = annual flow values
* @param methodType the name of the method used to generate and project the data to be used in the legend of the graph
*/
private void graphPredictedData(double[][] originalData,
double[][] predictedData,
String methodType) throws IOException{
Graphing graphing = new Graphing();
//Convert series into Dates
XYSeries series1 = new XYSeries("Original Data");
XYSeries series2 = new XYSeries(methodType + " warm up period");
XYSeries series3 = new XYSeries(methodType + " Predicted Data");
String[][] graphData = new String[originalData.length][3];
for(int i=0; i < originalData.length; i++) {
series1.add(originalData[i][0], originalData[i][1]);
//Save results for graphing with JSHighcharts
graphData[i][0] = String.valueOf(originalData[i][0]);
graphData[i][1] = String.valueOf(originalData[i][1]);
graphData[i][2] = String.valueOf(predictedData[i][1]);
}
for(int i=0; i < predictedData.length; i++) {
if(i<100){
//Keep the warm up series
series2.add(predictedData[i][0], predictedData[i][1]);
}else{
//Keep the final dataseries
series3.add(predictedData[i][0], predictedData[i][1]);
}
}
//Save results for graphing with JSHighcharts
DoubleArray doubleArray = new DoubleArray();
doubleArray.writeTimeSeries(mainFolder, graphData, "Yearly", getProjectedGraphOutput().getName(), false);
//Add first series to graph
XYPlot plot = new XYPlot();
XYDataset dataset1 = new XYSeriesCollection(series1);
XYItemRenderer currentRenderer = new XYLineAndShapeRenderer(true, false);
currentRenderer.setSeriesPaint(0, Color.black);
plot.setDataset(0, dataset1);
plot.setRenderer(0, currentRenderer);
//Add second series to graph
XYDataset dataset2 = new XYSeriesCollection(series2);
XYItemRenderer currentRenderer2 = new XYLineAndShapeRenderer(true, false);
currentRenderer2.setSeriesPaint(0, Color.gray);
plot.setDataset(1, dataset2);
plot.setRenderer(1, currentRenderer2);
//Add third series to graph
XYDataset dataset3 = new XYSeriesCollection(series3);
XYItemRenderer currentRenderer3 = new XYLineAndShapeRenderer(true, false);
currentRenderer3.setSeriesPaint(0, Color.red);
plot.setDataset(2, dataset3);
plot.setRenderer(2, currentRenderer3);
//Create the X Axis
NumberAxis xAxis = new NumberAxis("Year");
xAxis.setRange(originalData[0][0], predictedData[predictedData.length - 1][0]/35);
DecimalFormat formatter = new DecimalFormat("#0");
xAxis.setNumberFormatOverride(formatter);
plot.setDomainAxis(0, xAxis);
//Create the Y Axis
ValueAxis yAxis = new NumberAxis("Annual Discharge [acre-ft/yr]");
plot.setRangeAxis(0, yAxis);
//Set extra plot preferences
graphing.setAxisPreferences(plot);
//Create the chart with the plot
String graphTitle = "Projected Data 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);
try{
String path = mainFolder + File.separator + getProjectedDataGraph();
ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
System.out.println("Graph located at:\t" + path);
}catch(IOException e){
System.err.println("A problem occurred while trying to creating the chart.");
}
}
/**
* Graphs the drought length verses the return period for each lambda (drought deficit = lambda * droughtLimit) value on a scatter plot
* @param originalData double array of the original data. Column1 = years, column2 = annual flow values
* @param predictedData double array of the future predicted data. Column1 = years, column2 = annual flow values
*/
private void graphReturnPeriod(double[][] originalData,
double[][] predictedData) throws IOException{
DoubleMath doubleMath = new DoubleMath();
DoubleArray doubleArray = new DoubleArray();
Graphing graphing = new Graphing();
//Create xy scatter plot
XYPlot plot = new XYPlot();
//Add fist series to graph
double[] lambda = {0.0};//, 0.5, 0.75, 1.0};
Color[] colorMatrix2 = {Color.black, Color.darkGray, Color.gray, Color.lightGray};
ArrayList<ArrayList<Double>> graphDataX = new ArrayList<ArrayList<Double>>();
ArrayList<ArrayList<Double>> graphDataY = new ArrayList<ArrayList<Double>>();
int ctr = 0;
for(int i=0; i<lambda.length; i++){
//Create and populate the lambda series
XYSeries series2 = new XYSeries("Original Data: Lambda = " + lambda[i]);
ArrayList<Double> temp_originalX = new ArrayList<Double>();
ArrayList<Double> temp_originalY = new ArrayList<Double>();
for(int j=0; j < originalData.length; j++){
if(Double.compare(originalData[j][0], lambda[i]) == 0){
series2.add(originalData[j][2], originalData[j][1]);
temp_originalX.add(originalData[j][2]);
temp_originalY.add(originalData[j][1]);
}
}
graphDataX.add(temp_originalX);
graphDataY.add(temp_originalY);
//Add the new series to the dataset
XYDataset dataset2 = new XYSeriesCollection(series2);
XYItemRenderer currentRenderer2 = new XYLineAndShapeRenderer(true, true);
currentRenderer2.setSeriesPaint(0, colorMatrix2[i]);
plot.setDataset(ctr, dataset2);
plot.setRenderer(ctr, currentRenderer2);
ctr++;
}
//Add second series to graph
double[] lambda2 = {0.0, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0};
Color[] colorMatrix1 = {new Color(105, 0, 255), Color.blue, Color.cyan, Color.green, new Color(255, 135, 0), new Color(255, 0, 50), new Color(255, 0, 255, 100)};
//Colors in the color matrix above are: purple, blue, cyan, green, gold, red, pink
for(int i=0; i<lambda2.length; i++){
//Create and populate the lambda series
XYSeries series2 = new XYSeries("Simmulated Data: Lambda = " + lambda2[i]);
ArrayList<Double> temp_predictedX = new ArrayList<Double>();
ArrayList<Double> temp_predictedY = new ArrayList<Double>();
for(int j=0; j < predictedData.length; j++){
if(Double.compare(predictedData[j][0], lambda2[i]) == 0){
series2.add(predictedData[j][2], predictedData[j][1]);
temp_predictedX.add(predictedData[j][2]);
temp_predictedY.add(predictedData[j][1]);
}
}
graphDataX.add(temp_predictedX);
graphDataY.add(temp_predictedY);
//Add the new series to the dataset
XYDataset dataset2 = new XYSeriesCollection(series2);
XYItemRenderer currentRenderer2 = new XYLineAndShapeRenderer(true, true);
currentRenderer2.setSeriesPaint(0, colorMatrix1[i]);
plot.setDataset(ctr, dataset2);
plot.setRenderer(ctr, currentRenderer2);
ctr++;
}
//Save results for graphing with JSHighcharts
int maxSize = 0;
for(int i=0; i<graphDataX.size(); i++){
int size = graphDataX.get(i).size();
if(size > maxSize){
maxSize = size;
}
}
String[][] graphData = new String[maxSize][16];//16 = 8 * 2 columns for both x and y points
ctr = 0;
for(int j=0; j<8; j++){
ArrayList<Double> currentX = graphDataX.get(j);
ArrayList<Double> currentY = graphDataY.get(j);
for(int i=0; i<maxSize; i++){
try{
graphData[i][ctr] = String.valueOf(currentX.get(i));
graphData[i][ctr + 1] = String.valueOf(currentY.get(i));
}catch(IndexOutOfBoundsException e){
graphData[i][ctr] = "-1";
graphData[i][ctr + 1] = "-1";
}
}
ctr++;//Increase by 1 to move from x column to y column
ctr++;//Increase by 1 to move from y column to new x column
}
doubleArray.writeXYseries(mainFolder, graphData, getRecurrenceGraphOutput().getName());
//Create the X Axis
ValueAxis xAxis = new NumberAxis("Drought Length [years]");
xAxis.setRange(0, 11);
TickUnits unit1 = new TickUnits();
unit1.add(new NumberTickUnit(1, NumberFormat.getNumberInstance()));
xAxis.setStandardTickUnits(unit1);
plot.setDomainAxis(0, xAxis);
//Create the Y Axis
LogarithmicAxis yAxis = new LogarithmicAxis("Recurrence Interval [years]");
yAxis.setAllowNegativesFlag(true);
try{
double maxValue = Math.log10(doubleMath.max(doubleArray.getColumn(predictedData, 1)));
if(Double.compare(maxValue, Double.NEGATIVE_INFINITY) != 0 &&
Double.compare(maxValue, Double.POSITIVE_INFINITY) != 0 ){
maxValue = Math.ceil(maxValue);
yAxis.setRange(1, Math.pow(10, maxValue));
}
}catch(ArrayIndexOutOfBoundsException e){
System.err.print("No predicted data lambda values\n");
}
plot.setRangeAxis(0, yAxis);
//Add equation explaining the term lambda
XYTextAnnotation equation = new XYTextAnnotation(" Lambda = Drought Deficit / Drought Limit ", 9, yAxis.getUpperBound()*0.5);
equation.setFont(graphing.masterFont);
equation.setBackgroundPaint(Color.white);
equation.setOutlinePaint(Color.black);
equation.setOutlineVisible(true);
plot.addAnnotation(equation);
//Set extra plot preferences
graphing.setAxisPreferences(plot);
//Create the chart with the plot
String graphTitle = "Drought Recurrence Intervals 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);
try{
String path = mainFolder + File.separator + getDroughtRecurrenceGraph();
ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
System.out.println("Graph located at:\t" + path);
}catch(IOException e){
System.err.println("A problem occurred while trying to creating the chart.");
}
}
/**
* Graph the AR(p) AIC and BIC verses the p order of the AR(p) model
* @param aic an array list of aic values corresponding to p=1, 2, etc.
* @param bic an array list of bic values corresponding to p=1, 2, etc.
*/
private void graphAR_AIC_BIC(ArrayList<Double> aic, ArrayList<Double> bic){
DoubleMath doubleMath = new DoubleMath();
Graphing graphing = new Graphing();
//Graph AIC and BIC data vs. the p parameter of the AR(p) model
XYSeries series1 = new XYSeries("AIC");
XYSeries series2 = new XYSeries("BIC");
for(int i=0; i < aic.size(); i++) {
series1.add(i+1, aic.get(i));
series2.add(i+1, bic.get(i));
}
XYPlot plot = new XYPlot();
//Add first series to graph
XYDataset dataset1 = new XYSeriesCollection(series1);
XYItemRenderer currentRenderer = new XYLineAndShapeRenderer(true, true);
currentRenderer.setSeriesPaint(0, Color.black);
plot.setDataset(0, dataset1);
plot.setRenderer(0, currentRenderer);
//Add second series to graph
XYDataset dataset2 = new XYSeriesCollection(series2);
XYItemRenderer currentRenderer2 = new XYLineAndShapeRenderer(true, true);
currentRenderer2.setSeriesPaint(0, Color.cyan);
plot.setDataset(1, dataset2);
plot.setRenderer(1, currentRenderer2);
//Create the X Axis
ValueAxis xAxis = new NumberAxis("Number of Model Parameters");
TickUnits unit1 = new TickUnits();
unit1.add(new NumberTickUnit(1, NumberFormat.getNumberInstance()));
xAxis.setStandardTickUnits(unit1);
xAxis.setRange(0.01, aic.size() + 0.99);
plot.setDomainAxis(0, xAxis);
//Create the Y Axis
ValueAxis yAxis = new NumberAxis("AIC/BIC Value");
double max = doubleMath.max(aic);
if(max < doubleMath.max(bic)){
max = doubleMath.max(bic);
}
double min = doubleMath.min(aic);
if(min > doubleMath.min(bic)){
min = doubleMath.min(bic);
}
yAxis.setRange(0.98*min, 1.02*max);
plot.setRangeAxis(0, yAxis);
//Create the legend inside of the graph
LegendTitle lt = new LegendTitle(plot);
lt.setItemFont(new Font("Dialog", Font.PLAIN, 12));
lt.setBackgroundPaint(new Color(255, 255, 255, 255));
lt.setFrame(new BlockBorder(Color.black));
lt.setPosition(RectangleEdge.RIGHT);
lt.setPosition(RectangleEdge.LEFT);
XYTitleAnnotation ta = new XYTitleAnnotation(1, 1, lt, RectangleAnchor.TOP_RIGHT);
plot.addAnnotation(ta);
//Set extra plot preferences
graphing.setAxisPreferences(plot);
//Create the chart with the plot
JFreeChart chart = new JFreeChart("AR(p) Parameter Optimization", graphing.titleFont, plot, false);
try{
String[] graphNames = get_optimizeModel_output();
String path = mainFolder + File.separator + graphNames[0];
ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
System.out.println("Graph located at:\t" + path);
}catch(IOException e){
System.err.println("A problem occurred while trying to creating the chart.");
}
}
/**
* Graph the ARMA(p,q) AIC or BIC values verses the total number of ARMA model parameters (p+q)
* @param method the name of the method either AIC or BIC
* @param results a double[][] containing the points to be plotted
* @param graphNumber the index of the graph either 3 (AIC) or 4 (BIC)
*/
private void graphARMA_AIC_BIC(String method, double[][] results, int graphNumber){
DoubleMath doubleMath = new DoubleMath();
DoubleArray doubleArray = new DoubleArray();
Graphing graphing = new Graphing();
//Add each point of the aic/bic to the graph
XYPlot plot = new XYPlot();
XYSeries series1 = new XYSeries(method + " Points");
for(int i=0; i < results.length; i++){
series1.add(results[i][0] + results[i][1], results[i][graphNumber - 1]);
}
//Determine what the optimal values of AIC/BIC are for each p+q combination
XYSeries series2 = new XYSeries(method + " Optimal Front");
ArrayList<Double> aicBIC = new ArrayList<Double>();
ArrayList<Double> pValues = new ArrayList<Double>();
ArrayList<Double> qValues = new ArrayList<Double>();
for(int i=2; i<=8; i++){
//For each "value"=p+q find all of the p+q combinations which add to this
//"value" and retrieve their corresponding AIC/BIC value
ArrayList<Double> reArrangedData = new ArrayList<Double>();
for(int j=0; j<results.length; j++){
if(results[j][0] + results[j][1] == i){
reArrangedData.add(results[j][graphNumber - 1]);
}
}
double currentMinimum = doubleMath.min(reArrangedData);
series2.add(i, currentMinimum);
for(int j=0; j<results.length; j++){
if(Double.compare(currentMinimum,results[j][graphNumber - 1]) == 0){
aicBIC.add(results[j][graphNumber - 1]);
pValues.add(results[j][0]);
qValues.add(results[j][1]);
}
}
}
//Add first series to graph
XYDataset dataset1 = new XYSeriesCollection(series1);
XYItemRenderer currentRenderer = new XYLineAndShapeRenderer(false, true);
currentRenderer.setSeriesPaint(0, Color.black);
plot.setDataset(0, dataset1);
plot.setRenderer(0, currentRenderer);
//Add second series to graph
XYDataset dataset2 = new XYSeriesCollection(series2);
XYItemRenderer currentRenderer2 = new XYLineAndShapeRenderer(true, false);
currentRenderer2.setSeriesStroke(0,
new BasicStroke(
1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,
1.0f, new float[] {6.0f, 6.0f}, 0.0f
));
currentRenderer2.setSeriesPaint(0, Color.blue);
plot.setDataset(1, dataset2);
plot.setRenderer(1, currentRenderer2);
//Add highlight to best AIC/BIC value (lowest)
XYSeries series3 = new XYSeries("Error");
double minAICbic = doubleMath.min(aicBIC);
String p = "", q = "";
for(int i=0; i<aicBIC.size(); i++){
if(Double.compare(aicBIC.get(i), minAICbic) == 0){
p = String.valueOf(pValues.get(i));
q = String.valueOf(qValues.get(i));
series3.add(pValues.get(i) + qValues.get(i), minAICbic);
break;
}
}
//Create label for optimal ARMA model
p = p.substring(0,p.indexOf("."));
q = q.substring(0,q.indexOf("."));
XYDataset dataset3 = new XYSeriesCollection(series3);
XYItemRenderer currentRenderer3 = new XYLineAndShapeRenderer(false, true);
currentRenderer3.setSeriesPaint(0, Color.black);
currentRenderer3.setSeriesVisibleInLegend(0, false);
//Add an outline around the point
CircleDrawer cd = new CircleDrawer(Color.black, new BasicStroke(1.0f), null);
double x = series3.getX(0).doubleValue();
double y = series3.getY(0).doubleValue();
XYAnnotation bestBid = new XYDrawableAnnotation(x, y, 11, 11, cd);
plot.addAnnotation(bestBid);
//Add an arrow pointing to the point with the year of that flood
final XYPointerAnnotation pointer = new XYPointerAnnotation("Optimal ARMA Model: p= " + p + " q= " + q, x, y, Math.PI - 0.3);
pointer.setBaseRadius(30.0);
pointer.setTipRadius(10.0);
pointer.setFont(graphing.masterFont);
pointer.setPaint(Color.black);
pointer.setBackgroundPaint(Color.white);
pointer.setOutlinePaint(Color.black);
pointer.setTextAnchor(TextAnchor.CENTER_RIGHT);
plot.addAnnotation(pointer);
//Put the line data, renderer, and axis into plot
plot.setDataset(3, dataset3);
plot.setRenderer(3, currentRenderer3);
//Create the X Axis
ValueAxis xAxis = new NumberAxis("Number of Model Parameters");
TickUnits unit1 = new TickUnits();
unit1.add(new NumberTickUnit(1, NumberFormat.getNumberInstance()));
xAxis.setStandardTickUnits(unit1);
xAxis.setRange(0.01, 8.99);
plot.setDomainAxis(0, xAxis);
//Create the Y Axis
ValueAxis yAxis = new NumberAxis(method + " Value");
double max = doubleMath.max(doubleArray.getColumn(results, graphNumber - 1));
double min = doubleMath.min(doubleArray.getColumn(results, graphNumber - 1));
yAxis.setRange(0.8*min, 1.02*max);
plot.setRangeAxis(0, yAxis);
//Set extra plot preferences
graphing.setAxisPreferences(plot);
//Create the chart with the plot
JFreeChart chart = new JFreeChart("ARMA(p, q) " + method + " Parameter Optimization", graphing.titleFont, plot, false);
try{
String[] graphNames = get_optimizeModel_output();
String path = mainFolder + File.separator + graphNames[graphNumber-3];
ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
System.out.println("Graph located at:\t" + path);
}catch(IOException e){
System.err.println("A problem occurred while trying to creating the chart.");
}
}
/**
* Writes out the dynamically created summary table to be displayed to the user along with the flood graph
* @param dynamicSummary string[] array to be written as each line of the text file
* @throws IOException
*/
public void writeSummary(String[] dynamicSummary) throws IOException{
//Intialize the file writer
String path = mainFolder + File.separator + getResult().getName();
FileWriter writer = new FileWriter(path, false);
PrintWriter print_line = new PrintWriter(writer);
//Write the summary to the file
for(int i=0; i < dynamicSummary.length; i++) {
print_line.printf("%s" + "\r\n", dynamicSummary[i]);
}
print_line.close();
writer.close();
System.out.println("Text File located at:\t" + path);
}
/**
* 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);
}
public void run() throws IOException, InterruptedException, 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);
}
//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);
}
//Perform drought analysis
String[] droughtInfo = null;
if(Double.compare(droughtLimit, 0) < 0){
//If no drought limit is provided (aka -1 is provided) then calculate the drought limit = average(annual flows)
droughtInfo = generalDroughtAnalysis(sortedData_combined);
}else{
//If a drought limit is provided (aka greater than or equal to zero) then use this as the drought limit
droughtInfo = generalDroughtAnalysis(sortedData_combined, droughtLimit);
}
//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);
if(database.equalsIgnoreCase("USGS")){
this.dataSource = "Stream flow data retrieved from the U.S. Geological Survey, National Water Information System: Web Interface. http://waterdata.usgs.gov/nwis, accessed: " + today;
}else if(database.equalsIgnoreCase("STORET")){
this.dataSource = "Stream flow data retrieved from the U.S. Environmental Protection Agency, STORET. http://www.epa.gov/storet/index.html accessed: " + today;
}else if(database.equalsIgnoreCase("CDWR")){
this.dataSource = "Stream flow data retrieved from the Colorado Division of Water Resources, CDWR. http://www.dwr.state.co.us accessed: " + today;
}
this.len = String.valueOf(sortedData_combined.length);
this.start = String.valueOf(sortedData_combined[0][0]);
this.end = String.valueOf(sortedData_combined[sortedData_combined.length - 1][0]);
writeSummary(droughtInfo);
}
public static void main(String[] args) throws IOException, InterruptedException, Exception {
guiDrought_Model drought_Model = new guiDrought_Model();
//Set Inputs
// assert args.length > 0;
// drought_Model.setMainFolder(args[0]); //The output location of the graph
// drought_Model.setFileName(args[1]); //The name of the output graph and summary text file
// drought_Model.setOrganizationName(args[2]); //Supervising organization of the station (only used for STORET stations)
// drought_Model.setStationID(args[3]); //The station ID used to retrieve the station's flow data
// drought_Model.setStationName(args[4]); //The station Name for the current station, used to title the graph
// drought_Model.setBeginDate(args[5]); //Begin date of analysis
// drought_Model.setEndDate(args[6]); //End date of analysis
// drought_Model.setLambdaString(args[7]); //the value of lambda for the Box-Cox transformation or the word "optimize" to optimize this value
// drought_Model.setAction(args[8]); //The action to be taken for the drought analysis, either "all" for a full model, "optimizeParameters" to return only the phi and theta values for the AR/ARMA model fit, or useParameters to use the provided phi and theta parameters
// drought_Model.setPhiValues(args[9]); //the number of phi values desired for the AR/ARMA model or a tab-delimited string of the previously calculated phi values
// drought_Model.setThetaValues(args[10]); //the number of theta values desired for the AR/ARMA model or a tab-delimited string of the previously calculated theta values (blank is acceptable and results in an AR(p) model instead of an ARMA(p,q) model)
// drought_Model.setDroughtLimit(Double.parseDouble(args[11])); //If not <0 then this represents the user specified drought limit so use it instead of calculating the long term average as the drought limit
//Run Model
drought_Model.run();
}
}