WaterLocations_CDWR.java [src/WaterLocations] Revision: default Date:
package WaterLocations;
import java.io.IOException;
import java.util.ArrayList;
//import java.util.regex.Matcher;
//import java.util.regex.Pattern;
import utils.WebPageUtils;
/**
* Last Updated: 27-June-2019
* @author Tyler Wible
* @since 3-October-2018
* For more information on all of CDWR's CDSS data services, go to:
* https://dwr.state.co.us/Rest/GET/Help
*/
public class WaterLocations_CDWR implements WaterLocationsInterface {
public String database = "CDWR";
@Override
public ArrayList<String> downloadStations(String minLat, String maxLat, String minLong, String maxLong, boolean keepHistoricTF) throws WaterLocationsException {
//NOTE: CDWR uses a lat/long plus a radius (miles) search for stations by location. So we need to convert the bounding box into this.
//Calculate center of bounding box for CDWR's search
double minLatDouble = Double.parseDouble(minLat);
double minLongDouble = Double.parseDouble(minLong);
double maxLatDouble = Double.parseDouble(maxLat);
double maxLongDouble = Double.parseDouble(maxLong);
double averageLatitude = (minLatDouble + maxLatDouble) / 2.0;
double averageLongitude = (minLongDouble + maxLongDouble) / 2.0;
//Calculate distance between center (aveLat, aveLong) and a corner (minLat, minLong) of bounding box for CDWR's search
double radiusMiles = calculateDistanceBetweenLocations(averageLatitude, minLatDouble, averageLongitude, minLongDouble);
int radiusMilesRound = (int) Math.ceil(radiusMiles);
//Specify station website from inputs
//https://dwr.state.co.us/rest/GET/api/v2/telemetrystations/telemetrystation/?format=csv&latitude=40.4500296208208&longitude=-105.204340919673&radius=15&units=miles
String stationUrl = "https://dwr.state.co.us/rest/GET/api/v2/telemetrystations/telemetrystation/?format=csv" +
"&latitude=" + String.valueOf(averageLatitude) +
"&longitude=" + String.valueOf(averageLongitude) +
"&radius=" + String.valueOf(radiusMilesRound) +
"&units=miles";
if(keepHistoricTF){
stationUrl += "&includeHistoric=true";
}
//Fetch the monitoring stations webpage for current bounding box
ArrayList<String> webpageAll = new ArrayList<>();
try {
webpageAll = WebPageUtils.downloadWebpage(stationUrl);
} catch (IOException ex) {
throw new WaterLocationsException("The was an issue extracting " + database + " station locations from the specified URl: " + stationUrl + ". " + ex.getMessage());
}
return webpageAll;
}
@Override
public ArrayList<Station> getStations(String minLat, String maxLat, String minLong, String maxLong, boolean keepHistoricTF) throws WaterLocationsException {
//Fetch monitoring stations web page for current bounding box
ArrayList<String> webpageAll = downloadStations(minLat, maxLat, minLong, maxLong, keepHistoricTF);
ArrayList<Station> stations = new ArrayList();
for(int i=3; i<webpageAll.size(); i++){//Skip header line
String[] columns = webpageAll.get(i).split("\\,");
// String[] temp = splitWithQuotes(webpageAll.get(i));
// String[] temp2 = splitWithQuotes(webpageAll.get(i), ",", false);
// String[] temp4 = splitWithQuotes(webpageAll.get(i), "\\,", false);
//Due to extra commas in the organization names, look for which column is "active/historic"
int index = 11;
for(int j=0; j<columns.length; j++){
if(columns[j].equalsIgnoreCase("active") || columns[j].equalsIgnoreCase("historic")){
index = j;
}
}
int extraNameColumns = index - 11;
//Keep out only the desired data
//columns[3] = Station Name
//columns[4] = Org. ID
//columns[5] = Org. Name
//columns[9] = Station ID (abbreviation)
//columns[11] = station status (active, historic)
//columns[12] = Station Type (Diversion Structure, Stream Gage, Storage Structure)
//columns[22] = Drainage Area
//columns[23] = HUC10
//columns[26] = Latitude
//columns[27] = Longitude
Station currentStation = new Station(database + "-" + columns[9]);
currentStation.SetStationName(columns[3]);
currentStation.SetOrgId(columns[4]);
currentStation.SetOrgName(columns[5]);
currentStation.SetStationId(columns[index - 2]);
currentStation.SetStationType(columns[index + 1]);
currentStation.SetDrainageArea(columns[index + 11]);
currentStation.SetHUC(columns[index + 12]);
currentStation.SetLatitude(columns[index + 15]);
currentStation.SetLongitude(columns[index + 16]);
boolean realTimeDataFlag = false;
if(columns[index].equalsIgnoreCase("active")){
realTimeDataFlag = true;
}
currentStation.SetRealTimeDataFlag(realTimeDataFlag);
if(extraNameColumns > 0){
String currentName = columns[5];
for(int j=1;j<=extraNameColumns; j++){
currentName += columns[5 + j];
}
currentStation.SetOrgName(currentName);
}
stations.add(currentStation);
}
return stations;
}
// private static String[] splitWithQuotes(String text){
// ArrayList<String> list = new ArrayList<>();
// Matcher m = Pattern.compile( "([^\"]\\S*|\".+?\")\\s*" ).matcher( text );
//
// while(m.find()){
// list.add( m.group( 1 ) ); // Add .replace("\"", "") to remove surrounding quotes.
// }
//
// return list.toArray( new String[0] );
// }
// /**
// *
// * @param text The text you want to split
// * @param delimiter The character or characters to split the text on
// * @param ignoreDuplicates <pre>Allows for multiple delimiters in a row acting as one delimiter.
// * This most often happens when " " (space) is the delimiter, used in human readable files.
// * Two spaces in a row is effectively the same as one space.
// *
// * In a csv file, on the other hand, multiple delimiters often mean a null value.
// * ignoreDuplicates would be true in the case of spaces, false in the case of commas.</pre>
// *
// * @return
// */
// public static String[] splitWithQuotes( String text, String delimiter, boolean ignoreDuplicates )
// {
// if(delimiter.equalsIgnoreCase( " " )){
// delimiter = "\\s";
// }
// ArrayList<String> list = new ArrayList<>();
// String pattern = "([^\"]\\S*|\".+?\")" + delimiter;
// if(ignoreDuplicates){
// pattern += "*";
// }
//
// Matcher m = Pattern.compile( pattern ).matcher( text );
//
// while(m.find()){
// list.add( m.group( 1 ) );
// }
//
// return list.toArray( new String[0] );
// }
private double calculateDistanceBetweenLocations(double latitude1, double latitude2, double longitude1, double longitude2){
//This uses the ‘haversine’ formula to calculate the great-circle distance between two points – that is, the shortest distance over the earth’s surface – giving an ‘as-the-crow-flies’ distance between the points.
//
//Haversine formula: a = sin^2(deltapsi/2) + cos(psi1) * cos(psi2) * sin²(deltalambda/2)
//c = 2 * atan2( sqrt(a), sqrt(1−a) )
//d = R * c
//where psi is latitude, lambda is longitude, R is earth’s radius (mean radius = 6,371km);
//Note: angles need to be in radians to work with trig functions
double R = 6371 * Math.pow(10, 3);//earth's radius in metres
double psi1 = degreesToRadians(latitude1);
double psi2 = degreesToRadians(latitude2);
double deltaPsi = degreesToRadians(latitude2-latitude1);
double deltaLambda = degreesToRadians(longitude2-longitude1);
double a = Math.sin(deltaPsi/2) * Math.sin(deltaPsi/2) + Math.cos(psi1) * Math.cos(psi2) * Math.sin(deltaLambda/2) * Math.sin(deltaLambda/2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
double distanceMeters = R * c;
double distanceMiles = distanceMeters * (0.000621371);
return distanceMiles;
}
private double degreesToRadians(double degrees){
double radians = degrees * Math.PI / 180.0;
return radians;
}
}