DurationCurve.java [src/java/cfa] Revision: ed4e4a960882eb4a0da28bca0df90ba790549b15 Date: Fri Oct 18 10:18:06 MDT 2013
package cfa;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Stroke;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.LogarithmicAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.IntervalMarker;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.Layer;
import org.jfree.ui.RectangleAnchor;
import org.jfree.ui.TextAnchor;
class DateComparator implements Comparator<String[]>{
//Compares the first entry of sorted data as flow values and sorts largest to smallest
public int compare(final String[] entry1, final String[] entry2) {
String value1 = entry1[0];
String value2 = entry2[0];
int comparison = value1.compareTo(value2);
if(comparison>0){
return 1;
}else if(comparison<0){
return -1;
}else{
return 0;
}
}
}
class FlowComparator implements Comparator<String[]>{
//Compares the second entry of sorted data as flow values and sorts largest to smallest
public int compare(final String[] entry1, final String[] entry2) {
double value1 = 0;
double value2 = 0;
try{
value1 = Double.parseDouble(entry1[1]);
value2 = Double.parseDouble(entry2[1]);
}catch(NumberFormatException e){
e.printStackTrace();
}
if (value1 > value2){
return -1;
}else if (value1 < value2){
return 1;
}else{
return 0;
}
}
}
/**
* Last Updated: 8-August-2013
* @author Tyler Wible
* @since 21-June-2011
*/
public class DurationCurve {
private String fontFamily = "SansSerif";
Font titleFont = new Font(fontFamily, Font.BOLD, 30);
Font masterFont = new Font(fontFamily, Font.PLAIN, 22);
Font DClabelFont = new Font(fontFamily, Font.ITALIC, 20);
Font floodLabelFont = new Font(fontFamily, Font.PLAIN, 18);
//Calibri
//Georgia
//SansSerif
//Serif
//Tahoma
//Verdana
/**
* Sub-graphing function to add graph x-interval marker/label
* @param Label_title the desired label title.
* @param lowerlimit the lower limit of the x-interval marker.
* @param upperlimit the upper limit of the x-interval marker.
* @return An Interval Marker.
*/
public XYPlot addIntervalLabel (XYPlot plot, String Label_title, double lowerlimit, double upperlimit){
//Create a new interval marker and set its properties
final IntervalMarker newLabel = new IntervalMarker(lowerlimit,upperlimit);
newLabel.setLabel(Label_title);
newLabel.setLabelFont(DClabelFont);
newLabel.setLabelAnchor(RectangleAnchor.BOTTOM);
newLabel.setLabelTextAnchor(TextAnchor.BASELINE_CENTER);
newLabel.setPaint(plot.getBackgroundPaint());//new Color(250, 250, 250, 100));//new Color(222, 222, 255, 130));
newLabel.setOutlinePaint(Color.black);
//Add the interval to the provided plot
plot.addDomainMarker(newLabel, Layer.BACKGROUND);
return plot;
}
/**
* Sub-function to create lines/rectangle to mimic a box plot
* @param plot the plot in which the objects are being graphed
* @param x_coord x coordinate of the correct boxplot
* @param data water quality data within the current flow interval
* @param series_int which element in the plot series is being created
* @param existingOutliers a variable indicating if there are already
* existing outliers (true) which are already labeled in the legend,
* therefore don't label these outliers in the legend
* @param existingExtremeOutliers a variable indicating if there are already
* existing extreme outliers (true) which are already labeled in the legend,
* therefore don't label these outliers in the legend
* @return the original plot with new object in it.
*/
public Object[] boxplot_shapes(XYPlot plot, double x_coord,
List<Double> data,
int series_int,
boolean existingOutliers,
boolean existingExtremeOutliers){
DoubleMath doubleMath = new DoubleMath();
//Calculate and add Median to dataset
XYSeries median_series = new XYSeries("Medians");
double mid_median = doubleMath.Percentile_function(data,0.50);
median_series.add(x_coord,mid_median);
//Create median Line
XYDataset median_scatter = new XYSeriesCollection(median_series);
XYItemRenderer renderer_median = new XYLineAndShapeRenderer(false, true);
renderer_median.setSeriesShape(0, new Rectangle2D.Double(-4.0, 0.0, 8.0, 0.5));//new Ellipse2D.Double(-4, -4, 8, 8));
renderer_median.setSeriesPaint(0, Color.red);
renderer_median.setSeriesVisibleInLegend(0, false);
plot.setDataset(series_int, median_scatter);
plot.setRenderer(series_int, renderer_median);
//Create quartile Box shapes for the box plot
//Create XYSeries for the box shape
XYSeries shapeSeries = new XYSeries("Shape");
double lowerQuartile = doubleMath.Percentile_function(data,0.25);
double upperQuartile = doubleMath.Percentile_function(data,0.75);
shapeSeries.add(x_coord, lowerQuartile);
shapeSeries.add(x_coord, upperQuartile);
//Create the quartile rectangle shape
XYDataset shapeDataset = new XYSeriesCollection(shapeSeries);
XYItemRenderer renderer_shape = new XYLineAndShapeRenderer(true, false);
Stroke thickness = new BasicStroke(8);
renderer_shape.setSeriesStroke(0, thickness);
renderer_shape.setSeriesPaint(0, Color.blue);
renderer_shape.setSeriesVisibleInLegend(0, false);
plot.setDataset(series_int + 1, shapeDataset);
plot.setRenderer(series_int + 1, renderer_shape);
//Creates 1.5 * Interquartile Range (IQR) lines
//Create XYSeries for the min-max lines
double IQR = upperQuartile - lowerQuartile;
double lowerLimit = lowerQuartile - 1.5*IQR;
double upperLimit = upperQuartile + 1.5*IQR;
if(lowerLimit < doubleMath.Min_Max(data,false)){
lowerLimit = doubleMath.Min_Max(data,false);
}
if(upperLimit > doubleMath.Min_Max(data,true)){
upperLimit = doubleMath.Min_Max(data,true);
}
XYSeries lineSeries = new XYSeries("Line");
lineSeries.add(x_coord, lowerLimit);
lineSeries.add(x_coord, upperLimit);
//Create the 1.5*IQR lines
XYDataset lineDataset = new XYSeriesCollection(lineSeries);
XYItemRenderer lineRenderer = new XYLineAndShapeRenderer(true, true);
Stroke thickness2 = new BasicStroke(1);
lineRenderer.setSeriesStroke(0, thickness2);
lineRenderer.setSeriesShape(0, new Rectangle2D.Double(-10.0, 0.0, 20.0, 1));
lineRenderer.setSeriesPaint(0, Color.black);
lineRenderer.setSeriesVisibleInLegend(0, false);
plot.setDataset(series_int + 2, lineDataset);
plot.setRenderer(series_int + 2, lineRenderer);
//Calculate and create Outliers (# < lowerQuartile - 1.5*IQR or # > upperQuartile + 1.5*IQR)
//Calculate and create Extreme Outliers (# < lowerQuartile - 3*IQR or # > upperQuartile + 3*IQR)
XYSeries outliers = new XYSeries("Outliers");
XYSeries extremeOutliers = new XYSeries("Extreme Outliers");
for(int i=0; i<data.size(); i++){
double value = data.get(i);
//Lower outliers
if(value < (lowerQuartile - 1.5*IQR) && value > (lowerQuartile - 3*IQR)){
outliers.add(x_coord, value);
}
//Upper outliers
if(value > (upperQuartile + 1.5*IQR) && value < (lowerQuartile + 3*IQR)){
outliers.add(x_coord, value);
}
//Extreme Lower outliers
if(value < (lowerQuartile - 3*IQR)){
extremeOutliers.add(x_coord, value);
}
//Extreme Upper outliers
if(value > (lowerQuartile + 3*IQR)){
extremeOutliers.add(x_coord, value);
}
}
//Create outlier scatter
XYDataset outlier_scatter = new XYSeriesCollection(outliers);
XYItemRenderer renderer_outlier = new XYLineAndShapeRenderer(false, true);
renderer_outlier.setSeriesShape(0, new Ellipse2D.Double(-2.0, 2.0, 4.0, 4.0));
renderer_outlier.setSeriesPaint(0, Color.darkGray);
if(outliers.isEmpty() || existingOutliers == true){
renderer_outlier.setSeriesVisibleInLegend(0, false);
}else{
existingOutliers = true;
}
plot.setDataset(series_int + 3, outlier_scatter);
plot.setRenderer(series_int + 3, renderer_outlier);
//Create extreme outlier scatter
XYDataset extremeOutlier_scatter = new XYSeriesCollection(extremeOutliers);
XYItemRenderer renderer_ExtremeOutlier = new XYLineAndShapeRenderer(false, true);
renderer_ExtremeOutlier.setSeriesShape(0, new Ellipse2D.Double(-2.0, 2.0, 4.0, 4.0));
renderer_ExtremeOutlier.setSeriesPaint(0, Color.red);
if(extremeOutliers.isEmpty() || existingExtremeOutliers == true){
renderer_ExtremeOutlier.setSeriesVisibleInLegend(0, false);
}else{
existingExtremeOutliers = true;
}
plot.setDataset(series_int + 4, extremeOutlier_scatter);
plot.setRenderer(series_int + 4, renderer_ExtremeOutlier);
Object[] returnArray = {plot, existingOutliers, existingExtremeOutliers};
return returnArray;
}
/**
* Checks if the provided dates are subsequent dates, aka nextDate = date + 1day.
* This check includes December 31st to January 1st catchs, 4-year leap-year catches,
* 100-year non-leap-year catches and 400-year leap-year catches
* @param date the first date to be compared (expected format = yyyy-mm-dd)
* @param nextDate the second date to be compared (expected format = (yyyy-mm-dd)
* @return returns true if nextDate = date + 1day, false otherwise
*/
public boolean checkSubsequentDates(String date, String nextDate){
double year = Double.parseDouble(date.substring(0,4));
double month = Double.parseDouble(date.substring(5,7));
double day = Double.parseDouble(date.substring(8));
double year2 = Double.parseDouble(nextDate.substring(0,4));
double month2 = Double.parseDouble(nextDate.substring(5,7));
double day2 = Double.parseDouble(nextDate.substring(8));
boolean subsequentDates = false;
//Check if nextDate = date + 1 day
if(Double.compare(year,year2) == 0){//Check if same year
if(Double.compare(month, month2) == 0){//Check if same month
if(Double.compare(day + 1, day2) == 0){//Check if subsequent day
subsequentDates = true;
}
}else{
if((Double.compare(month + 1, month2) == 0) && //Check if subsequent month
(Double.compare(day2,1) == 0)){//Check if first day
//Check months with 31 days
if((Double.compare(day, 31) == 0) &&
((Double.compare(month, 1) == 0) ||
(Double.compare(month, 3) == 0) ||
(Double.compare(month, 5) == 0) ||
(Double.compare(month, 7) == 0) ||
(Double.compare(month, 8) == 0) ||
(Double.compare(month, 10) == 0))){
subsequentDates = true;
//Check months with 30 days
}else if((Double.compare(day, 30) == 0) &&
((Double.compare(month, 4) == 0) ||
(Double.compare(month, 6) == 0) ||
(Double.compare(month, 9) == 0) ||
(Double.compare(month, 11) == 0))){
subsequentDates = true;
//Check February for leap years (including the 100-year-not-leap-year and 400-year-leap-year)
}else if((Double.compare(month, 2) == 0)){
boolean leapYear = false;
double yearUp4 = Math.ceil(year/4);
double yearDown4 = Math.floor(year/4);
double yearUp100 = Math.ceil(year/100);
double yearDown100 = Math.floor(year/100);
double yearUp400 = Math.ceil(year/400);
double yearDown400 = Math.floor(year/400);
if(yearUp400 == yearDown400){
leapYear = true;
}else if(yearUp100 == yearDown100){
leapYear = false;
}else if(yearUp4 == yearDown4){
leapYear = true;
}
//Check non-leap years (28 day February)
if(!leapYear && (Double.compare(day, 28) == 0)){//Check if is subsequent day
subsequentDates = true;
//Check leap years (29 day February)
}else if(leapYear && (Double.compare(day, 29) == 0)){//Check if is subsequent day
subsequentDates = true;
}
}
}
}
}else{
//Check if subsequent years, months, and days from December 31st to January 1st
if((Double.compare(year + 1, year2) == 0) && //Check if subsequent years
(Double.compare(month, 12) == 0) && //and the first date is December
(Double.compare(month2, 1) == 0) && //and the second date is January
(Double.compare(day, 31) == 0) && //and the first date is the 31st
(Double.compare(day2, 1) == 0)){ //and the second date is the 1st
subsequentDates = true;
}
}
return subsequentDates;
}
/**
* Sub-function to convert month text to month integer
* @param month_String the text version of the month (Ex. January).
* @return An Integer (Ex. 1).
*/
public int convertMonth (String month_String){
int month_number = 0;
if (month_String.equalsIgnoreCase("January")){
month_number = 1;
}else if (month_String.equalsIgnoreCase("February")){
month_number = 2;
}else if (month_String.equalsIgnoreCase("March")){
month_number = 3;
}else if (month_String.equalsIgnoreCase("April")){
month_number = 4;
}else if (month_String.equalsIgnoreCase("May")){
month_number = 5;
}else if (month_String.equalsIgnoreCase("June")){
month_number = 6;
}else if (month_String.equalsIgnoreCase("July")){
month_number = 7;
}else if (month_String.equalsIgnoreCase("August")){
month_number = 8;
}else if (month_String.equalsIgnoreCase("September")){
month_number = 9;
}else if (month_String.equalsIgnoreCase("October")){
month_number = 10;
}else if (month_String.equalsIgnoreCase("November")){
month_number = 11;
}else if (month_String.equalsIgnoreCase("December")){
month_number = 12;
}
return month_number;
}
/**
* Performs a Weibull plotting position ranking of the provided flow values in sortedData's second column
* @param sortedData a string[][] containing: column1 = dates (unused) column2 = flowValues to be ranked
* @return a double[][] containing: column1 = x, column2 = y coordinates of the ranking based on a duration
* curve method and Weibull plotting position
*/
public double[][] durationCurveWeibullRanking(String[][] sortedData){
//Sort the Data by flow to prepare for ranking
Arrays.sort(sortedData, new FlowComparator());
// Index new variables of rank, and non-exceedence probability
double[] sorted_rank = new double[sortedData.length];
double total_samples = sortedData.length;
double[][] xyRanks = new double[sortedData.length + 1][2];
xyRanks[0][0] = 0;
int g = 0, h = 0;
//Flow duration curve using "Weibul" distribution
for(int i=1; (i<sortedData.length +1); i++){
if((i != sortedData.length) && (sortedData[i-1][1].equals(sortedData[i][1]))){
//Find how many elements equal
h = i;
while((h != sortedData.length) && (sortedData[h-1][1].equals(sortedData[h][1]))){
h++;
}
//Give all the equal elements the rank of the largest equal element (max rank for tied rank scenarios)
for(g=i; g<=h; g++){
sorted_rank[g-1] = h;
xyRanks[g][0] = (sorted_rank[g-1] / (total_samples))*100;
xyRanks[g-1][1] = Double.parseDouble(sortedData[i-1][1]);
if (g==h){
//If on the last repeated element, set the initial counter "i" that last
//rank value as to not repeat comparing already sorted and ranked values
i=h;
}
}
}else{
sorted_rank[i-1] = i;
xyRanks[i-1][0] = (sorted_rank[i-1] / (total_samples))*100;
xyRanks[i-1][1] = Double.parseDouble(sortedData[i-1][1]);
}
}
//duplicate last value to match output of matlab's "ecdf" function
if(sortedData.length >= 2){
double third_last_entry = Double.parseDouble(sortedData[sortedData.length - 2][1]);
double second_last_entry = Double.parseDouble(sortedData[sortedData.length - 1][1]);
double last_entry = 0;
if((second_last_entry == 0) && (third_last_entry != 0)){
second_last_entry = 0.0001;
}else{
last_entry = second_last_entry - 0.01;
if(last_entry < 0){
last_entry = 0;
}
}
xyRanks[sortedData.length - 1][1] = second_last_entry;
xyRanks[sortedData.length][1] = last_entry;
}
return xyRanks;
}
/**
* Create the dynamic paragraph requested
* @param paragraphTitle title of the desired paragraph
* @param database Database type to give credit to where the data originated in the reference cited section
* @return the dynamic paragraph to be displayed to the user
*/
public String[] dynamicParagraph(String paragraphTitle, String database) {
String[] dynamic_paragraph = new String[9];
//Get today's date for the source reference
DateFormat desiredDateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date currentDate = new Date();
String today = desiredDateFormat.format(currentDate);
String sourceText = "";
//Determine if current source is USGS or STORET to give credit for the data
if(database.equals("USGS")){
sourceText = "Stream flow data and water quality test data courtesy of the U.S. Geological Survey, National Water Information System: Web Interface. http://waterdata.usgs.gov/nwis, accessed " + today;
}else{
sourceText = "Stream flow data and water quality test data courtesy of the U.S. Environmental Protection Agency, STORET. http://www.epa.gov/storet/index.html accessed " + today;
}
//Create the correct dynamic paragraph
if(paragraphTitle.equals("Point Sources and Wastewater Sources: ")){
dynamic_paragraph[1] = "When the target load duration curve is exceeded primarily under Low Flow and Dry Conditions flow intervals; point sources and wastewater source pollution are the likely cause. Note: in order to have wastewater pollution source there must be an upstream wastewater plant discharging into the watershed. Solutions to this problem may include but are not limited to: point source controls, septic system inspection programs, and sanitary sewer overflow repair (Cleland 2003). In urban areas solutions might involve detecting illicit connections from storm water programs (Cleland 2007). In agricultural settings, solutions may include fencing livestock from riparian areas along waterways, or other similar basic management practices (Cleland 2007).";
dynamic_paragraph[2] = "The grey graphed lines are duration curves for each individual year within the analysis period.";
}else if(paragraphTitle.equals("Upper Flow Sources: ")){
dynamic_paragraph[1] = "Upper flow sources are likely when the target is met except under the High Flows, Moist Conditions, and the upper end of Mid-Range Flows. The higher flows under these regions, primarily above 55 percent, are likely due to large amounts of storm runoff. These flows and sources are similar to those of wet-weather, but are due to larger runoff events and higher flows. The increased pollution may be due to combined sewer overflows, storm water runoff from both up river and nearby, and pasture runoff from agricultural areas (Cleland 2003). Solutions to this pollutant problem may include but are not limited to: combined sewer overflow repair, pasture management, and up river and nearby storm runoff solutions such as riparian buffers (Cleland 2003). In agricultural areas remediation efforts aimed towards grassed waterways, conservation tillage, pasture management practices, and contour strips may help alleviate these pollutions issues (Cleland 2007).";
dynamic_paragraph[2] = "The grey graphed lines are duration curves for each individual year within the analysis period.";
}else if(paragraphTitle.equals("Wet-Weather Sources: ")){
dynamic_paragraph[1] = "When the target load duration curve for pollution concentration is satisfied under the Low Flow and Dry Conditions flow intervals but becomes exceeded during Mid-Range Flows and Moist Conditions primarily above 70 percent flow interval, wet-weather related pollution sources are likely. These mid-size flow rates tend to be the result of light storm runoff with increased pollutant transportation through riparian areas and from impervious regions. Some solutions to these problems may include but are not limited to: riparian buffer zones, agricultural conservation tillage, contour strips, and grassed waterways (Cleland 2007). Combined sewer overflow repair, pet waste ordinances, and hobby farm livestock education may also alleviate the pollution problems due to these wet-weather sources (Cleland 2003).";
dynamic_paragraph[2] = "The grey graphed lines are duration curves for each individual year within the analysis period.";
}else if(paragraphTitle.equals("Erosion Sources: ")){
dynamic_paragraph[1] = "Erosion based pollutant sources are likely when the target is met under all the flow intervals except under the High Flows and upper end of the Moist Conditions. Due to the higher velocities of the water during these flows, bank erosion and channel scour are more likely to occur and release pollutants (particularly sediment) into the water. Solutions to this pollutant problem may include but are not limited to: river bank stabilization efforts and channel protection policies (Cleland 2007). Pollutants related to sediment concentration will be those most affected by these efforts.";
dynamic_paragraph[2] = "The grey graphed lines are duration curves for each individual year within the analysis period.";
}else if(paragraphTitle.equals("Multiple Pollution Sources: ")){
dynamic_paragraph[1] = "Most of the flow intervals contain many points which exceed the target. No single pollution source is likely. Please click 'Further Information' for more pollutant identification help.";
dynamic_paragraph[2] = "The grey graphed lines are duration curves for each individual year within the analysis period.";
}else if(paragraphTitle.equals("Flow Duration Curve Overview: ")){
dynamic_paragraph[1] = "A flow duration curve (FDC) is the ranked graphing of river flows on a scale of percent exceedence. For example a flow value associated with the flow interval of 15% means that particular flow value is met or exceeded only 15% of the time. This graph is meant to give a quick overview of the flow ranges, variability, and probability of flows of a river segment during the different flow periods of a river; which are High Flows from 0 to 10 percent flow interval, Moist Conditions 10-40, Mid-Range Flows 40-60, Dry Conditions 60-90, and Low Flows 90-100 (Cleland 2003).";
dynamic_paragraph[2] = "The grey graphed lines are duration curves for each individual year within the analysis period.";
}else if(paragraphTitle.equals("Time Series Graph Overview: ")){
dynamic_paragraph[1] = "A time series graph is a straight scale graphing of available flow data with the oldest date on the bottom left and the most recent date on the bottom right with flows on the y axis. This can be useful to identify hydrographs from storm runoff for small time frames (ie. less than a couple days worth of data points)";
dynamic_paragraph[2] = "";
}else{
dynamic_paragraph[1] = "Although some observed points may exceed the target curve's concentration there is no single apparent pollutant source.";
dynamic_paragraph[2] = "Please click 'Further Model Information' for more pollutant source identification help.";
}
//Create references for paragraph
dynamic_paragraph[0] = paragraphTitle;
dynamic_paragraph[3] = "";
dynamic_paragraph[4] = "References:";
dynamic_paragraph[5] = sourceText;
dynamic_paragraph[6] = "Cleland, B. R. November 2003. TMDL Development from the 'Bottom Up' Part III: Duration Curves and Wet-Weather Assessments. National TMDL Science and Policy 2003.";
dynamic_paragraph[7] = "Cleland, B. R. August 2007. An Approach for Using Load Duration Curves in the Development of TMDLs. National TMDL Science and Policy 2007.";
return dynamic_paragraph;
}
/**
* Reduces the provided data to only within the specified year
* @param allData all the data for the to be minimized by year (column1 = date (yyyy-mm-dd), column2 = value)
* @param year the specified year (ex "1981")
* @return an array (same format as allData) containing the dates and values that occur within the specified year
*/
public String[][] getAnnualData(String[][] allData, String year){
//Remove data that are outside specified year
int ctr = 0;
for(int i=0; i<allData.length; i++){
String tempYear = allData[i][0].substring(0,4);
if(year.compareToIgnoreCase(tempYear) == 0){
ctr++;
}
}
String[][] SeasonalData = new String[ctr][2];
ctr = 0;
for(int i=0; i<allData.length; i++){
String tempYear = allData[i][0].substring(0,4);
if(year.compareToIgnoreCase(tempYear) == 0){
SeasonalData[ctr][0] = allData[i][0];
SeasonalData[ctr][1] = allData[i][1];
ctr++;
}
}
return SeasonalData;
}
/**
* Reduces the provided water quality data to only within the user specified season
* @param allWQdata all the WQdata for the user specified date range to be minimized by season (column1 = date (yyyy-mm-dd), column2 = value)
* @param seasonBegin the user specified begin month of the season (ex "February")
* @param seasonEnd the user specified end month of the season (ex "April")
* @return an array containing the dates and values of water quality tests that occur within the specified season
*/
public String[][] getSeasonalWQData(String[][] allWQdata, String seasonBegin, String seasonEnd){
//Convert season month into season integer
int seasonBegin_number = convertMonth(seasonBegin);
int seasonEnd_number = convertMonth(seasonEnd);
//Remove water quality tests that are outside user specified seasonal range
int ctr = 0;
if(seasonBegin_number < seasonEnd_number){
for(int i=0; i<allWQdata.length; i++){
int tempdate = Integer.parseInt(allWQdata[i][0].substring(5,7));
if((tempdate > seasonBegin_number) && (tempdate < seasonEnd_number)){
ctr++;
}
}
}else{
for(int i=0; i<allWQdata.length; i++){
int tempdate = Integer.parseInt(allWQdata[i][0].substring(5,7));
if((tempdate > seasonBegin_number) || (tempdate < seasonEnd_number)){
ctr++;
}
}
}
String[][] SeasonalWQdata = new String[ctr][2];
ctr = 0;
if(seasonBegin_number < seasonEnd_number){
for(int i=0; i<allWQdata.length; i++){
int tempdate = Integer.parseInt(allWQdata[i][0].substring(5,7));
if((tempdate > seasonBegin_number) && (tempdate < seasonEnd_number)){
SeasonalWQdata[ctr][0] = allWQdata[i][0];
SeasonalWQdata[ctr][1] = allWQdata[i][1];
ctr++;
}
}
}else{
for(int i=0; i<allWQdata.length; i++){
int tempdate = Integer.parseInt(allWQdata[i][0].substring(5,7));
if((tempdate > seasonBegin_number) || (tempdate < seasonEnd_number)){
SeasonalWQdata[ctr][0] = allWQdata[i][0];
SeasonalWQdata[ctr][1] = allWQdata[i][1];
ctr++;
}
}
}
return SeasonalWQdata;
}
/**
* Main graphing function for FDC analysis, plots the provided xyRanks as x,y points on a graph
* with or without low flow analysis (low_mQn) added depending if it is non-zero or not
* @param mainFolder the output location of the graph
* @param stationName the stationID and name of the station for which this timeseries graph is being created (will be incoperated into the graph title)
* @param startDatd the beginning date of analysis (yyyy-mm-dd)
* @param endDate the ending date of analysis (yyyy-mm-dd)
* @param sortedData_combined String[][] containing 2 columns, first column dates (yyyy-mm-dd) second column y values of flow of all the data
* @param sortedData_user String[][] containing 2 columns, first column dates (yyyy-mm-dd) second column y values of flow of the user data only
* @param low_mQn the low flow analysis value, if it is not zero it will be graphed, otherwise it will not be graphed
* @param low_m the "m" value of an mQn flow analysis (like 7Q10)
* @param low_n the "n" value of an mQn flow analysis (like 7Q10)
* @param mQnHide if true then mQn wil be not graphed, otherwise it will be shown in the legend
* @throws IOException
*/
public void graphFDC(String mainFolder,
String stationName,
String startDate,
String endDate,
String[][] sortedData_combined,
String[][] sortedData_user,
double low_mQn,
double low_m,
double low_n,
boolean mQnHide) throws IOException{
//Perform Weibull Plotting Position Ranking for the Flow Duration Curve Method
double[][] xyRanks = durationCurveWeibullRanking(sortedData_combined);
//Graph the complete flow duration curve for the time period
XYSeries FDC_xy = new XYSeries("FDC");
XYSeries low_mQn_xy = new XYSeries(String.valueOf(low_m) + "Q" + String.valueOf(low_n) + " low flow");
XYSeries FDC_user = new XYSeries("User Data");
double y = 0, x = 0;
int ctr = 0, seriesIndex = 0;
for(int i=0; i<xyRanks.length; i++){
y = xyRanks[i][1];
x = xyRanks[i][0];
FDC_xy.add(x, y);
if(i != 0){//get low flow intersection point for mQn flow
if(xyRanks[i][1] < low_mQn && xyRanks[i-1][1] >= low_mQn){
low_mQn_xy.add(x,low_mQn);
}
}
//Create an XYSeries only if userdata is not empty
if(sortedData_user.length > 0 && sortedData_user.length > ctr){
double userValue = Double.parseDouble(sortedData_user[ctr][1]);
if(Double.compare(xyRanks[i][1], userValue) == 0){
FDC_user.add(x, y);
ctr++;
}
}
}
//Graph resulting FDC and flow value data
XYPlot plot = new XYPlot();
boolean showLegend = false;
//Create X Axis
ValueAxis xAxis = new NumberAxis("Flow Duration Interval [%]");
xAxis.setRange(0,100);
plot.setDomainAxis(0, xAxis);
//Set log-scale y axis
LogarithmicAxis yAxis = new LogarithmicAxis("Discharge [cfs]");
yAxis.setAllowNegativesFlag(true);
plot.setRangeAxis(0, yAxis);
//Create a graph with the FDC (line)
XYDataset FDC_data = new XYSeriesCollection(FDC_xy);
XYItemRenderer renderer1 = new XYLineAndShapeRenderer(true, false);
renderer1.setSeriesPaint(0, Color.BLACK);
//Set the FDC line data, renderer, and axis into plot
plot.setDataset(seriesIndex, FDC_data);
plot.setRenderer(seriesIndex, renderer1);
//Map the line to the first Domain and first Range
plot.mapDatasetToDomainAxis(seriesIndex, 0);
plot.mapDatasetToRangeAxis(seriesIndex, 0);
seriesIndex++;
//Create low mQn point
if(Double.compare(low_mQn, 0) != 0){//only show mQn if it is not zero
XYDataset low_mQn_data = new XYSeriesCollection(low_mQn_xy);
XYItemRenderer renderer = new XYLineAndShapeRenderer(false, true);
renderer.setSeriesShape(0, new Ellipse2D.Double(-3.0, 3.0, 6.0, 6.0));
renderer.setSeriesPaint(0, Color.red);
plot.setDataset(seriesIndex,low_mQn_data);
plot.setRenderer(seriesIndex,renderer);
plot.mapDatasetToDomainAxis(seriesIndex,0);
plot.mapDatasetToRangeAxis(seriesIndex,0);
seriesIndex++;
showLegend = true;
}
//Create user data points
if(sortedData_user.length != 0){//only show user points if it is not zero
XYDataset user_data = new XYSeriesCollection(FDC_user);
XYItemRenderer renderer = new XYLineAndShapeRenderer(false, true);
renderer.setSeriesPaint(0, Color.black);
plot.setDataset(seriesIndex,user_data);
plot.setRenderer(seriesIndex,renderer);
plot.mapDatasetToDomainAxis(seriesIndex, 0);
plot.mapDatasetToRangeAxis(seriesIndex, 0);
seriesIndex++;
showLegend = true;
}
//Graph a FDC for each year in time period
String currentYear = startDate.substring(0,4);
String finalYear = endDate.substring(0,4);
boolean moreYears = xyRanks.length > 0;
while(moreYears){
//Get current year's data and graph it
String[][] partialData = getAnnualData(sortedData_combined, currentYear);
double[][] partialRanks = durationCurveWeibullRanking(partialData);
graphSeries(plot, partialRanks, Color.lightGray, seriesIndex);
seriesIndex++;
int nextYear = Integer.parseInt(currentYear) + 1;
if(finalYear.compareToIgnoreCase(String.valueOf(nextYear)) >= 0){
currentYear = String.valueOf(nextYear);
}else{
moreYears = false;
}
}
//Set extra plot preferences
plot.setOutlinePaint(Color.black);
plot.setDomainGridlinePaint(Color.black);
plot.setRangeGridlinePaint(Color.black);
setAxisFonts(plot);
//Add Flow Range Labels:
plot = addIntervalLabel(plot, "High Flow", 0, 10);
plot = addIntervalLabel(plot, "Moist Conditions", 10, 40);
plot = addIntervalLabel(plot, "Mid-Range Flows", 40, 60);
plot = addIntervalLabel(plot, "Dry Conditions", 60, 90);
plot = addIntervalLabel(plot, "Low Flow", 90, 100);
//Graph plot onto JfreeChart
String graph_title = "Flow Duration Curve for Station No. " + stationName;
JFreeChart chart = new JFreeChart(graph_title, titleFont, plot, showLegend);
//Write a results file containing the flow duration curve interval (non-exceedence values) and their corresponding discharge values
writeResults(xyRanks, mainFolder, "Discharge (cfs)");
//Save resulting graph for proof it works
try{
guiDC_Model model = new guiDC_Model();
String path = mainFolder + File.separator + model.getGraph();
ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
System.out.println("JFreeChart created properly at: " + path);
}catch(IOException e){
System.out.println("A problem occurred while trying to creating the chart for station '" + stationName + "'. Error: USGSFDC0003");
System.out.println(e.toString());
}
}
/**
* Sub-graphing function to create combined-range graph
* @param mainFolder the location where the graph will be saved
* @param stationName the name of the station to be used in error reporting
* @param graph_title the desired graph title.
* @param yaxis_title the desired y-axis title.
* @param wqTest the test code of the water quailty test being graphed
* @param sortedData_combined the load duration curve XY line data of combined data
* @param sortedData_user the load duration curve XY line data of user data only
* @param WQdata_combined the observed water quality points XY scatter of combined data
* @param WQdata_user the user data water quality points XY scatter of user data only
* @param seasonalWQ_combined the observed water quality points within the "season" XY scatter of combined data
* @param seasonalWQ_user the user data water quality points within the "season" XY scatter of user data only
* @param mQn the value of the mQn flow (if it is zero then don't show this line in the graph)
* @param m the value of m from mQn flow calculations
* @param n the value of n from mqn flow calculations
* @return the name/title of the dynamic paragraph to be created to accompany the graph created during this function
* @throws IOException
*/
public Object[] graphLDC(String mainFolder,
String stationName,
String startDate,
String endDate,
String graph_title,
String yaxis_title,
String wqTest,
double conversion,
double wqTarget,
String[][] sortedData_combined,
String[][] sortedData_user,
String[][] WQdata_combined,
String[][] WQdata_user,
String[][] seasonalWQ_combined,
String[][] seasonalWQ_user,
double mQn,
double m,
double n) throws IOException {
//Perform Weibull Plotting Position Ranking for the Load Duration Curve Method
//Note: this data is already converted to loads, see "guiDC_Model.createLDC"
double[][] xyRanks = durationCurveWeibullRanking(sortedData_combined);
//Break scattered WQ points data into the 5 flow sub classifications 0-10, 10-40, 40-60, 60-90, 90-100
//which are High flows, Moist conditions, Mid-range flows, Dry conditions, and low flows respectively Assimilative
XYSeries LDC_xy = new XYSeries("LDC");
XYSeries LDC_user = new XYSeries("LDC User Data");
XYSeries WQ_pts = new XYSeries("Water Quality Obs.");
XYSeries WQ_pts_user = new XYSeries("Water Quality Obs. User Data");
XYSeries Seasonal_WQ_pts = new XYSeries("Seasonal Water Quality Obs.");
XYSeries Seasonal_WQ_pts_user = new XYSeries("Seasonal Water Quality Obs. User Data");
XYSeries mQn_xy = new XYSeries("Current " + String.valueOf(m) + "Q" + String.valueOf(n) + " water quality standard");
double y = 0, x = 0;
int ctr = 0;
for (int i=0; i<xyRanks.length; i++){
y = xyRanks[i][1];
x = xyRanks[i][0];
LDC_xy.add(x, y);
if(y > mQn){
mQn_xy.add(x,mQn);
}else{
mQn_xy.add(x, y);
}
//Create an XYSeries only if userdata is not empty
if(sortedData_user.length > 0 && sortedData_user.length > ctr){
double userValue = Double.parseDouble(sortedData_user[ctr][1]);
if(Double.compare(xyRanks[i][1], userValue) == 0){
LDC_user.add(x, y);
ctr++;
}
}
}
//Add WQ test data to graph series and generate box plot data
List<Double> high_data = new ArrayList<Double>();
List<Double> moist_data = new ArrayList<Double>();
List<Double> mid_data = new ArrayList<Double>();
List<Double> dry_data = new ArrayList<Double>();
List<Double> low_data = new ArrayList<Double>();
double highexceed_count = 0, moistexceed_count =0, midexceed_count =0, dryexceed_count =0,
lowexceed_count =0, totalexceed_count =1, totalCount = 0;
double WQ_load = 0, Seasonal_WQ_load=0;
ctr = 0;
for(int i=0;i<WQdata_combined.length;i++){
for(int j=0; j<sortedData_combined.length;j++){
if(WQdata_combined[i][0].equalsIgnoreCase(sortedData_combined[j][0])){
totalCount = totalCount+1;
//Create an XYSeries for WQ points
double tempWQValue = Double.parseDouble(WQdata_combined[i][1]);
WQ_load = (Double.parseDouble(sortedData_combined[j][1]))*conversion*tempWQValue/wqTarget;
WQ_pts.add(xyRanks[j][0],WQ_load);
//Create an XYSeries only if user WQ data is not empty
if(WQdata_user.length > 0 && WQdata_user.length > ctr){
if(WQdata_user[ctr][0].equalsIgnoreCase(sortedData_combined[j][0])){
WQ_pts_user.add(xyRanks[j][0],WQ_load);
ctr++;
}
}
//Also add to category box plot data
if((xyRanks[j][0] <= 10) & (xyRanks[j][0] > 0)){
high_data.add(WQ_load);
if(WQ_load > xyRanks[j][1]){
highexceed_count++;
}
}
if((xyRanks[j][0] <= 40) & (xyRanks[j][0] > 10)){
moist_data.add(WQ_load);
if(WQ_load > xyRanks[j][1]){
moistexceed_count++;
}
}
if((xyRanks[j][0] <= 60) & (xyRanks[j][0] > 40)){
mid_data.add(WQ_load);
if(WQ_load > xyRanks[j][1]){
midexceed_count++;
}
}
if((xyRanks[j][0] <= 90) & (xyRanks[j][0] > 60)){
dry_data.add(WQ_load);
if(WQ_load > xyRanks[j][1]){
dryexceed_count++;
}
}
if((xyRanks[j][0] <= 100) & (xyRanks[j][0] > 90)){
low_data.add(WQ_load);
if(WQ_load > xyRanks[j][1]){
lowexceed_count++;
}
}
for(int k=0;k<seasonalWQ_combined.length;k++){
if(seasonalWQ_combined[k][0].equalsIgnoreCase(sortedData_combined[j][0])){
double tempSeasonalWQValue = Double.parseDouble(seasonalWQ_combined[k][1]);
Seasonal_WQ_load = (Double.parseDouble(sortedData_combined[j][1]))*conversion*tempSeasonalWQValue/wqTarget;
Seasonal_WQ_pts.add(xyRanks[j][0],Seasonal_WQ_load);
}
}
for(int k=0;k<seasonalWQ_user.length;k++){
if(seasonalWQ_user[k][0].equalsIgnoreCase(sortedData_combined[j][0])){
double tempSeasonalWQValue = Double.parseDouble(seasonalWQ_user[k][1]);
Seasonal_WQ_load = (Double.parseDouble(sortedData_combined[j][1]))*conversion*tempSeasonalWQValue/wqTarget;
Seasonal_WQ_pts_user.add(xyRanks[j][0],Seasonal_WQ_load);
}
}
break;
}
}
}
//Determine which pollution source is likely based on location of exceeded water quality tests.
totalexceed_count = highexceed_count + moistexceed_count + midexceed_count + dryexceed_count + lowexceed_count;
if (totalexceed_count == 0){
totalexceed_count = 1;
}
//Takes the counts of exceedence points and makes them relative to the total count of exceedences.
highexceed_count = highexceed_count/totalexceed_count;
moistexceed_count = moistexceed_count/totalexceed_count;
midexceed_count = midexceed_count/totalexceed_count;
dryexceed_count = dryexceed_count/totalexceed_count;
lowexceed_count = lowexceed_count/totalexceed_count;
//Pollution Source paragraph is only for nutrient tests so check if the current test is for nutrients:
boolean nutrient = false;
if (wqTest.equalsIgnoreCase("00597") || wqTest.equalsIgnoreCase("00600") || wqTest.equalsIgnoreCase("00601") || wqTest.equalsIgnoreCase("00602") ||
wqTest.equalsIgnoreCase("00604") || wqTest.equalsIgnoreCase("00605") || wqTest.equalsIgnoreCase("00606") || wqTest.equalsIgnoreCase("00607") ||
wqTest.equalsIgnoreCase("00608") || wqTest.equalsIgnoreCase("00610") || wqTest.equalsIgnoreCase("00613") || wqTest.equalsIgnoreCase("00615") ||
wqTest.equalsIgnoreCase("00618") || wqTest.equalsIgnoreCase("00619") || wqTest.equalsIgnoreCase("00620") || wqTest.equalsIgnoreCase("00623") ||
wqTest.equalsIgnoreCase("00624") || wqTest.equalsIgnoreCase("00625") || wqTest.equalsIgnoreCase("00628") || wqTest.equalsIgnoreCase("00630") ||
wqTest.equalsIgnoreCase("00631") || wqTest.equalsIgnoreCase("00635") || wqTest.equalsIgnoreCase("00636") || wqTest.equalsIgnoreCase("00639") ||
wqTest.equalsIgnoreCase("00650") || wqTest.equalsIgnoreCase("00653") || wqTest.equalsIgnoreCase("00660") || wqTest.equalsIgnoreCase("00665") ||
wqTest.equalsIgnoreCase("00666") || wqTest.equalsIgnoreCase("00667") || wqTest.equalsIgnoreCase("00669") || wqTest.equalsIgnoreCase("00670") ||
wqTest.equalsIgnoreCase("00671") || wqTest.equalsIgnoreCase("00672") || wqTest.equalsIgnoreCase("00673") || wqTest.equalsIgnoreCase("00674") ||
wqTest.equalsIgnoreCase("00675") || wqTest.equalsIgnoreCase("00676") || wqTest.equalsIgnoreCase("00677") || wqTest.equalsIgnoreCase("00678") ||
wqTest.equalsIgnoreCase("01425") || wqTest.equalsIgnoreCase("01465") || wqTest.equalsIgnoreCase("49567") || wqTest.equalsIgnoreCase("49570") ||
wqTest.equalsIgnoreCase("62854") || wqTest.equalsIgnoreCase("62855") || wqTest.equalsIgnoreCase("64832") || wqTest.equalsIgnoreCase("70507") ||
wqTest.equalsIgnoreCase("71845") || wqTest.equalsIgnoreCase("71846") || wqTest.equalsIgnoreCase("71850") || wqTest.equalsIgnoreCase("71851") ||
wqTest.equalsIgnoreCase("71855") || wqTest.equalsIgnoreCase("71856") || wqTest.equalsIgnoreCase("71886") || wqTest.equalsIgnoreCase("71887") ||
wqTest.equalsIgnoreCase("71888") || wqTest.equalsIgnoreCase("76008") || wqTest.equalsIgnoreCase("76009") || wqTest.equalsIgnoreCase("76010") ||
wqTest.equalsIgnoreCase("82046") || wqTest.equalsIgnoreCase("83044") || wqTest.equalsIgnoreCase("83047") || wqTest.equalsIgnoreCase("83050") ||
wqTest.equalsIgnoreCase("83053") || wqTest.equalsIgnoreCase("83056") || wqTest.equalsIgnoreCase("83059") || wqTest.equalsIgnoreCase("83062") ||
wqTest.equalsIgnoreCase("83065") || wqTest.equalsIgnoreCase("83068") || wqTest.equalsIgnoreCase("83071") || wqTest.equalsIgnoreCase("83074") ||
wqTest.equalsIgnoreCase("83077") || wqTest.equalsIgnoreCase("83080") || wqTest.equalsIgnoreCase("83083") || wqTest.equalsIgnoreCase("83086") ||
wqTest.equalsIgnoreCase("83089") || wqTest.equalsIgnoreCase("83092") || wqTest.equalsIgnoreCase("83095") || wqTest.equalsIgnoreCase("83098") ||
wqTest.equalsIgnoreCase("83101") || wqTest.equalsIgnoreCase("83108") || wqTest.equalsIgnoreCase("83111") || wqTest.equalsIgnoreCase("83114") ||
wqTest.equalsIgnoreCase("83117") || wqTest.equalsIgnoreCase("83326") || wqTest.equalsIgnoreCase("83329") || wqTest.equalsIgnoreCase("83332") ||
wqTest.equalsIgnoreCase("83335") || wqTest.equalsIgnoreCase("83338") || wqTest.equalsIgnoreCase("83341") || wqTest.equalsIgnoreCase("83344") ||
wqTest.equalsIgnoreCase("83347") || wqTest.equalsIgnoreCase("83350") || wqTest.equalsIgnoreCase("83353") || wqTest.equalsIgnoreCase("83356") ||
wqTest.equalsIgnoreCase("83359") || wqTest.equalsIgnoreCase("83362") || wqTest.equalsIgnoreCase("83365") || wqTest.equalsIgnoreCase("83368") ||
wqTest.equalsIgnoreCase("83371") || wqTest.equalsIgnoreCase("83374") || wqTest.equalsIgnoreCase("83377") || wqTest.equalsIgnoreCase("83380") ||
wqTest.equalsIgnoreCase("83383") || wqTest.equalsIgnoreCase("83390") || wqTest.equalsIgnoreCase("83393") || wqTest.equalsIgnoreCase("83396") ||
wqTest.equalsIgnoreCase("83399") || wqTest.equalsIgnoreCase("90859") || wqTest.equalsIgnoreCase("91003") || wqTest.equalsIgnoreCase("91004") ||
wqTest.equalsIgnoreCase("99116") || wqTest.equalsIgnoreCase("99120") || wqTest.equalsIgnoreCase("99121") || wqTest.equalsIgnoreCase("99122") ||
wqTest.equalsIgnoreCase("99123") || wqTest.equalsIgnoreCase("99124") || wqTest.equalsIgnoreCase("99125") || wqTest.equalsIgnoreCase("99126") ||
wqTest.equalsIgnoreCase("99133") || wqTest.equalsIgnoreCase("99410") || wqTest.equalsIgnoreCase("99411") || wqTest.equalsIgnoreCase("99412") ||
wqTest.equalsIgnoreCase("99413") || wqTest.equalsIgnoreCase("99414") || wqTest.equalsIgnoreCase("99415") || wqTest.equalsIgnoreCase("99416") ||
wqTest.equalsIgnoreCase("99889") || wqTest.equalsIgnoreCase("99891") || wqTest.equalsIgnoreCase("99892") || wqTest.equalsIgnoreCase("99893") ||
wqTest.equalsIgnoreCase("99894")){
nutrient = true;
}
//Decide based on properties of the data which pollution source is likely and display the paragraph
//about that pollution source.
String paragraphTitle = "";
if (nutrient == true && (lowexceed_count > 0 || dryexceed_count > 0) &&
(midexceed_count < 0.1 && moistexceed_count < 0.075 && highexceed_count < 0.05) &&
((totalexceed_count/totalCount) > 0.15 || totalexceed_count > 5)){
paragraphTitle = "Point Sources and Wastewater Sources: ";
//If the pollutant target is exceeded under low flows, it is probably a "point source".
}else if (nutrient == true && (moistexceed_count > 0 && midexceed_count > 0) &&
(dryexceed_count < 0.1 && dryexceed_count > 0 && lowexceed_count < 0.05) &&
((totalexceed_count/totalCount) > 0.15 || totalexceed_count > 5)){
paragraphTitle = "Upper Flow Sources: ";
//If the pollutant target is exceeded primarily beginning around 55% flow interval,
//it is probably an "upper-flow" driven non-point-source pollution.
}else if (nutrient == true && (moistexceed_count > 0 && midexceed_count > 0 && dryexceed_count > 0 && lowexceed_count < 0.12) &&
(moistexceed_count < 0.12 && midexceed_count < 0.12 && dryexceed_count < 0.1) &&
((totalexceed_count/totalCount) > 0.15 || totalexceed_count > 5)){
paragraphTitle = "Wet-Weather Sources: ";
//If the pollutant target is exceeded primarily beginning around 70% flow interval,
//it is probably a "wet-weather" driven non-point-source pollution.
}else if (nutrient == true && (highexceed_count > 0 || (highexceed_count > 0 && moistexceed_count > 0)) &&
midexceed_count < 0.2 &&
dryexceed_count < 0.1 &&
lowexceed_count < 0.1 &&
((totalexceed_count/totalCount) > 0.15 || totalexceed_count > 5)){
paragraphTitle = "Erosion Sources: ";
//If the pollutant target is exceeded primarily in the high flow interval,
//"erosion" processes are the likely pollution source.
}else if (nutrient == true && (dryexceed_count > 0.1 && midexceed_count > 0.1 && moistexceed_count > 0.1) ||
(totalexceed_count/totalCount) > 0.5){
paragraphTitle = "Multiple Pollution Sources: ";
//if the target is almost always exceeded in most of the flow intervals,
//display that multiple sources are likely.
}else{
paragraphTitle = "No Apparent Pollution Source: ";
//If there is no easily identified pollution source, display that information along
//with instructions to get to more information.
}
//Create a graph with the LDC (line) and WQ points (scatter)
XYPlot plot = new XYPlot();
XYDataset LDC_data = new XYSeriesCollection(LDC_xy);
XYItemRenderer renderer1 = new XYLineAndShapeRenderer(true, false);
//Create X Axis
ValueAxis xAxis = new NumberAxis("Duration Curve Interval [%]");
xAxis.setRange(0,100);
plot.setDomainAxis(0, xAxis);
//Set log-scale Y axis with scientific notation
LogarithmicAxis yAxis = new LogarithmicAxis(yaxis_title);
yAxis.setAllowNegativesFlag(true);
yAxis.setLog10TickLabelsFlag(true);
plot.setRangeAxis(0, yAxis);
//Put the LDC line data, renderer, and axis into plot
plot.setDataset(0, LDC_data);
plot.setRenderer(0, renderer1);
//Put the line on the first Domain and first Range
plot.mapDatasetToDomainAxis(0, 0);
plot.mapDatasetToRangeAxis(0, 0);
//Create mQn Line
if(mQn != 0){//only show mQn if it is not zero
XYDataset mQn_data = new XYSeriesCollection(mQn_xy);
XYItemRenderer renderer2 = new XYLineAndShapeRenderer(true, false);
renderer2.setSeriesPaint(0, Color.black);
plot.setDataset(1,mQn_data);
plot.setRenderer(1,renderer2);
plot.mapDatasetToDomainAxis(1,0);
plot.mapDatasetToRangeAxis(1,0);
}
//Create user data points
if(sortedData_user.length != 0){//only show user points if it is not zero
//User LDC line
XYDataset user_data = new XYSeriesCollection(LDC_user);
XYItemRenderer renderer = new XYLineAndShapeRenderer(false, true);
renderer.setSeriesPaint(0, Color.red);
plot.setDataset(2,user_data);
plot.setRenderer(2,renderer);
plot.mapDatasetToDomainAxis(2,0);
plot.mapDatasetToRangeAxis(2,0);
}
if(WQdata_user.length != 0){//only show user points if it is not zero
//User WQ points
XYDataset user_wq_data = new XYSeriesCollection(WQ_pts_user);
XYItemRenderer renderer = new XYLineAndShapeRenderer(false, true);
renderer.setSeriesShape(0, new Rectangle2D.Double(-3.0, 3.0, 6.0, 6.0));
renderer.setSeriesPaint(0, new Color(105, 0, 255));
plot.setDataset(4,user_wq_data);
plot.setRenderer(4,renderer);
plot.mapDatasetToDomainAxis(4,0);
plot.mapDatasetToRangeAxis(4,0);
}
if(seasonalWQ_user.length != 0){//only show user points if it is not zero
//User seasonal WQ points
XYDataset user_seasonal_wq_data = new XYSeriesCollection(Seasonal_WQ_pts_user);
XYItemRenderer renderer = new XYLineAndShapeRenderer(false, true);
renderer.setSeriesShape(0, new Rectangle2D.Double(-1.5, 4.5, 3.0, 3.0));
renderer.setSeriesPaint(0, Color.gray);
plot.setDataset(3,user_seasonal_wq_data);
plot.setRenderer(3,renderer);
plot.mapDatasetToDomainAxis(3,0);
plot.mapDatasetToRangeAxis(3,0);
}
//Create the scatter data and renderer
XYDataset scatter_data3 = new XYSeriesCollection(WQ_pts);
XYDataset scatter_data4 = new XYSeriesCollection(Seasonal_WQ_pts);
XYItemRenderer renderer3 = new XYLineAndShapeRenderer(false, true);
XYItemRenderer renderer4 = new XYLineAndShapeRenderer(false, true);
renderer3.setSeriesShape(0, new Rectangle2D.Double(-3.0, 3.0, 6.0, 6.0));
renderer4.setSeriesShape(0, new Rectangle2D.Double(-1.5, 4.5, 3.0, 3.0));
renderer3.setSeriesPaint(0, Color.GREEN);
renderer4.setSeriesPaint(0, Color.black);
//Put the scatter data and renderer into plot
plot.setDataset(7, scatter_data3);
plot.setDataset(6, scatter_data4);
plot.setRenderer(7, renderer3);
plot.setRenderer(6, renderer4);
//Put the scatters on the first Domain and first Range
plot.mapDatasetToDomainAxis(6, 0);
plot.mapDatasetToDomainAxis(7, 0);
plot.mapDatasetToRangeAxis(6, 0);
plot.mapDatasetToRangeAxis(7, 0);
//Format median series
XYSeries median_series = new XYSeries("Medians");
XYDataset median_scatter = new XYSeriesCollection(median_series);
XYItemRenderer renderer_median = new XYLineAndShapeRenderer(false, true);
renderer_median.setSeriesShape(0, new Rectangle2D.Double(-4.0, 0.0, 8.0, 0.5));//new Ellipse2D.Double(-4, -4, 8, 8));
renderer_median.setSeriesPaint(0, Color.red);
renderer_median.setSeriesVisibleInLegend(0, false);
plot.setDataset(5, median_scatter);
plot.setRenderer(5, renderer_median);
//Create box plot of WQ points
boolean showOutliers = false, showExtremeOutliers = false;
if(high_data.size() > 1){
//Create quartile rectangle, min-max line, and median line
Object[] returnArray = boxplot_shapes(plot, 5, high_data, 8, showOutliers, showExtremeOutliers);
plot = (XYPlot) returnArray[0];
showOutliers = (Boolean) returnArray[1];
showExtremeOutliers = (Boolean) returnArray[2];
}
if(moist_data.size() > 1){
//Create quartile rectangle, min-max line, and median line
Object[] returnArray = boxplot_shapes(plot, 25, moist_data, 13, showOutliers, showExtremeOutliers);
plot = (XYPlot) returnArray[0];
showOutliers = (Boolean) returnArray[1];
showExtremeOutliers = (Boolean) returnArray[2];
}
if(mid_data.size() > 1){
//Create quartile rectangle, min-max line, and median line
Object[] returnArray = boxplot_shapes(plot, 50, mid_data, 18, showOutliers, showExtremeOutliers);
plot = (XYPlot) returnArray[0];
showOutliers = (Boolean) returnArray[1];
showExtremeOutliers = (Boolean) returnArray[2];
}
if(dry_data.size() > 1){
//Create quartile rectangle, min-max line, and median line
Object[] returnArray = boxplot_shapes(plot, 75, dry_data, 23, showOutliers, showExtremeOutliers);
plot = (XYPlot) returnArray[0];
showOutliers = (Boolean) returnArray[1];
showExtremeOutliers = (Boolean) returnArray[2];
}
if(low_data.size() > 1){
//Create quartile rectangle, min-max line, and median line
Object[] returnArray = boxplot_shapes(plot, 95, low_data, 31, showOutliers, showExtremeOutliers);
plot = (XYPlot) returnArray[0];
showOutliers = (Boolean) returnArray[1];
showExtremeOutliers = (Boolean) returnArray[2];
}
//Graph a FDC for each year in time period
int seriesIndex = 32;
String currentYear = startDate.substring(0,4);
String finalYear = endDate.substring(0,4);
boolean moreYears = xyRanks.length > 0;
while(moreYears){
//Get current year's data and graph it
String[][] partialData = getAnnualData(sortedData_combined, currentYear);
double[][] partialRanks = durationCurveWeibullRanking(partialData);
graphSeries(plot, partialRanks, Color.lightGray, seriesIndex);
seriesIndex++;
int nextYear = Integer.parseInt(currentYear) + 1;
if(finalYear.compareToIgnoreCase(String.valueOf(nextYear)) >= 0){
currentYear = String.valueOf(nextYear);
}else{
moreYears = false;
}
}
//Add Flow Range Labels
plot = addIntervalLabel(plot, "High Flow", 0, 10);
plot = addIntervalLabel(plot, "Moist Conditions", 10, 40);
plot = addIntervalLabel(plot, "Mid-Range Flows", 40, 60);
plot = addIntervalLabel(plot, "Dry Conditions", 60, 90);
plot = addIntervalLabel(plot, "Low Flow", 90, 100);
//Set extra plot preferences
plot.setOutlinePaint(Color.black);
plot.setDomainGridlinePaint(Color.black);
plot.setRangeGridlinePaint(Color.black);
setAxisFonts(plot);
//Graph plot onto JfreeChart
JFreeChart chart = new JFreeChart(graph_title, titleFont, plot, true);
//Set legend Font
LegendTitle legendTitle = chart.getLegend();
legendTitle.setItemFont(masterFont);
//Write a results file containing the flow duration curve interval (non-exceedence values) and their corresponding discharge values
writeResults(xyRanks, mainFolder, yaxis_title);
//Save resulting graph
try{
guiDC_Model model = new guiDC_Model();
String path = mainFolder + File.separator + model.getGraph();
ChartUtilities.saveChartAsJPEG(new File(path), chart, 1280, 800);
System.out.println("JFreeChart created properly at: " + path);
}catch(IOException e){
System.out.println("A problem occurred while trying to creating the chart for station '" + stationName + "'. Error: USGSLDC0003");
System.out.println(e.toString());
}
Object[] returnArray = {paragraphTitle, totalCount};
return returnArray;
}
/**
* Graphs the provided "series" as an XY series with x as the first column y as the second column in the provided color
* on the provided XYPlot, this series is not visible in the legend
* @param plot the XYPlot to add the "series" to
* @param series a double[][] array with series[all][0] = x-values, series[all][1] = y-values
* @param lineColor the color of the line to be graphed
* @param seriesIndex the graph index of the series to be plotted
* @return the provided XYPlot with the series added to it with the above properties
*/
public XYPlot graphSeries(XYPlot plot, double[][] series, Color lineColor, int seriesIndex){
XYSeries xySeries = new XYSeries("");
for(int i=0; i<series.length; i++){
double y = series[i][1];
double x = series[i][0];
xySeries.add(x, y);
}
//Create a graph with the line
XYDataset xyDataset = new XYSeriesCollection(xySeries);
XYItemRenderer renderer = new XYLineAndShapeRenderer(true, false);
renderer.setSeriesPaint(0, lineColor);
renderer.setSeriesVisibleInLegend(0, false);
//Set the FDC line data, renderer, and axis into plot
plot.setDataset(seriesIndex, xyDataset);
plot.setRenderer(seriesIndex, renderer);
//Map the line to the first Domain and first Range
plot.mapDatasetToDomainAxis(0, 0);
plot.mapDatasetToRangeAxis(0, 0);
return plot;
}
/**
* Sorts the String[][] by the first column (dates) and then removes duplicate
* date values and returns an array of equal or lesser size than the original
* @param currentStringArray the String[][] to be sorted and removed, column1 = dates (yyyy-mm-dd), column2 = values, other columns will also be kept
* @return a String[][] of equal or lesser size than the original containing the original data sorted by the first column
*/
public String[][] removeDuplicateDates(String[][] currentStringArray){
//Sort the Data by date to remove duplicate date entries
Arrays.sort(currentStringArray, new DateComparator());
//Check and remove duplicate days as to correct statistical implication of duplicate flow values on a day
int ctr=0;
for(int i=0; i<(currentStringArray.length); i++){
if(i == 0){
ctr++;
continue;
}
if(!currentStringArray[i-1][0].equals(currentStringArray[i][0])){
ctr++;
}
}
String[][] sortedData = new String[ctr][2];
ctr=0;
for(int i=0; i<(currentStringArray.length); i++){
if(i==0){
sortedData[ctr][0] = currentStringArray[i][0];
sortedData[ctr][1] = currentStringArray[i][1];
ctr++;
continue;
}
if(!currentStringArray[i-1][0].equals(currentStringArray[i][0])){
sortedData[ctr][0] = currentStringArray[i][0];
sortedData[ctr][1] = currentStringArray[i][1];
ctr++;
}
}
return sortedData;
}
/**
* This subfunction takes the provided plot and changes the fonts of the axis to a standardized new font
* @param plot the XYPlot containing the graph on which the fonts will be changed
* @return the original plot with the modifications to the axis fonts
*/
private XYPlot setAxisFonts(XYPlot plot){
//Set Y axis fonts
LogarithmicAxis yAxis = (LogarithmicAxis) plot.getRangeAxis();
yAxis.setLabelFont(masterFont);
yAxis.setTickLabelFont(masterFont);
//Set X axis fonts
ValueAxis xAxis = (ValueAxis) plot.getDomainAxis();
xAxis.setLabelFont(masterFont);
xAxis.setTickLabelFont(masterFont);
return plot;
}
/**
* @param parameterCode USGS parameter code for a specific water quality test.
* @return a string with the type of units for the current test.
*/
public String USGSwqUnits(String parameterCode) throws IOException{
URL webpage = new URL("http://nwis.waterdata.usgs.gov/usa/nwis/pmcodes?radio_pm_search=param_group&pm_group=All+--+include+all+parameter+groups&pm_search=&casrn_search=&srsname_search=&format=rdb&show=parameter_group_nm&show=parameter_nm&show=casrn&show=srsname&show=parameter_units");
URLConnection yc = webpage.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(yc.getInputStream()));
String inputLine;
int line_length = 0;
String units = "0";
//Find the units of the specified test
while ((inputLine = in.readLine()) != null) {
String[] f = inputLine.split("\t");
line_length = f.length;
if((line_length >= 6) && (f[0].length() == 5)){
if(f[0].equals(parameterCode)){
units = f[5];
}
}
}
return units;
}
/**
* @param units the units of the current USGS water quality test.
* @return a double with the correct conversion factor for the units.
*/
public double USGSwqConversion(String units){
double conversion = 0;
if(units.equalsIgnoreCase("#/l")){
conversion = (1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("#/m3")){
conversion = (java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("#/ml")){
conversion = (1000)*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("MPN/100 ml")){
conversion = (100)*(1000)*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("MPN/100L")){
conversion = (100)*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("cfu/100ml")){
conversion = (100)*(1000)*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("cfu/mL")){
conversion = (1000)*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("col/mL")){
conversion = (1000)*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("cysts/100L")){
conversion = (100)*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("cysts/10L")){
conversion = (10)*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("g/cm3") || units.equalsIgnoreCase("g/mL @ 20C")){
conversion = (java.lang.Math.pow(10,-6))*(1/1)*(1000)*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("g/m3")){
conversion = (java.lang.Math.pow(10,-6))*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("mg/l") || units.equalsIgnoreCase("mg/l CaCO3") || units.equalsIgnoreCase("mg/l NH4") ||
units.equalsIgnoreCase("mg/l NO3") || units.equalsIgnoreCase("mg/l PO4") || units.equalsIgnoreCase("mg/l SiO2") ||
units.equalsIgnoreCase("mg/l as H") || units.equalsIgnoreCase("mg/l as N") || units.equalsIgnoreCase("mg/l as Na") ||
units.equalsIgnoreCase("mg/l as P") || units.equalsIgnoreCase("mg/l as S") || units.equalsIgnoreCase("mgC3H6O2/L")){
conversion = (java.lang.Math.pow(10,-6))*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("mg/mL @25C")){
conversion = (java.lang.Math.pow(10,-6))*(1000)*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("ml/l")){
conversion = (1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("ng/l") || units.equalsIgnoreCase("pg/mL")){
conversion = (java.lang.Math.pow(10,-12))*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("ng/m3") || units.equalsIgnoreCase("pg/l")){
conversion = (java.lang.Math.pow(10,-12))*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("ocyst/100L")){
conversion = (100)*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("oocyst/10L")){
conversion = (10)*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("pfu/100L")){
conversion = (100)*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("pfu/100ml")){
conversion = (100)*(1000)*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("pg/m3")){
conversion = (java.lang.Math.pow(10,-15))*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("ug/L 2,4-D") || units.equalsIgnoreCase("ug/L U3O8") || units.equalsIgnoreCase("ug/L as As") ||
units.equalsIgnoreCase("ug/L as Cl") || units.equalsIgnoreCase("ug/L as N") || units.equalsIgnoreCase("ug/L as P") ||
units.equalsIgnoreCase("ug/l") || units.equalsIgnoreCase("ugAtrazn/L")){
conversion = (java.lang.Math.pow(10,-9))*(1000)*(java.lang.Math.pow(0.3048,3))*(86400);
}else if(units.equalsIgnoreCase("ug/m3")){
conversion = (java.lang.Math.pow(10,-9))*(java.lang.Math.pow(0.3048,3))*(86400);
}
return conversion;
}
/**
* @param units the units of the current USGS water quality test.
* @return a string with the end result units of the conversion.
*/
public String USGSwqEndUnits(String units){
String endUnits = "No Units";
if(units.equalsIgnoreCase("#/l") || units.equalsIgnoreCase("#/m3") || units.equalsIgnoreCase("#/ml")){
endUnits = "#/day";
}else if(units.equalsIgnoreCase("MPN/100 ml") || units.equalsIgnoreCase("MPN/100L")){
endUnits = "MPN/day";
}else if(units.equalsIgnoreCase("cfu/100ml") || units.equalsIgnoreCase("cfu/mL")){
endUnits = "cfu/day";
}else if(units.equalsIgnoreCase("col/mL")){
endUnits = "col/day";
}else if(units.equalsIgnoreCase("cysts/100L") || units.equalsIgnoreCase("cysts/10L")){
endUnits = "cysts/day";//= cysts/100L*cfs
}else if(units.equalsIgnoreCase("mg/l") || units.equalsIgnoreCase("mg/l CaCO3") || units.equalsIgnoreCase("mg/l NH4") ||
units.equalsIgnoreCase("mg/l NO3") || units.equalsIgnoreCase("mg/l PO4") || units.equalsIgnoreCase("mg/l SiO2") ||
units.equalsIgnoreCase("mg/l as H") || units.equalsIgnoreCase("mg/l as N") || units.equalsIgnoreCase("mg/l as Na") ||
units.equalsIgnoreCase("mg/l as P") || units.equalsIgnoreCase("mg/l as S") || units.equalsIgnoreCase("mgC3H6O2/L") ||
units.equalsIgnoreCase("g/cm3") || units.equalsIgnoreCase("g/mL @ 20C") || units.equalsIgnoreCase("g/m3") ||
units.equalsIgnoreCase("mg/mL @25C") || units.equalsIgnoreCase("ng/l") || units.equalsIgnoreCase("pg/mL") ||
units.equalsIgnoreCase("ng/m3") || units.equalsIgnoreCase("pg/l") || units.equalsIgnoreCase("pg/m3") ||
units.equalsIgnoreCase("ug/L 2,4-D") || units.equalsIgnoreCase("ug/L U3O8") || units.equalsIgnoreCase("ug/L as As") ||
units.equalsIgnoreCase("ug/L as Cl") || units.equalsIgnoreCase("ug/L as N") || units.equalsIgnoreCase("ug/L as P") ||
units.equalsIgnoreCase("ug/l") || units.equalsIgnoreCase("ugAtrazn/L") || units.equalsIgnoreCase("ug/m3")){
endUnits = "kg/day";//= mg/l*cfs
}else if(units.equalsIgnoreCase("ml/l")){
endUnits = "ml/day";//= mg/l*cfs
}else if(units.equalsIgnoreCase("pfu/100L") || units.equalsIgnoreCase("pfu/100ml")){
endUnits = "pfu/day";
}else if(units.equalsIgnoreCase("ocyst/100L")){
endUnits = "ocyst/day";
}else if(units.equalsIgnoreCase("oocyst/10L")){
endUnits = "oocyst/day";
}
return endUnits;
}
/**
* Writes out the non-zero results of the duration curve analysis which are the flow duration curve intervale and their corresponding flow/load values
* @param results double[][] array to be written as each line of the text file
* @param partialpath the partial folder path of the file to be written
* @param resultType the second column of data header label
* @throws IOException
*/
public void writeResults(double[][] results, String partialpath, String resultType) throws IOException{
//Remove zero results
ArrayList<String> finalResults = new ArrayList<String>();
for(int i=0; i<results.length; i++){
//Only keep non-zero data
if(Double.compare(results[i][0],0) != 0){
finalResults.add(results[i][0] + "\t" + results[i][1]);
}
}
String path = partialpath + File.separator + "duration_curve_results.txt";
FileWriter writer = new FileWriter(path, false);
PrintWriter printLine = new PrintWriter(writer);
//Add Headers to text file
printLine.printf("%s" + "%n", "Flow Duration Interval (Non-Excedence Probability based on Weibull Plotting Position Ranking)\t" + resultType);
//Output data to text file
for(int i=0; i < finalResults.size(); i++) {
printLine.printf("%s" + "%n", finalResults.get(i));
}
System.out.println("Text File located at:\t" + path);
printLine.close();
writer.close();
}
}