gui15minTimeseries_Model.java [src/java/m/cfa/timeseries15min] Revision: Date:
package m.cfa.timeseries15min;
import WaterData.WaterData;
import WaterData.WaterDataInterface;
import m.cfa.DateComparator;
import m.cfa.DoubleArray;
import m.cfa.DoubleMath;
import m.cfa.Graphing;
import java.awt.Color;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.general.SeriesException;
import org.jfree.data.time.Day;
import org.jfree.data.time.Minute;
import org.jfree.data.time.TimeSeries;
/**
* Last Updated: 9-April-2019
* @author Tyler Wible
* @since 23-June-2014
*/
public class gui15minTimeseries_Model {
String directory = "E:/Projects/TylerWible_repos/NetBeans/data/CFA/Timeseries";
String database = "USGS";//"CDWR";
String stationId = "06752260";//"CLAGRECO";
String stationName = "CACHE LA POUDRE RIVER AT FORT COLLINS, CO";//"Cache La Poudre Near Greeley";
String startDate = "";//yyyy-MM-dd
String endDate = "";//yyyy-MM-dd
String userData = "";//"Date\tFlow\n1999-04-29 00:00\t8.3\n1999-05-09 00:00\t60.2\n1999-05-29 00:00\t20.1";//
boolean mergeDatasets = false;//true;//
String mergeMethod = "user";//"public";//"max";//"average";//"min";//
//Outputs
String len = "-1";
String start = "?";
String end = "?";
String units = "?";
String dataSource = "?";
double max = -1;
double min = -1;
double range = -1;
double mean = -1;
double standardDeviation = -1;
//Gets
public File getOutputSummary() {
return new File(directory, "timeseries15min_summary.csv");
}
public String getGraph() {
return "timeseries15min_graph.jpg";
}
public File getTimeseriesOutput(){
//This output file is for use with JSHighCharts
return new File(directory, "timeseries15min_graph.out");
}
public String getLen(){ return len; }
public String getStart(){ return start; }
public String getEnd(){ return end; }
public String getUnits(){ return units; }
public String getDataSource(){ return dataSource; }
public String getMax(){ return String.valueOf(max); }
public String getMin(){ return String.valueOf(min); }
public String getRange(){ return String.valueOf(range); }
public String getMean(){ return String.valueOf(mean); }
public String getStandardDeviation(){ return String.valueOf(standardDeviation); }
//Sets
public void setDirectory(String directory_str){ directory = directory_str; }
public void setDatabase(String database_str){ database = database_str; }
public void setStationId(String stationId_str){ stationId = stationId_str; }
public void setStationName(String stationName_str){ stationName = stationName_str; }
public void setStartDate(String startDate_str){ startDate = startDate_str; }
public void setEndDate(String endDate_str){ endDate = endDate_str; }
public void setUserData(String userData_str){ userData = userData_str; }
public void setMergeDatasets(boolean mergeDatasets_TF){ mergeDatasets = mergeDatasets_TF; }
public void setMergeMethod(String mergeMethod_str){ mergeMethod = mergeMethod_str; }
/**
* Main statistics function calls other functions to calculate each statistic value then stores the results as global variables
* @param sortedData_combined data on which statistical values are desired
*/
private void CalculateDailyStatistics(String[][] flowData) throws IOException, ParseException {
//Calculate flow statistics for the entire analysis period
ArrayList<Double> allData = new ArrayList<>();
for(int i=0; i<flowData.length; i++){
allData.add(Double.parseDouble(flowData[i][1]));
}
String[][] resultSummary = new String[6][1];
resultSummary[0][0] = "Method";
resultSummary[1][0] = "Maximum";
resultSummary[2][0] = "Minimum";
resultSummary[3][0] = "Range";
resultSummary[4][0] = "Mean";
resultSummary[5][0] = "Standard Deviation";
resultSummary = CalculateStatistics(allData, resultSummary, "All Data " + start + " to " + end);
max = Double.parseDouble(resultSummary[1][1]);
min = Double.parseDouble(resultSummary[2][1]);
range = Double.parseDouble(resultSummary[3][1]);
mean = Double.parseDouble(resultSummary[4][1]);
standardDeviation = Double.parseDouble(resultSummary[5][1]);
//Calculate Flow statistics for each day in time period
boolean moreDays = flowData.length > 0;
String currentDay = flowData[0][0].substring(0,10);
String finalDay = flowData[flowData.length - 1][0].substring(0,10);
while(moreDays){
//Get current year's data and calculate it's statistics
ArrayList<Double> partialData = DoubleArray.getDaysData(flowData, currentDay);
resultSummary = CalculateStatistics(partialData, resultSummary, currentDay);
String nextDay = DoubleArray.getDay(currentDay, 1);
if(finalDay.compareToIgnoreCase(String.valueOf(nextDay)) >= 0){
currentDay = String.valueOf(nextDay);
}else{
moreDays = false;
}
}
writeSummary(resultSummary);
}
private String[][] CalculateStatistics(ArrayList<Double> dataList, String[][] resultSummary, String header){
double maxVal = DoubleMath.max(dataList);
double minVal = DoubleMath.min(dataList);
double rangeVal = maxVal - minVal;
double aveVal = DoubleMath.meanArithmetic(dataList);
double stDev = DoubleMath.StandardDeviationSample(dataList);
//Append current results to summary
String[] resultArray = {header,
String.valueOf(DoubleMath.round(maxVal,3)),
String.valueOf(DoubleMath.round(minVal,3)),
String.valueOf(DoubleMath.round(rangeVal,3)),
String.valueOf(DoubleMath.round(aveVal,3)),
String.valueOf(DoubleMath.round(stDev,3))};
resultSummary = DoubleArray.appendcolumn_Matrix(resultSummary, resultArray);
return resultSummary;
}
/**
* Graph the time series and user data and save the resulting graph to the specified location
* @param sortedData the String[][] containing sorted data for the time series
* (column 1 = dates (yyyy-mm-dd if timeStep = "daily", yyyy-mm if
* timeStep = "monthly", yyyy if timeStep = "yearly") column 2 = value
* @param sortedData_user the String[][] containing sorted user data for the
* time series (column 1 = dates (yyyy-mm-dd if timeStep = "daily", yyyy-mm
* if timeStep = "monthly", yyyy if timeStep = "yearly") column 2 = value
* @param yAxisTitle the String label for the y axis of the graph
*/
private void createTimeseriesGraph(String[][] sortedData,
String[][] sortedData_user) throws ParseException, IOException {
//Create TimeSeries graph of merged data
TimeSeries series = new TimeSeries(stationId + ": Data");
DateFormat desiredDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
Date jfreeChartDateLimit = desiredDateFormat.parse("1900-01-01 00:00");
String[][] graphData = new String[sortedData.length][2];
for(int i=0; i < sortedData.length; i++) {
double value = Double.parseDouble(sortedData[i][1]);
Date currentDate = desiredDateFormat.parse(sortedData[i][0]);
Minute date = new Minute(currentDate);
try{
series.add(date, value);
graphData[i][0] = sortedData[i][0];
graphData[i][1] = sortedData[i][1];
}catch(SeriesException e){
//Some times there are multiple data points for a single yyyy-MM-dd HH:mm so catch the error here and continue
graphData[i][0] = "1900-01-01 00:00";
graphData[i][1] = "-1";
}catch(IllegalArgumentException e){
//If the date is prior to 1900-01-01, JFreechart will throw and error trying to parse it into the graph.
//However, keep the data for the dynamic graph which does not have this limit
graphData[i][0] = sortedData[i][0];
graphData[i][1] = sortedData[i][1];
}
}
//Create Time Series graph of user data
TimeSeries series2 = new TimeSeries(stationId + ": User Data");
for(int i=0; i < sortedData_user.length; i++) {
double value = Double.parseDouble(sortedData_user[i][1]);
//artificial limit for JFreeCharts' "Day" class
Date currentDate = desiredDateFormat.parse(sortedData_user[i][0]);
if(currentDate.compareTo(jfreeChartDateLimit) >= 0){
series2.add(new Day(currentDate), value);
}
}
//Output XY data for use with JHighCharts
DoubleArray.writeTimeSeries(directory, graphData, "15-min", getTimeseriesOutput().getName(), false);
//Create renderer, and axis for timeseries graph
XYPlot plotTime = new XYPlot();
boolean showLegend = false;
//Create user data points
if(sortedData_user.length != 0){//only show user points if it is not zero
plotTime = Graphing.addTimeseriesData(plotTime, series, true, Color.blue, false, false, false, true, 0);
plotTime = Graphing.addTimeseriesData(plotTime, series2, true, Color.darkGray, false, false, false, true, 1);
showLegend = true;
}else{
plotTime = Graphing.addTimeseriesData(plotTime, series, true, Color.blue, false, false, false, true, 0);
}
//Create Y Axis
ValueAxis rangeTime = new NumberAxis("Discharge [cfs]");
plotTime.setRangeAxis(0, rangeTime);
//Create X Axis
DateAxis domainTime = new DateAxis("Date");
domainTime.setLowerMargin(0.05);
domainTime.setUpperMargin(0.05);
plotTime.setDomainAxis(0, domainTime);
//Set extra plot preferences
plotTime = Graphing.setTimeAxisPreferences(plotTime);
//Create the chart with the plot and a legend
String graphTitle = "Time Series for " + database + " Station " + stationId + "; " + stationName;
JFreeChart chart = new JFreeChart(graphTitle, Graphing.titleFont, plotTime, showLegend);
//Set legend Font
if(showLegend){
LegendTitle legendTitle = chart.getLegend();
legendTitle.setItemFont(Graphing.masterFont);
}
//Save resulting graph for use later
try{
String path = directory + File.separator + getGraph();
ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
System.out.println("JFreeChart created properly at: " + path);
}catch(IOException e){
System.err.println("A problem occurred while trying to creating the chart.");
}
}
/**
* 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);
}
/**
* Writes out the dynamically created stats summary of the 15min data
* @param resultsSummary string array to be written as each line and column of the csv file
* @throws IOException
*/
private void writeSummary(String[][] resultsSummary) throws IOException{
//Open a file writer for the summary of the flow statistcs
String path = directory + File.separator + getOutputSummary().getName();
FileWriter newFile = new FileWriter(path, false);
PrintWriter writer = new PrintWriter(newFile);
for(int i=0; i<resultsSummary.length; i++){
String currentLine = resultsSummary[i][0];
for(int j=1; j<resultsSummary[i].length; j++){
currentLine = currentLine + "," + resultsSummary[i][j];
}
writer.printf("%s" + "\r\n", currentLine);
}
//Close file writer
newFile.close();
writer.close();
System.out.println("Text File located at:\t" + path);
}
public void run() throws IOException, InterruptedException, ParseException, Exception{
//If no date input, make it the maximum of available data
if(startDate == null || startDate.equalsIgnoreCase("")){
if(!database.equalsIgnoreCase("UserData")){
startDate = "2007-10-01";
}else{
startDate = "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
WaterDataInterface waterLib = WaterData.getNewWaterDataInterface(database, "");
String[][] sortableData = waterLib.extractInstantaneousFlowData_formatted(directory, stationId, startDate, endDate);
dataSource = waterLib.getDataSourceCitation();
//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){
WaterDataInterface waterLibUser = WaterData.getNewWaterDataInterface("UserData", userData);
sortableData = waterLibUser.extractInstantaneousFlowData_formatted(directory, stationId, startDate, endDate);
}
//Sort the Data by date to remove duplicate date entries
Arrays.sort(sortableData, new DateComparator());
Arrays.sort(sortableData_user, new DateComparator());
//Merge the two datasets (if user data is empty nothing will be merged)
String[][] sortedData_combined = DoubleArray.mergeData(sortableData, sortableData_user, mergeMethod);
if(sortedData_combined.length == 0){
ArrayList<String> errorMessage = new ArrayList<>();
if(sortableData.length == 0){
errorMessage.add("There is no available 15-minute (instantaneous) 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(sortableData_user.length == 0){
errorMessage.add("There is no available 15-minute (instantaneous) uploaded data for station '" + stationId + "' and the specified date range");
}
writeError(errorMessage);
}
//Save analysis results
this.start = sortedData_combined[0][0];
this.end = sortedData_combined[sortedData_combined.length - 1][0];
//Calculate stats of data
CalculateDailyStatistics(sortedData_combined);
//Graph the timeseries data
createTimeseriesGraph(sortedData_combined, sortableData_user);
this.len = String.valueOf(sortedData_combined.length);
this.units = "cfs";
}
public static void main(String[] args) throws IOException, InterruptedException, Exception {
gui15minTimeseries_Model Timeseries15min_Model = new gui15minTimeseries_Model();
//Run model
Timeseries15min_Model.run();
}
}