/* * 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. */ package thredds.server.radarServer; import org.jdom2.Document; import org.jdom2.Element; import thredds.catalog.query.Station; import thredds.servlet.ServletUtil; import ucar.nc2.units.DateRange; import ucar.nc2.units.DateType; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; /** * By: Robb Kambic * Date: Nov 29, 2008 * Time: 2:29:36 PM */ public class RadarMethods { static public final String nexradStations = "RadarNexradStations.xml"; static public final String terminalStations = "RadarTerminalStations.xml"; static public List<Station> nexradList = new ArrayList<Station>(); static public List<Station> terminalList = new ArrayList<Station>(); static public HashMap<String, Station> nexradMap; static public HashMap<String, Station> terminalMap; static public final ArrayList<String> nexradVars = new ArrayList<String>(); static public final ArrayList<String> terminalVars = new ArrayList<String>(); static private final String serviceName = "OPENDAP"; static private final String serviceType = "OPENDAP"; private ServerMethods sm; private boolean debug = false; private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger( getClass() ); //private org.slf4j.Logger log; public RadarMethods( ) {} public RadarMethods( String contentPath, org.slf4j.Logger log ) { //this.log = log; sm = new ServerMethods( log ); if( nexradList.size() == 0 ) { nexradList = sm.getStations( contentPath + getPath() + nexradStations ); terminalList = sm.getStations( contentPath + getPath() + terminalStations ); if( nexradList == null || terminalList == null) { log.error( "Station initialization problem using "+ contentPath + getPath() + nexradStations +" "+ contentPath + getPath() + terminalStations ); return; } nexradMap = sm.getStationMap( nexradList ); terminalMap = sm.getStationMap( terminalList ); nexradVars.add( "N0R"); nexradVars.add( "N1R"); nexradVars.add( "N2R"); nexradVars.add( "N3R"); nexradVars.add( "DPA"); nexradVars.add( "DHR"); nexradVars.add( "DSP"); nexradVars.add( "N1P"); nexradVars.add( "N0S"); nexradVars.add( "N1S"); nexradVars.add( "N2S"); nexradVars.add( "N3S"); nexradVars.add( "N0V"); nexradVars.add( "N1V"); nexradVars.add( "N0Z"); nexradVars.add( "NCR"); nexradVars.add( "NET"); nexradVars.add( "NMD"); nexradVars.add( "NTP"); nexradVars.add( "NVL"); nexradVars.add( "NVW"); nexradVars.add( "N0Q"); // new vars starting on Feb 8, 2010 nexradVars.add( "NAQ"); nexradVars.add( "N1Q"); nexradVars.add( "NBQ"); nexradVars.add( "N2Q"); nexradVars.add( "N3Q"); nexradVars.add( "N0U"); nexradVars.add( "NAU"); nexradVars.add( "N1U"); nexradVars.add( "NBU"); nexradVars.add( "N2U"); nexradVars.add( "N3U"); nexradVars.add( "DVL"); nexradVars.add( "EET"); nexradVars.add( "N0X"); // new vars starting on Nov 17, 2010 nexradVars.add( "NAX"); nexradVars.add( "N1X"); nexradVars.add( "NBX"); nexradVars.add( "N2X"); nexradVars.add( "N3X"); nexradVars.add( "N0C"); nexradVars.add( "NAC"); nexradVars.add( "N1C"); nexradVars.add( "NBC"); nexradVars.add( "N2C"); nexradVars.add( "N3C"); nexradVars.add( "N0K"); nexradVars.add( "NAK"); nexradVars.add( "N1K"); nexradVars.add( "NBK"); nexradVars.add( "N2K"); nexradVars.add( "N3K"); nexradVars.add( "N0H"); nexradVars.add( "NAH"); nexradVars.add( "N1H"); nexradVars.add( "NBH"); nexradVars.add( "N2H"); nexradVars.add( "N3H"); nexradVars.add( "N0M"); nexradVars.add( "NAM"); nexradVars.add( "N1M"); nexradVars.add( "NBM"); nexradVars.add( "N2M"); nexradVars.add( "N3M"); nexradVars.add( "DPR"); nexradVars.add( "HHC"); nexradVars.add( "OHA"); nexradVars.add( "DAA"); nexradVars.add( "PTA"); nexradVars.add( "DTA"); nexradVars.add( "DU3"); nexradVars.add( "DU6"); nexradVars.add( "DOD"); nexradVars.add( "DSD"); nexradVars.add( "BREF1");// old vars naming conventions nexradVars.add( "BREF2"); nexradVars.add( "BREF248"); nexradVars.add( "BREF3"); nexradVars.add( "BREF4"); nexradVars.add( "LREF1"); nexradVars.add( "LREF2"); nexradVars.add( "LREF3"); nexradVars.add( "CREF"); nexradVars.add( "BVEL1"); nexradVars.add( "BVEL2"); nexradVars.add( "VEL1"); nexradVars.add( "VEL2"); nexradVars.add( "VEL3"); nexradVars.add( "VEL4"); nexradVars.add( "LREF1"); nexradVars.add( "PRECIP1"); nexradVars.add( "PRECIPTOT"); nexradVars.add( "SRMV1"); nexradVars.add( "SRMV2"); nexradVars.add( "SRVEL1"); nexradVars.add( "SRVEL2"); nexradVars.add( "SRVEL3"); nexradVars.add( "SRVEL4"); nexradVars.add( "TOPS"); nexradVars.add( "VIL"); nexradVars.add( "PRE1"); nexradVars.add( "PRET"); nexradVars.add( "PREA"); nexradVars.add( "VAD"); // add terminal vars terminalVars.add( "TR0"); terminalVars.add( "TR1"); terminalVars.add( "TR2"); terminalVars.add( "TV0"); terminalVars.add( "TV1"); terminalVars.add( "TV2"); terminalVars.add( "TZL"); terminalVars.add( "DHR"); terminalVars.add( "NCR"); terminalVars.add( "NET"); terminalVars.add( "NVW"); terminalVars.add( "NVL"); terminalVars.add( "NST"); terminalVars.add( "NHI"); terminalVars.add( "NTV"); terminalVars.add( "FTM"); terminalVars.add( "N1P"); terminalVars.add( "NTP"); terminalVars.add( "DPA"); terminalVars.add( "SPD"); terminalVars.add( "DSP"); terminalVars.add( "NMD"); terminalVars.add( "RSL"); terminalVars.add( "GSM"); } } public Document stationsXML( RadarServer.RadarType radarType, Document doc, Element rootElem, String path ) throws Exception { // stations in this dataset, set by path String[] stations = stationsDS( radarType, RadarServer.dataLocation.get(path )); if( path.contains( "level3") && stations[ 0 ].length() == 4 ) { for( int i = 0; i < stations.length; i++ ) stations[ i ] = stations[ i ].substring( 1 ); } doc = makeStationDocument( doc, rootElem, stations, radarType ); return doc; } // must end with "/" protected String getPath() { return "servers/"; } // get/check/process query from servlet call public void radarQuery(RadarServer.RadarType radarType, HttpServletRequest req, HttpServletResponse res, PrintWriter pw ) throws ServletException, IOException { String radarDir = null; try { // long startms = System.currentTimeMillis(); // long endms; // need to extract data according to the (dataset) given String pathInfo = req.getPathInfo(); if (pathInfo == null) pathInfo = ""; if( pathInfo.startsWith( "/")) pathInfo = pathInfo.substring( 1 ); Boolean level2 = pathInfo.contains( "level2"); radarDir = RadarServer.dataLocation.get( pathInfo ); if ( radarDir == null) radarDir = RadarServer.dataLocation.get( "nexrad/level2/IDD" ); // default // parse the input QueryParams qp = new QueryParams(); if( ! qp.parseQuery(req, res, new String[]{ QueryParams.XML, QueryParams.HTML, QueryParams.RAW, QueryParams.NETCDF})) return; // has sent the error message // endms = System.currentTimeMillis(); // System.out.println( "after QueryParams "+ (endms - startms)); // startms = System.currentTimeMillis(); // check Query Params if( ! checkQueryParms( radarType, qp, level2 ) ) { //qp.writeErr(req, res, qp.errs.toString(), HttpServletResponse.SC_BAD_REQUEST); log.error( "checkQueryParms Failed "+ req.getQueryString() ); throw new Exception( "checkQueryParms Failed "+ req.getQueryString() ); } // endms = System.currentTimeMillis(); // System.out.println( "after checkQueryParms "+ (endms - startms)); // startms = System.currentTimeMillis(); // check if all data needs to be return, ie not time information given // boolean allTimes = ! ( qp.hasTimePoint || qp.hasDateRange ); // what type of output wanted XML html String serviceBase; qp.acceptType = qp.acceptType.replaceFirst( ".*/", "" ); if( ServerMethods.p_html_i.matcher(qp.acceptType).find()) { // accept html res.setContentType( qp.acceptType ); serviceBase = pathInfo +"/"; } else { serviceBase = "/thredds/dodsC/"+ pathInfo +"/"; } // writes first part of catalog if( ! writeHeader( radarType, qp, pathInfo, pw) ) { //qp.writeErr(req, res, qp.errs.toString(), HttpServletResponse.SC_BAD_REQUEST); log.error( "Write Header Failed "+ req.getQueryString() ); throw new Exception( "Write Header Failed "+ req.getQueryString() ); } // endms = System.currentTimeMillis(); // System.out.println( "after writeHeader "+ (endms - startms)); // startms = System.currentTimeMillis(); // gets products according to stations, time, and variables boolean dataFound = processQuery( radarDir, qp, pw, serviceBase, radarType ); // endms = System.currentTimeMillis(); // System.out.println( "after processQuery "+ (endms - startms)); // startms = System.currentTimeMillis(); // add ending tags if ( ServerMethods.p_xml_i.matcher(qp.acceptType).find() ) { if (! dataFound ) { pw.println(" <documentation>No data available for station(s) "+ "and time range</documentation>"); } pw.println(" </dataset>"); pw.println("</catalog>"); } else if ( ServerMethods.p_html_i.matcher(qp.acceptType).find()) { pw.println(" </table>"); if (! dataFound ) pw.println("<p>No data available for station(s) and time range "+ req.getQueryString() +"</p>"); pw.println("</html>"); } // endms = System.currentTimeMillis(); // System.out.println( "after radarQuery "+ (endms - startms)); // startms = System.currentTimeMillis(); } catch (Throwable t) { log.error("Query error "+ req.getQueryString()); ServletUtil.handleException(t, res); } } // end radarNexradQuery // check that parms have valid stations, vars or times private Boolean checkQueryParms(RadarServer.RadarType radarType, QueryParams qp, Boolean level2 ) throws IOException { try { if (qp.hasBB) { if( radarType.equals( RadarServer.RadarType.nexrad ) ) qp.stns = sm.getStationNames(qp.getBB(), nexradList ); else qp.stns = sm.getStationNames(qp.getBB(), terminalList ); if( ! level2 ) qp.stns = sm.convert4to3stations( qp.stns ); if (qp.stns.size() == 0) { //qp.errs.append("<documentation>ERROR: Bounding Box contains no stations</documentation>\n"); //qp.writeErr(res, qp.errs.toString(), HttpServletResponse.SC_BAD_REQUEST); log.error( "Bounding Box contains no stations " ); throw new Exception( "Bounding Box contains no stations " ); } } if (qp.hasStns ) { if( isStationListEmpty(qp.stns, radarType )) { //qp.errs.append("<documentation>ERROR: No valid stations specified</documentation>\n"); //qp.writeErr(res, qp.errs.toString(), HttpServletResponse.SC_BAD_REQUEST); log.error( "No valid stations specified 1" ); throw new Exception( "No valid stations specified" ); } } if (qp.hasLatlonPoint) { qp.stns = new ArrayList<String>(); if( radarType.equals( RadarServer.RadarType.nexrad ) ) qp.stns.add( sm.findClosestStation(qp.lat, qp.lon, nexradList )); else qp.stns.add( sm.findClosestStation(qp.lat, qp.lon, terminalList )); if( ! level2 ) qp.stns = sm.convert4to3stations( qp.stns ); } else if (qp.fatal) { //qp.errs.append("<documentation>ERROR: No valid stations specified</documentation>\n"); //qp.writeErr(res, qp.errs.toString(), HttpServletResponse.SC_BAD_REQUEST); log.error( "No valid stations specified 2" ); throw new Exception( "No valid stations specified" ); } // qp.stns could be null, ouch boolean useAllStations = ( qp.stns.get( 0 ).toUpperCase().equals( "ALL")); if (useAllStations) { if( radarType.equals( RadarServer.RadarType.nexrad ) ) qp.stns = sm.getStationNames( nexradList ); //need station names else qp.stns = sm.getStationNames( terminalList ); //need station names if( ! level2 ) qp.stns = sm.convert4to3stations( qp.stns ); } /* if (qp.hasTimePoint && ( sm.filterDataset(qp.time) == null)) { //qp.errs.append("<documentation>ERROR: This dataset does not contain the time point= " + qp.time + " </documentation>\n"); //qp.writeErr(res, qp.errs.toString(), HttpServletResponse.SC_BAD_REQUEST); log.error( "No valid stations specified" ); throw new Exception( "No valid stations specified" ); return; } */ /* // needs work start and end aren't set, too expensive to set if (qp.hasDateRange) { DateRange dr = qp.getDateRange(); if (! sm.intersect(dr, start, end)) { //qp.errs.append("<documentation>ERROR: This dataset does not contain the time range= " + qp.time + " </documentation>\n"); //qp.writeErr(res, qp.errs.toString(), HttpServletResponse.SC_BAD_REQUEST); log.error( "No valid stations specified" ); throw new Exception( "No valid stations specified" ); return; } } */ /* if (useAllStations && useAllTimes) { qp.errs.append("<documentation>ERROR: You must subset by space or time</documentation>\n"); //qp.writeErr(res, qp.errs.toString(), HttpServletResponse.SC_BAD_REQUEST); log.error( "No valid stations specified" ); throw new Exception( "No valid stations specified" ); return; } */ // if time in not set, return latest if( qp.hasTimePoint ) { if( qp.time.isPresent() ) { try { qp.time_end = new DateType( "present", null, null); qp.time_start = new DateType(ServerMethods.epic, null, null); } catch (java.text.ParseException e) { //qp.errs.append("Illegal param= 'time' must be valid ISO Duration\n"); log.error("Illegal param= 'time' must be valid ISO Duration"); throw new Exception("Illegal param= 'time' must be valid ISO Duration"); } } else { qp.time_end = qp.time; qp.time_start = qp.time; } } else if( qp.hasDateRange ) { DateRange dr = qp.getCalendarDateRange().toDateRange(); qp.time_start = dr.getStart(); qp.time_end = dr.getEnd(); } else { //qp.hasTimePoint = true; try { qp.time = new DateType( "present", null, null); qp.time_end = new DateType( "present", null, null); qp.time_start = new DateType(ServerMethods.epic, null, null); } catch (java.text.ParseException e) { //qp.errs.append("Illegal param= 'time' must be valid ISO Duration\n"); log.error("Illegal param= 'time' must be valid ISO Duration"); throw new Exception("Illegal param= 'time' must be valid ISO Duration"); } } if( level2 ) { qp.vars = null; // level2 can't select vars } else { if( qp.vars != null ) { // remove desc from vars ArrayList<String> tmp = new ArrayList<String>(); for ( String var: qp.vars ) { tmp.add( var.replaceFirst( "/.*", "" ) ); } qp.vars = tmp; } } } catch ( Exception e ) { return false; } return true; } // write out catalog Header private Boolean writeHeader(RadarServer.RadarType radarType, QueryParams qp, String pathInfo, PrintWriter pw) throws IOException { try { // accept = XML inplies OPeNDAP server Boolean level2 = pathInfo.contains( "level2"); int level = (level2) ? 2 : 3; String serviceBase = ""; if ( ServerMethods.p_xml_i.matcher(qp.acceptType).find()) { serviceBase = "/thredds/dodsC/"+ pathInfo +"/"; pw.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); pw.print("<catalog xmlns=\"http://www.unidata.ucar.edu/namespaces/thredds/InvCatalog/v1.0\""); pw.println(" xmlns:xlink=\"http://www.w3.org/1999/xlink\" name=\"Radar Level"+ level +" datasets in near real time\" version=\""+ "1.0.1\">"); pw.println(""); pw.print(" <service name=\""+ serviceName +"\" serviceType=\""+ serviceType +"\""); pw.println(" base=\"" + serviceBase + "\"/>"); pw.print(" <dataset name=\"RadarLevel"+ level +" datasets for available stations and times\" collectionType=\"TimeSeries\" ID=\""+ "accept=" + qp.acceptType + "&"); if( ! level2 && qp.vars != null ) { // add vars pw.print("var="); for (int i = 0; i < qp.vars.size(); i++ ) { pw.print( qp.vars.get( i ) ); if( i < qp.vars.size() -1 ) pw.print( "," ); } pw.print("&"); } // use all stations if( qp.stns.get( 0 ).toUpperCase().equals( "ALL") ) { pw.print("stn=ALL&"); } else if (qp.hasStns ) { for (String station : qp.stns) { pw.print("stn=" + station +"&"); } } else if (qp.hasBB) { pw.print("south="+ qp.south +"&north="+ qp.north +"&" ); pw.print("west="+ qp.west +"&east="+ qp.east +"&" ); } if( qp.hasDateRange ) { if( qp.time_start.getDate() == null || qp.time_start.isBlank() || qp.time_end.getDate() == null || qp.time_end.isBlank() ) { pw.println("time_start=" + qp.time_start.toString() +"&time_end=" + qp.time_end.toString() +"\">"); pw.println( "<documentation>need ISO time</documentation>\n" ); pw.println(" </dataset>"); pw.println("</catalog>"); return false; } else { pw.println("time_start=" + qp.time_start.toDateTimeStringISO() +"&time_end=" + qp.time_end.toDateTimeStringISO() +"\">"); } } else if( qp.time.isPresent() ) { pw.println("time=present\">"); } else if( qp.hasTimePoint ) { if( qp.time.getDate() == null || qp.time.isBlank()) { pw.println("time=" + qp.time.toString() +"\">"); pw.println( "<documentation>need ISO time</documentation>\n" ); pw.println(" </dataset>"); pw.println("</catalog>"); return false; } else { pw.println("time=" + qp.time.toDateTimeStringISO() +"\">"); } } else { pw.println( "\">" ); } pw.println(" <metadata inherited=\"true\">"); pw.println(" <dataType>Radial</dataType>"); pw.print(" <dataFormat>" ); if( level2 ) { pw.print( "NEXRAD2" ); } else if( radarType.equals( RadarServer.RadarType.nexrad ) ){ pw.print( "NIDS" ); } else { pw.print( "TDWR" ); } pw.println( "</dataFormat>"); pw.println(" <serviceName>" + serviceName + "</serviceName>"); pw.println(" </metadata>"); pw.println(); } else if ( ServerMethods.p_html_i.matcher(qp.acceptType).find()) { // accept html pw.println("<Head><Title>THREDDS RadarNexrad Server</Title></Head>"); pw.println("<body>"); // link=\"red\" alink=\"red\" vlink=\"red\">"); pw.println("<center><H1>Nexrad Level"+ level +" Radar Results</H1></center>"); pw.println(" <table align=\"center\" border cellpadding=\"5\" width=\"90%\">"); pw.println(" <tr>"); pw.println(" <th scope=\"col\"><u>OPENDAP</u></th>"); pw.println(" <th scope=\"col\"><u>HTTPServer</u></th>"); pw.println(" </tr>"); serviceBase = pathInfo +"/"; } else if ( ServerMethods.p_ascii_i.matcher(qp.acceptType).find()) { pw.println( "<documentation>\n" ); pw.println( "Request not implemented: "+ pathInfo ); pw.println( "</documentation>\n" ); pw.println( "<documentation>need ISO time</documentation>\n" ); pw.println(" </dataset>"); pw.println("</catalog>"); return false; } // at this point must have stations if( isStationListEmpty(qp.stns, radarType )) { pw.println(" <documentation>No data available for station(s) "+ "and time range</documentation>"); pw.println(" </dataset>"); pw.println("</catalog>"); return false; } } catch (Exception e ) { return false; } return true; } // This routine is very complex because if figures out different paths to // actual products ie stn/var/time stn/time/var var/stn/time etc. // processQuery is limited by the stns, dates and vars in the query private Boolean processQuery( String tdir, QueryParams qp, PrintWriter pw, String serviceBase, RadarServer.RadarType radarType ) throws IOException { int numProds = 0; try { // could have null pointer exceptions on dirs & checks // set date info String yyyymmddStart = qp.time_start.toDateString(); yyyymmddStart = yyyymmddStart.replace( "-", ""); String yyyymmddEnd = qp.time_end.toDateString(); yyyymmddEnd = yyyymmddEnd.replace( "-", ""); String dateStart = yyyymmddStart +"_"+ sm.hhmm( qp.time_start.toDateTimeString() ); String dateEnd = yyyymmddEnd +"_"+ sm.hhmm( qp.time_end.toDateTimeString() ); // top dir has to point to stns, vars, or date dir File files = new File( tdir ); String[] tdirs = files.list(); if( tdirs == null ) return false; // need to check/eliminate . file names ArrayList<String> tmp = new ArrayList<String>(); for( String name : tdirs ) { if( name.startsWith( ".")) continue; tmp.add( name ); } // redo stations array for removal of . files if( tdirs.length != tmp.size() ) { tdirs = new String[tmp.size()]; tdirs = (String[]) tmp.toArray( tdirs ); } // decide if directory contains stns, dates or vars, only one true Boolean isStns = isStation(tdirs[ 0 ], radarType ); Boolean isDates = ServerMethods.p_yyyymmdd.matcher(tdirs[ 0 ]).find(); Boolean isVars = isVar( tdirs[ 0 ].toUpperCase(), radarType ); if( ! ( isStns || isDates || isVars ) ) { log.error("processQuery error, no valid stn, date, or var "+ qp.toString() ); pw.println( "<documentation>\n" ); pw.println( "Query can't be satisfied :<![CDATA["+ qp.toString() +"]]>\n" ); pw.println( "</documentation>\n" ); return numProds > 0; // invalid query } if( isStns ) { // limit stations to the ones in the query for (String station : qp.stns ) { String sDir = tdir +'/'+ station ; files = new File( sDir ); if( ! files.exists()) continue; String[] sdirs = files.list(); if( sdirs == null) continue; // need to check next dirs for products, dates or vars File file = new File( sDir +"/"+ sdirs[ 0 ] ); if( file.isFile() ) { // products in dir, process dir // TODO: check and delete //numProds += processProducts( sdirs, sDir.replaceFirst( tdir, "").substring( 1 ), numProds += processProducts( sdirs, sDir.substring( tdir.length() +1), dateStart, dateEnd, qp, pw, serviceBase ); } else if( ServerMethods.p_yyyymmdd.matcher(sdirs[ 0 ]).find() ) { //dates java.util.Arrays.sort( sdirs, new CompareKeyDescend() ); for( int j = 0; j < sdirs.length; j++) { if( sm.isValidDay( sdirs[ j ], yyyymmddStart, yyyymmddEnd ) ) { // valid date // check for products or vars String dDir = sDir +"/"+ sdirs[ j ]; files = new File( dDir ); String[] ndirs = files.list(); file = new File( dDir +"/"+ ndirs[ 0 ]); if( file.isFile() ) { // products in dir, process dir // TODO: check and delete //numProds += processProducts( ndirs, dDir.replaceFirst( tdir, "").substring( 1 ), numProds += processProducts( ndirs, dDir.substring( tdir.length() +1), dateStart, dateEnd, qp, pw, serviceBase ); if( qp.hasTimePoint ) // only want one product break; } else if( nexradVars.contains( ndirs[ 0 ].toUpperCase() ) ) { // not implemented, doesn't make sense stn/date/vars } } } } else if( nexradVars.contains( sdirs[ 0 ].toUpperCase() ) || terminalVars.contains( sdirs[ 0 ].toUpperCase() )) { // variable if( qp.vars == null ) return false; for (String variable : qp.vars ) { String vDir = sDir +'/'+ variable ; files = new File( vDir ); String[] vdirs = files.list(); // need to check next dirs for products, dates file = new File( vDir +"/"+ vdirs[ 0 ] ); if( file.isFile() ) { // products in dir, return dir //dirTree.add( vDir ); // TODO: check and delete // numProds += processProducts( vdirs, vDir.replaceFirst( tdir, "").substring( 1 ), numProds += processProducts( vdirs, vDir.substring( tdir.length() +1), dateStart, dateEnd, qp, pw, serviceBase ); } } } } } else if( isDates ) { // limit dates to yyyymmddStart and yyyymmddEnd from query // need to check next dirs for stns and vars } else if( isVars ) { if( qp.vars == null ) return false; // limit vars to ones in query for (String variable : qp.vars ) { String vDir = tdir +'/'+ variable ; files = new File( vDir ); String[] vdirs = files.list(); // need to check next dirs for products, dates or stations File file = new File( vDir +"/"+ vdirs[ 0 ] ); if( file.isFile() ) { // products in dir, process dir // TODO: check and delete //numProds += processProducts( vdirs, vDir.replaceFirst( tdir, "").substring( 1 ), numProds += processProducts( vdirs, vDir.substring( tdir.length() +1), dateStart, dateEnd, qp, pw, serviceBase ); // TODO: check and delete // nexradMap.get( "K"+ vdirs[ 0 ] ) != null } else if( isStation(vdirs[ 0 ], radarType )) { for (String station : qp.stns ) { String sDir = vDir +'/'+ station ; files = new File( sDir ); if( ! files.exists()) continue; String[] sdirs = files.list(); if( sdirs == null) continue; // need to check next dirs for products, dates file = new File( sDir +"/"+ sdirs[ 0 ] ); if( file.isFile() ) { // products in dir, return dir // TODO: check and delete //numProds += processProducts( sdirs, sDir.replaceFirst( tdir, "").substring( 1 ), numProds += processProducts( sdirs, sDir.substring( tdir.length() +1), dateStart, dateEnd, qp, pw, serviceBase ); } else if( ServerMethods.p_yyyymmdd.matcher(sdirs[ 0 ]).find() ) { //dates java.util.Arrays.sort( sdirs, new CompareKeyDescend() ); for( int k = 0; k < sdirs.length; k++) { if( sm.isValidDay( sdirs[ k ], yyyymmddStart, yyyymmddEnd ) ) { // valid date String dDir = sDir +"/"+ sdirs[ k ]; files = new File( dDir ); String[] ddirs = files.list(); // TODO: check and delete //numProds += processProducts( ddirs, dDir.replaceFirst( tdir, "").substring( 1 ), numProds += processProducts( ddirs, dDir.substring( tdir.length() +1), dateStart, dateEnd, qp, pw, serviceBase); if( qp.hasTimePoint ) // only want one product break; } } } } } } } else { return numProds > 0; // invalid query } return numProds > 0; } catch ( Exception e ) { log.error("radarServer processQuery error" ); pw.println( "<documentation>\n" ); pw.println( "Query can't be satisfied :<![CDATA["+ qp.toString() +"]]>\n" ); pw.println( "</documentation>\n" ); return numProds > 0; // partial or invalid query } } // check if product has valid time then creates a dataset for product private int processProducts( String[] products, String rPath, String dateStart, String dateEnd, QueryParams qp, PrintWriter pw, String serviceBase ) throws Exception { Boolean latest = qp.hasTimePoint; // Just return all products boolean allTimes = ! ( qp.hasTimePoint || qp.hasDateRange ); java.util.Arrays.sort( products, new CompareKeyDescend() ); int numProducts = 0; // write out products with latest first order if ( ServerMethods.p_xml_i.matcher(qp.acceptType).find() ) { for( int t = 0; t < products.length; t++ ) { if( products[ t ].startsWith( "." ) ) continue; if( ! allTimes ) { if( ! sm.isValidDate( products[ t ], dateStart, dateEnd ) ) continue; } numProducts++; XMLdataset( products[ t ], rPath, pw ); if ( latest ) { break; } } } else { for( int t = 0; t < products.length; t++ ) { if( products[ t ].startsWith( "." ) ) continue; if( ! allTimes ) { if( ! sm.isValidDate( products[ t ], dateStart, dateEnd ) ) continue; } numProducts++; HTMLdataset( products[ t ], rPath, pw, serviceBase ); if( latest ) { break; } } } return numProducts; } // create a XML dataset entry for a catalog public void XMLdataset(String product, String rPath, PrintWriter pw ) throws IOException { pw.println(" <dataset name=\""+ product +"\" ID=\""+ product.hashCode() +"\"" ); String pDate = sm.getObTimeISO( product ); pw.print(" urlPath=\""); pw.println( rPath +"/"+ product +"\">" ); pw.println( " <date type=\"start of ob\">"+ pDate +"</date>" ); pw.println( " </dataset>" ); } // end datasetOut // create a HTML dataset entry for a catalog public void HTMLdataset(String product, String rPath, PrintWriter pw, String serviceBase ) throws IOException { pw.println( " <tr>" ); pw.println(" <td align=center valign=center><a href=\"/thredds/dodsC/"+ serviceBase + rPath +"/"+ product +".html\">"+ product +"</a></td>" ); pw.println(" <td align=center valign=center><a href=\"/thredds/fileServer/"+ serviceBase + rPath +"/"+ product +"\">"+ product +"</a></td>" ); pw.println( " </tr>" ); } // end HTMLdataset /** * Create an XML station document * @param doc * @param rootElem * @param stations * @return Document */ public Document makeStationDocument( Document doc, Element rootElem, String[] stations, RadarServer.RadarType radarType ) throws Exception { for (String s : stations ) { Station stn = getStation( s, radarType ); Element sElem = new Element("station"); if( stn == null ) { // stn not in table sElem.setAttribute("id", s ); sElem.setAttribute("state", "XXX"); sElem.setAttribute("country", "XX"); sElem.addContent(new Element("name").addContent("Unknown")); sElem.addContent(new Element("longitude").addContent( "0.0" )); sElem.addContent(new Element("latitude").addContent( "0.0" )); sElem.addContent(new Element("elevation").addContent( "0" )); rootElem.addContent(sElem); continue; } sElem.setAttribute("id",s ); if( stn.getState() != null ) sElem.setAttribute("state",stn.getState()); if( stn.getCountry() != null ) sElem.setAttribute("country",stn.getCountry()); //if (s.getWmoId() != null) // sElem.setAttribute("wmo_id",s.getWmoId()); if (stn.getName() != null) sElem.addContent(new Element("name").addContent(stn.getName())); sElem.addContent(new Element("longitude").addContent( ucar.unidata.util.Format.d(stn.getLocation().getLongitude(), 6))); sElem.addContent(new Element("latitude").addContent( ucar.unidata.util.Format.d(stn.getLocation().getLatitude(), 6))); if (!Double.isNaN(stn.getLocation().getElevation())) sElem.addContent(new Element("elevation").addContent( ucar.unidata.util.Format.d(stn.getLocation().getElevation(), 6))); rootElem.addContent(sElem); } return doc; } public String[] stationsDS( RadarServer.RadarType radarType, String path ) throws Exception { String[] stations = null; if( path != null ) { File files = new File( path ); stations = files.list(); ArrayList<String> tmp = new ArrayList<String>(); for( String station : stations ) { if( station.startsWith( ".")) continue; tmp.add( station ); } // redo stations array for removal of . files if( stations.length != tmp.size() ) { stations = new String[tmp.size()]; stations = (String[]) tmp.toArray( stations ); } if( isVar( stations[ 0 ].toUpperCase(), radarType ) ) { if( radarType.equals( RadarServer.RadarType.nexrad ) ) { //path += "/N0R"; path += "/"+ nexradVars.get( 0 ); files = new File( path ); stations = files.list(); } else { //path += "/TR0"; path += "/"+ terminalVars.get( 0 ); files = new File( path ); stations = files.list(); } } } // no stations found if( stations == null || stations.length == 0 ) { stations = new String[ 1 ]; if( radarType.equals( RadarServer.RadarType.nexrad )) stations = nexradMap.keySet().toArray( stations ); else stations = terminalMap.keySet().toArray( stations ); } return stations; } /** * print station in a XML format from this info * @param stations * @param pw */ public void printStations( String[] stations, PrintWriter pw, RadarServer.RadarType radarType ) throws Exception { for (String s : stations ) { Station stn = getStation( s, radarType ); if( stn == null ) { pw.println( " <station id=\""+ s +"\" state=\"XX\" country=\"XX\">"); pw.println( " <name>Unknown</name>"); pw.println( " <latitude>0.0</latitude>"); pw.println( " <longitude>0.0</longitude>"); pw.println( " <elevation>0.0</elevation>"); pw.println( " </station>"); continue; } pw.println( " <station id=\""+ s +"\" state=\""+ stn.getState() +"\" country=\""+ stn.getCountry() +"\">"); pw.println( " <name>"+ stn.getName() +"</name>"); pw.println( " <latitude>"+ ucar.unidata.util.Format.d(stn.getLocation().getLatitude(), 6) +"</latitude>"); pw.println( " <longitude>"+ ucar.unidata.util.Format.d(stn.getLocation().getLongitude(), 6) +"</longitude>"); if (!Double.isNaN(stn.getLocation().getElevation())) pw.println( " <elevation>"+ ucar.unidata.util.Format.d(stn.getLocation().getElevation(), 6) +"</elevation>"); pw.println( " </station>"); } } /** * Determine if any of the given station names are actually in the dataset. * * @param stations List of station names * @return true if list is empty, ie no names are in the actual station list * @throws IOException if read error */ public boolean isStationListEmpty(List<String> stations, RadarServer.RadarType radarType ) { if( stations.get( 0 ).toUpperCase().equals( "ALL") ) return false; for (String s : stations ) { if( isStation( s, radarType )) return false; } return true; } /** * returns true if a station * @param station * @param radarType * @return boolean isStation */ public boolean isStation( String station, RadarServer.RadarType radarType ) { if( station.toUpperCase().equals( "ALL") ) return true; Station stn = null; if( station.length() == 3 && radarType.equals( RadarServer.RadarType.terminal ) ) { // terminal level3 station stn = terminalMap.get( "T"+ station ); } else if( station.length() == 3 ) { for( Station stn3 : nexradList ) { if( stn3.getValue().endsWith( station ) ) { stn = stn3; break; } } } else if( radarType.equals( RadarServer.RadarType.terminal ) ) { stn = terminalMap.get( station ); } else { stn = nexradMap.get( station ); } if( stn != null) return true; return false; } /** * returns station or null * @param station * @param radarType * @return station */ public Station getStation( String station, RadarServer.RadarType radarType ) { Station stn = null; if( station.length() == 3 && radarType.equals( RadarServer.RadarType.terminal ) ) { // terminal level3 station stn = terminalMap.get( "T"+ station ); } else if( station.length() == 3 ) { for( Station stn3 : nexradList ) { if( stn3.getValue().endsWith( station ) ) { stn = stn3; break; } } } else if( radarType.equals( RadarServer.RadarType.terminal ) ) { stn = terminalMap.get( station ); } else { stn = nexradMap.get( station ); } return stn; } /** * returns stations or null * @param radarType * @return station */ public String[] getStations( RadarServer.RadarType radarType ) { String[] stn = null; if( radarType.equals( RadarServer.RadarType.nexrad ) ) { stn = new String[ nexradList.size() ]; stn = nexradList.toArray( stn ); } else if( radarType.equals( RadarServer.RadarType.terminal ) ) { stn = new String[ terminalList.size() ]; stn = terminalList.toArray( stn ); } return stn; } /** * returns true if a variable * @param var * @param radarType * @return boolean isVar */ public boolean isVar( String var, RadarServer.RadarType radarType ) { if( var.toUpperCase().equals( "ALL") ) return true; /* if( radarType.equals( RadarServer.RadarType.nexrad ) ) { return nexradVars.contains( var ); } else if( radarType.equals( RadarServer.RadarType.terminal ) ) { return terminalVars.contains( var ); } */ if( nexradVars.contains( var ) ) { return true; } else if( terminalVars.contains( var ) ) { return true; } return false; } public String getStartDateTime( String path ) throws Exception { String timeDir = RadarServer.dataLocation.get( path ); log.debug( "timeDir ="+ timeDir ); // hard coded, otherwise not sure one get a valid station ID if( path.contains( "level3") ) { timeDir = timeDir + "/N0R/TLX"; } else if( path.contains( "level2") ) { timeDir = timeDir + "/KTLX"; } File dir = new File( timeDir ); String[] files = dir.list(); java.util.Arrays.sort( files, new CompareKeyDescend() ); // for( String file : files ) { // System.out.println( file ); // } String year = files[ files.length -1 ].substring( 0, 4); String month = files[ files.length -1 ].substring( 4, 6); String day = files[ files.length -1 ].substring( 6, 8); log.debug( year +"-"+ month +"-"+ day +"T:00:00:00Z" ); return year +"-"+ month +"-"+ day +"T:00:00:00Z"; } protected static class CompareKeyDescend implements Comparator<String> { /* public int compare(Object o1, Object o2) { String s1 = (String) o1; String s2 = (String) o2; return s2.compareTo(s1); } */ public int compare(String s1, String s2 ) { return s2.compareTo(s1); } } public static void main(String args[]) throws IOException { try { DateType dte = new DateType( "present", null, null); DateType dts = new DateType(ServerMethods.epic, null, null); System.out.println("DateType = (" + dts.toString() + ")"); System.out.println("Date = (" + dts.getDate() + ")"); } catch (java.text.ParseException e) { System.out.println("Illegal param= 'time' must be valid ISO Duration\n"); } } }