/*
* Copyright 1998-2009 University Corporation for Atmospheric Research/Unidata
*
* Portions of this software were developed by the Unidata Program at the
* University Corporation for Atmospheric Research.
*
* Access and use of this software shall impose the following obligations
* and understandings on the user. The user is granted the right, without
* any fee or cost, to use, copy, modify, alter, enhance and distribute
* this software, and any derivative works thereof, and its supporting
* documentation for any purpose whatsoever, provided that this entire
* notice appears in all copies of the software, derivative works and
* supporting documentation. Further, UCAR requests that the user credit
* UCAR/Unidata in any publications that result from the use of this
* software or in any product that includes this software. The names UCAR
* and/or Unidata, however, may not be used in any advertising or publicity
* to endorse or promote any products or commercial entity unless specific
* written permission is obtained from UCAR/Unidata. The user also
* understands that UCAR/Unidata is not obligated to provide the user with
* any support, consulting, training or assistance of any kind with regard
* to the use, operation and performance of this software nor to provide
* the user with any updates, revisions, new versions or "bug fixes."
*
* THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* class ServerMethods
*
* Utility methods for all the THREDDS server type of servlets
* Outputs either xml, html, ascii, dqc data or catalog files.
*
* By: Robb Kambic 08/12/2007
*
*/
package thredds.server.radarServer;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import thredds.catalog.query.Location;
import thredds.catalog.query.SelectStation;
import thredds.catalog.query.Station;
import ucar.unidata.geoloc.LatLonPointImpl;
import ucar.unidata.geoloc.LatLonRect;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ServerMethods {
public static final Pattern p_all_i = Pattern.compile("all", Pattern.CASE_INSENSITIVE);
public static final Pattern p_ascii_i = Pattern.compile("ascii", Pattern.CASE_INSENSITIVE);
public static final Pattern p_B_pound = Pattern.compile("^#");
public static final Pattern p_catalog_i = Pattern.compile("catalog", Pattern.CASE_INSENSITIVE);
public static final Pattern p_config = Pattern.compile("(\\w+)\\s*=\\s+?(.*)");
public static final Pattern p_dataset =
Pattern.compile("ID=\"(.*)\"\\s+path=\"(.*)\"\\s+dirLocation=\"(.*)\"\\s+filter");
public static final Pattern p_B_D8 = Pattern.compile("^\\d{8}");
public static final Pattern p_DODS_i = Pattern.compile("(DODS|OPENDAP)", Pattern.CASE_INSENSITIVE);
public static final Pattern p_HTTPServer_i = Pattern.compile("HTTPServer", Pattern.CASE_INSENSITIVE);
public static final Pattern p_html_i = Pattern.compile("html", Pattern.CASE_INSENSITIVE);
public static final Pattern p_isodate = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})");
public static final Pattern p_latitude_longitude = Pattern.compile("latitude=\"([-.0-9]*)\"\\s+longitude=\"([-.0-9]*)\"");
public static final Pattern p_name_value2 = Pattern.compile("name=\"([A-Za-z0-9 ()_,-:]*)\"\\s*");
public static final Pattern p_nexrad2 =
Pattern.compile("ID=\"(\\w+)/NEXRAD2\"\\s+path=\"(.*)\"\\s+dirLocation=\"(.*)\"\\s+filter");
public static final Pattern p_qc_or_dqc_i = Pattern.compile("(qc|dqc)", Pattern.CASE_INSENSITIVE);
public static final Pattern p_space20 = Pattern.compile("%20");
public static final Pattern p_spaces = Pattern.compile("\\s+");
public static final Pattern p_station_name = Pattern.compile("\\s*<station\\s+name=\"(.*)\" ");
public static final Pattern p_stn_i = Pattern.compile("stn", Pattern.CASE_INSENSITIVE);
public static final Pattern p_value2 = Pattern.compile("value=\"([A-Z0-9]*)\"");
public static final Pattern p_xml_i = Pattern.compile("xml", Pattern.CASE_INSENSITIVE);
public static final Pattern p_yyyymmdd = Pattern.compile("(\\d{8})");
public static final Pattern p_yymmdd_hhmm = Pattern.compile("(\\d{2})(\\d{4}_\\d{4})");
public static final Pattern p_yyyymmdd_hhmm = Pattern.compile("(\\d{8}_\\d{4})");
public static final String epic = "1970-01-01T00:00:00";
private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger( getClass() );
//private org.slf4j.Logger log;
protected SimpleDateFormat dateFormatISO;
protected SimpleDateFormat dateFormat;
public ServerMethods( org.slf4j.Logger log ) {
dateFormatISO = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatISO.setTimeZone(TimeZone.getTimeZone("GMT")); // same as UTC
dateFormat = new SimpleDateFormat("yyyyMMdd");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); // same as UTC
}
/*
* gets files in a directory that are directory themselves
* returns them in descending order
*/
/*
public ArrayList getDirData(String dirS, PrintWriter pw) {
File dir = new File(dirS);
if( ! dir.exists() )
return null;
File[] dirs = dir.listFiles();
ArrayList<File> onlyDirs = new ArrayList<File>();
// all entries must be directories
for (int i = 0; i < dirs.length; i++) {
if( dirs[ i ].isFile() )
continue;
onlyDirs.add(dirs[i]);
}
Collections.sort(onlyDirs, new CompareKeyDescend());
return onlyDirs;
}
*/
public String[] getDAYS(String dirS, PrintWriter pw) {
// Obtain the days available
// check for valid times
//$check = `date -u +"%Y%m%d"` ;
Date now = Calendar.getInstance().getTime();
String check = dateFormat.format(now);
File dir = new File(dirS);
if( ! dir.exists() )
return null;
String[] TMP = dir.list();
ArrayList<String> days = new ArrayList<String>();
for (int i = 0; i < TMP.length; i++) {
// check date starts with 8 numbers and not in the future
if ( p_B_D8.matcher(TMP[i]).find() &&
TMP[i].compareTo(check) <= 0) {
days.add(TMP[i]);
}
}
Collections.sort(days, new CompareKeyDescend());
String[] DAYS = new String[days.size()];
DAYS = (String[]) days.toArray(DAYS);
return DAYS;
}
///////////////////////////////////////
// station handling
// What happened here!!!!
//public boolean intersect(DateRange dr, Date start, Date end ) throws IOException {
// return dr.intersect(start, end);
//}
public List<Station> getStations( String stnLocation ) {
List<Station> stationList = new ArrayList<Station>();
DocumentBuilder parser;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
factory.setNamespaceAware(true);
SelectStation parent = new SelectStation(); //kludge to use Station
InputStream is = null;
try {
parser = factory.newDocumentBuilder();
//stnLocation = stnLocation.replaceAll( " ", "%20");
is = new FileInputStream( stnLocation );
org.w3c.dom.Document doc = parser.parse(is);
//System.out.println( "root=" + doc.getDocumentElement().getTagName() );
NodeList stns = doc.getElementsByTagName("station");
for (int i = 0; i < stns.getLength(); i++) {
//System.out.println( "node=" + d.item( i ).getNodeName() );
NamedNodeMap attr = stns.item(i).getAttributes();
String name = "", value = "", state = "", country = "" ;
for (int j = 0; j < attr.getLength(); j++) {
if (attr.item(j).getNodeName().equals("value")) {
value = attr.item(j).getNodeValue();
} else if (attr.item(j).getNodeName().equals("name")) {
name = attr.item(j).getNodeValue();
} else if (attr.item(j).getNodeName().equals("state")) {
state = attr.item(j).getNodeValue();
} else if (attr.item(j).getNodeName().equals("country")) {
country = attr.item(j).getNodeValue();
}
}
NodeList child = stns.item(i).getChildNodes(); //Children of station
Location location = null;
for (int j = 0; j < child.getLength(); j++) {
//System.out.println( "child =" + child.item( j ).getNodeName() );
if ( child.item(j).getNodeName().equals("location3D")) {
NamedNodeMap ca = child.item(j).getAttributes();
String latitude = "", longitude = "", elevation = "" ;
for (int k = 0; k < ca.getLength(); k++) {
if (ca.item(k).getNodeName().equals("latitude")) {
latitude = ca.item(k).getNodeValue();
} else if (ca.item(k).getNodeName().equals("longitude")) {
longitude = ca.item(k).getNodeValue();
} else if (ca.item(k).getNodeName().equals("elevation")) {
elevation = ca.item(k).getNodeValue();
}
}
location = new Location(latitude, longitude, elevation, null, null, null );
}
}
Station station = new Station( parent, name, value, state, country, null );
station.setLocation( location );
stationList.add( station );
}
} catch (SAXException e) {
e.printStackTrace();
stationList = null;
} catch (IOException e) {
e.printStackTrace();
stationList = null;
} catch (ParserConfigurationException e) {
e.printStackTrace();
stationList = null;
}
finally {
if (is != null) {
try {
is.close();
}
catch (IOException e) {
log.error("radarServer getStations(): error closing" + stnLocation);
}
}
}
return stationList;
}
public HashMap<String, Station> getStationMap( List<Station> list ) {
HashMap<String, Station> stationMap = new HashMap<String, Station>();
for (Station station : list) {
stationMap.put(station.getValue(), station);
}
return stationMap;
}
public List<String> getStationNames( List<Station> stations ) {
ArrayList<String> result = new ArrayList<String>();
for (Station s : stations) {
result.add(s.getValue());
}
return result;
}
public List<String> convert4to3stations( List<String> stations ) {
ArrayList<String> result = new ArrayList<String>();
for (String s : stations) {
result.add(s.substring( 1 ));
}
return result;
}
/**
* Get the list of station names that are contained within the bounding box.
*
* @param boundingBox lat/lon bounding box
* @return list of station names contained within the bounding box
* @throws IOException if read error
*/
public List<String> getStationNames(LatLonRect boundingBox, List<Station> stations ) {
LatLonPointImpl latlonPt = new LatLonPointImpl();
ArrayList<String> result = new ArrayList<String>();
for (Station s : stations) {
latlonPt.set(s.getLocation().getLatitude(), s.getLocation().getLongitude());
if (boundingBox.contains(latlonPt)) {
result.add(s.getValue());
}
}
return result;
}
/**
* Find the station closest to the specified point.
* The metric is (lat-lat0)**2 + (cos(lat0)*(lon-lon0))**2
*
* @param lat latitude value
* @param lon longitude value
* @return name of station closest to the specified point
* @throws IOException if read error
*/
public String findClosestStation(double lat, double lon, List<Station> stations) {
double cos = Math.cos(Math.toRadians(lat));
Station min_station = stations.get(0);
double min_dist = Double.MAX_VALUE;
for (Station s : stations) {
double lat1 = s.getLocation().getLatitude();
double lon1 = LatLonPointImpl.lonNormal(s.getLocation().getLongitude(), lon);
double dy = Math.toRadians(lat - lat1);
double dx = cos * Math.toRadians(lon - lon1);
double dist = dy * dy + dx * dx;
if (dist < min_dist) {
min_dist = dist;
min_station = s;
}
}
return min_station.getValue();
}
/**
* Get an input stream reader for the filename
*
* @param filename name of file
* @return corresponding input stream reader
* @throws FileNotFoundException couldn't find the file
* @throws IOException problem opening stream
*/
public static BufferedReader getInputStreamReader(String filename)
throws FileNotFoundException, IOException {
return new BufferedReader(new InputStreamReader(getInputStream(filename)));
}
/**
* Get an input stream for the filename
*
* @param filename name of file
* @return corresponding input stream
* @throws FileNotFoundException couldn't find the file
* @throws IOException problem opening stream
*/
public static InputStream getInputStream(String filename)
throws IOException {
return getInputStream(filename, null);
}
/**
* Get an input stream for the filename
*
* @param filename name of file
* @param origin relative origin point for file location
* @return corresponding input stream
* @throws FileNotFoundException couldn't find the file
* @throws IOException problem opening stream
*/
public static InputStream getInputStream(String filename, Class origin)
throws IOException {
InputStream s = null;
while (origin != null) {
s = origin.getResourceAsStream(filename);
if (s != null) {
break;
}
origin = origin.getSuperclass();
}
//Try an absolute resource path
if (s == null) {
s = ServerMethods.class.getResourceAsStream(filename);
}
//Try the file system
if (s == null) {
File f = new File(filename);
if (f.exists()) {
try {
s = new FileInputStream(f);
//System.out.println( "opened file " + filename );
} catch (Exception e) {
}
}
}
//Try it as a url
if (s == null) {
try {
//Pattern p_space20 = Pattern.compile( "%20" );
String encodedUrl = p_space20.matcher(filename).replaceAll(" ");
URL dataUrl = new URL(encodedUrl);
URLConnection connection = dataUrl.openConnection();
s = connection.getInputStream();
} catch (Exception exc) {
}
}
if (s == null) {
throw new FileNotFoundException("Unable to open:" + filename);
}
return s;
} // end getInputStream
protected static class CompareKeyDescend implements Comparator<String> {
public int compare(String s1, String s2) {
return s2.compareTo(s1);
}
}
// returns if day is between dayStart and dayEnd
public boolean isValidDay( String dateDir, String yyyymmddStart, String yyyymmddEnd )
{
if( dateDir.compareTo( yyyymmddStart ) >= 0 &&
dateDir.compareTo( yyyymmddEnd ) <= 0 )
return true;
return false;
}
// returns true if date is between dateStart and dateEnd
public boolean isValidDate( String dateReport, String dateStart, String dateEnd ) {
Matcher m;
m = p_yyyymmdd_hhmm.matcher( dateReport );
String date;
if( m.find() ) {
date = m.group( 1 );
} else { // try date w/o century
m = p_yymmdd_hhmm.matcher( dateReport );
if( m.find() ) { // add century, fails 2070
if( Integer.parseInt(m.group( 1 )) > 69 ) {
date = "19" + m.group( 1 ) + m.group( 2 );
} else {
date = "20" + m.group( 1 ) + m.group( 2 );
}
} else {
return false;
}
}
// extract hhmm from product
if( date.compareTo( dateStart ) >= 0 &&
date.compareTo( dateEnd ) <= 0 )
return true;
return false;
}
// returns hhmm of datetime string
public String hhmm( String dateTime ) {
return dateTime.substring( 11, 13 ) + dateTime.substring( 14, 16 );
}
// returns ISO time extracted from a product
public String getObTimeISO( String product ) {
Matcher m;
m = p_yyyymmdd_hhmm.matcher( product );
String date;
if( m.find() ) {
date = m.group( 1 );
} else { // try date w/o century
m = p_yymmdd_hhmm.matcher( product );
if( m.find() ) { // add century, fails 2070
if( Integer.parseInt(m.group( 1 )) > 69 ) {
date = "19" + m.group( 1 ) + m.group( 2 );
} else {
date = "20" + m.group( 1 ) + m.group( 2 );
}
} else {
return epic;
}
}
return date.substring(0,4) +"-"+ date.substring(4,6)
+"-"+ date.substring(6,8) +"T"+ date.substring(9, 11)
+":"+ date.substring(11,13) +":00";
}
} // end ServerMethods