// check that parms have valid stations, vars and times
  private Boolean checkQueryParms(
      DatasetRepository.RadarType radarType, QueryParams qp, Boolean level2) throws IOException {
    if (qp.hasBB) {
      if (radarType.equals(DatasetRepository.RadarType.nexrad))
        qp.stns = RadarServerUtil.getStationNames(qp.getBB(), DatasetRepository.nexradList);
      else qp.stns = RadarServerUtil.getStationNames(qp.getBB(), DatasetRepository.terminalList);

      if (qp.stns.size() == 0) {
        qp.errs.append("Bounding Box contains no stations ");
        return false;
      }
      if (!level2) qp.stns = RadarServerUtil.convert4to3stations(qp.stns);
    }

    if (qp.hasStns) {
      if (RadarServerUtil.isStationListEmpty(qp.stns, radarType)) {
        qp.errs.append("No valid stations specified, need 1 ");
        return false;
      } else if (level2) {
        for (String stn : qp.stns) {
          if (stn.length() == 3) {
            qp.errs.append("Need 4 character station names ");
            return false;
          }
        }
      } else if (!level2) qp.stns = RadarServerUtil.convert4to3stations(qp.stns);
    }

    if (qp.hasLatlonPoint) {
      qp.stns = new ArrayList<String>();
      if (radarType.equals(DatasetRepository.RadarType.nexrad))
        qp.stns.add(
            RadarServerUtil.findClosestStation(qp.lat, qp.lon, DatasetRepository.nexradList));
      else
        qp.stns.add(
            RadarServerUtil.findClosestStation(qp.lat, qp.lon, DatasetRepository.terminalList));
      if (!level2) qp.stns = RadarServerUtil.convert4to3stations(qp.stns);
    } else if (qp.fatal) {
      qp.errs.append("No valid stations specified 2 ");
      return false;
    }

    if (qp.stns == null || qp.stns.size() == 0) {
      qp.errs.append("No valid stations specified, need 1 ");
      return false;
    }
    boolean useAllStations = (qp.stns.get(0).toUpperCase().equals("ALL"));
    if (useAllStations) {
      if (radarType.equals(DatasetRepository.RadarType.nexrad))
        qp.stns =
            RadarServerUtil.getStationNames(DatasetRepository.nexradList); // need station names
      else
        qp.stns =
            RadarServerUtil.getStationNames(DatasetRepository.terminalList); // need station names
      if (!level2) qp.stns = RadarServerUtil.convert4to3stations(qp.stns);
    }

    if (qp.hasTimePoint) {
      if (qp.time.isPresent()) {
        try {
          qp.time_end = new DateType("present", null, null);
          qp.time_start = epicDateType;
        } catch (java.text.ParseException e) {
          qp.errs.append("Illegal param= 'time' must be valid ISO Duration");
          return false;
        }
      } 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 { // get all times
      qp.time_latest = 1;
      // qp.hasDateRange = true;
      try {
        qp.time = new DateType("present", null, null);
        qp.time_end = new DateType("present", null, null);
        qp.time_start = epicDateType;
      } catch (java.text.ParseException e) {
        qp.errs.append("Illegal param= 'time' must be valid ISO Duration ");
        return false;
      }
    }

    if (level2) {
      qp.vars = null; // level2 can't select vars
    } else if (qp.vars == null) { // level 3 with no vars
      qp.errs.append("No vars selected ");
      return false;
    } else if (qp.vars.get(0).contains("/")) { // remove desc from vars
      ArrayList<String> tmp = new ArrayList<String>();
      for (String var : qp.vars) {
        tmp.add(var.replaceFirst("/.*", ""));
      }
      qp.vars = tmp;
    }
    return true;
  }
  // create catalog Header
  private Boolean createHeader(
      DatasetRepository.RadarType radarType,
      QueryParams qp,
      String pathInfo,
      Map<String, Object> model)
      throws IOException {

    Boolean level2 = pathInfo.contains("level2");
    int level = (level2) ? 2 : 3;
    StringBuffer str = new StringBuffer();
    str.append("Radar Level").append(level).append(" datasets in near real time");
    model.put("name", str.toString());
    str.setLength(0);
    str.append("/thredds/dodsC/").append(pathInfo).append("/");
    model.put("base", str.toString());
    str.setLength(0);
    str.append("RadarLevel").append(level).append(" datasets for available stations and times");
    model.put("dname", str.toString());
    str.setLength(0);
    str.append("accept=").append(qp.acceptType).append("&");
    if (!level2 && qp.vars != null) { // add vars
      str.append("var=");
      for (int i = 0; i < qp.vars.size(); i++) {
        str.append(qp.vars.get(i));
        if (i < qp.vars.size() - 1) {
          str.append(",");
        }
      }
      str.append("&");
    }
    // use all stations
    if (qp.stns.get(0).toUpperCase().equals("ALL")) {
      str.append("stn=ALL&");
    } else if (qp.hasStns) {
      for (String station : qp.stns) {
        str.append("stn=").append(station).append("&");
      }
    } else if (qp.hasBB) {
      str.append("south=").append(qp.south).append("&north=").append(qp.north).append("&");
      str.append("west=").append(qp.west).append("&east=").append(qp.east).append("&");
    }

    // no time given
    if (qp.time_latest == 1) {
      // str.deleteCharAt( str.length() -1);
      str.append("time=present");
    } else if (qp.hasDateRange) {
      if (qp.time_start.getDate() == null
          || qp.time_start.isBlank()
          || qp.time_end.getDate() == null
          || qp.time_end.isBlank()) {
        str.append("time_start=").append(qp.time_start.toString());
        str.append("&time_end=").append(qp.time_end.toString());
        qp.errs.append("need ISO time format ");
        return false;
      } else {
        str.append("time_start=").append(qp.time_start.toDateTimeStringISO());
        str.append("&time_end=").append(qp.time_end.toDateTimeStringISO());
      }
    } else if (qp.time.isPresent()) {
      str.append("time=present");
    } else if (qp.hasTimePoint) {
      if (qp.time.getDate() == null || qp.time.isBlank()) {
        str.append("time=").append(qp.time.toString());
        qp.errs.append("need ISO time format ");
        return false;
      } else {
        str.append("time=").append(qp.time.toDateTimeStringISO());
      }
    }
    model.put("ID", str.toString());

    if (level2) {
      model.put("type", "NEXRAD2");
    } else if (radarType.equals(DatasetRepository.RadarType.nexrad)) {
      model.put("type", "NIDS");
    } else {
      model.put("type", "TDWR");
    }

    // at this point must have stations
    if (RadarServerUtil.isStationListEmpty(qp.stns, radarType)) {
      qp.errs.append("No station(s) meet query criteria ");
      return false;
    }
    return true;
  }
  // get/check/process query
  public void radarQuery(HttpServletRequest req, HttpServletResponse res, Map<String, Object> model)
      throws ServletException, IOException, RadarServerException {

    //      long  startms = System.currentTimeMillis();
    //      long  endms;
    DatasetRepository.RadarType radarType = DatasetRepository.RadarType.nexrad;
    // need to extract data according to the (dataset) given
    String pathInfo = req.getPathInfo();
    if (pathInfo == null) pathInfo = "";
    if (pathInfo.startsWith("/")) pathInfo = pathInfo.substring(1);
    try {
      String rt = pathInfo.substring(0, pathInfo.indexOf('/', 1));
      radarType = DatasetRepository.RadarType.valueOf(rt);
    } catch (Exception e) {
      log.info("Invalid dataset url reference " + pathInfo);
      throw new RadarServerException("Invalid dataset url reference " + pathInfo);
    }
    Boolean level2 = pathInfo.contains("level2");

    // parse the input
    QueryParams qp = new QueryParams();
    if (!qp.parseQuery(
        req,
        res,
        new String[] {QueryParams.XML, QueryParams.HTML, QueryParams.RAW, QueryParams.NETCDF})) {
      // log.error( "parseQuery Failed "+ qp.errs.toString() + req.getQueryString() );
      // throw new RadarServerException( qp.errs.toString() );//+ req.getQueryString() );
      return; // TODO: uncomment above 2 lines when QueryParams exception is fixed
    }
    //      endms = System.currentTimeMillis();
    //      System.out.println( "after QueryParams "+ (endms - startms));
    //      startms = System.currentTimeMillis();
    // check Query Params
    if (!checkQueryParms(radarType, qp, level2)) {
      log.error("checkQueryParms Failed " + qp.errs.toString() + req.getQueryString());
      throw new RadarServerException(qp.errs.toString()); // + req.getQueryString() );
    }
    //      endms = System.currentTimeMillis();
    //      System.out.println( "after checkQueryParms "+ (endms - startms));
    //      startms = System.currentTimeMillis();

    // check type of output wanted XML html
    qp.acceptType = qp.acceptType.replaceFirst(".*/", "");

    // creates first part of catalog
    if (!createHeader(radarType, qp, pathInfo, model)) {
      log.error("Write Header Failed " + qp.errs.toString() + req.getQueryString());
      throw new RadarServerException(qp.errs.toString()); // 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 = false;
    List<DatasetEntry> entries = new ArrayList<DatasetEntry>();
    if (qp.vars == null) {
      dataFound = processQuery(pathInfo, qp, null, entries);
      if (releaseDataset) DatasetRepository.removeRadarDatasetCollection(pathInfo, null);
    } else {
      int count = 0;
      for (String var : qp.vars) {
        dataFound = processQuery(pathInfo, qp, var, entries);
        if (dataFound) count++;
        if (releaseDataset) DatasetRepository.removeRadarDatasetCollection(pathInfo, var);
      }
      if (count > 0) dataFound = true;
    }
    // save entries
    model.put("datasets", entries);
    if (dataFound) {
      model.put("documentation", Integer.toString(entries.size()) + " datasets found for query");
    } else if (qp.errs.length() > 0) {
      model.put("documentation", qp.errs.toString());
    } else {
      model.put("documentation", "No data available for station(s) and time range");
    }

    //      endms = System.currentTimeMillis();
    //      System.out.println( "after radarQuery "+ (endms - startms));
    //      startms = System.currentTimeMillis();

  } // end radarNexradQuery