Beispiel #1
0
  /**
   * Saves an image as a gif. Currently this uses ImageMagick's "convert" (Windows or Linux) because
   * it does the best job at color reduction (and is fast and is cross-platform). This will
   * overwrite an existing file.
   *
   * @param bi
   * @param fullGifName but without the .gif at the end
   * @throws Exception if trouble
   */
  public static void saveAsGif(BufferedImage bi, String fullGifName) throws Exception {

    // POLICY: because this procedure may be used in more than one thread,
    // do work on unique temp files names using randomInt, then rename to proper file name.
    // If procedure fails half way through, there won't be a half-finished file.
    int randomInt = Math2.random(Integer.MAX_VALUE);

    // save as .bmp     (note: doesn't support transparent pixels)
    long time = System.currentTimeMillis();
    if (verbose) String2.log("SgtUtil.saveAsGif");
    ImageIO.write(bi, "bmp", new File(fullGifName + randomInt + ".bmp"));
    if (verbose) String2.log("  make .bmp done. time=" + (System.currentTimeMillis() - time));

    // "convert" to .gif
    SSR.dosOrCShell(
        "convert " + fullGifName + randomInt + ".bmp" + " " + fullGifName + randomInt + ".gif", 30);
    File2.delete(fullGifName + randomInt + ".bmp");

    // try fancy color reduction algorithms
    // Image2.saveAsGif(Image2.reduceTo216Colors(bi), fullGifName + randomInt + ".gif");

    // try dithering
    // Image2.saveAsGif216(bi, fullGifName + randomInt + ".gif", true);

    // last step: rename to final gif name
    File2.rename(fullGifName + randomInt + ".gif", fullGifName + ".gif");

    if (verbose)
      String2.log(
          "SgtUtil.saveAsGif done. TOTAL TIME=" + (System.currentTimeMillis() - time) + "\n");
  }
Beispiel #2
0
 /**
  * This returns the maxBoldCharsPerLine based on charsPerLine.
  *
  * @param legendTextWidth in pixels
  * @param fontScale
  */
 public static int maxCharsPerLine(int legendTextWidth, double fontScale) {
   // lessen the effect of small fonts (they stay wide to stay legible)
   if (fontScale < 1) fontScale = (1 + fontScale) / 2;
   int m = Math2.roundToInt(legendTextWidth / (SgtUtil.AVG_CHAR_WIDTH * fontScale));
   // String2.log("\n***maxCharsPerLine=" + m + " legendWidth=" + legendTextWidth + " fontScale=" +
   // fontScale);
   return m;
 }
Beispiel #3
0
  /**
   * Saves an image as a gif. Currently this uses ImageMagick's "convert" (Windows or Linux) because
   * it does the best job at color reduction (and is fast and is cross-platform). This will
   * overwrite an existing file.
   *
   * @param bi
   * @param transparent the color to be made transparent
   * @param fullGifName but without the .gif at the end
   * @throws Exception if trouble
   */
  public static void saveAsTransparentGif(BufferedImage bi, Color transparent, String fullGifName)
      throws Exception {

    // POLICY: because this procedure may be used in more than one thread,
    // do work on unique temp files names using randomInt, then rename to proper file name.
    // If procedure fails half way through, there won't be a half-finished file.
    int randomInt = Math2.random(Integer.MAX_VALUE);

    // convert transparent color to be transparent
    long time = System.currentTimeMillis();
    Image image = Image2.makeImageBackgroundTransparent(bi, transparent, 10000);

    // convert image back to bufferedImage
    bi = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_ARGB);
    Graphics g = bi.getGraphics();
    g.drawImage(image, 0, 0, bi.getWidth(), bi.getHeight(), null);
    image = null; // encourage garbage collection

    // save as png
    int random = Math2.random(Integer.MAX_VALUE);
    ImageIO.write(bi, "png", new File(fullGifName + randomInt + ".png"));

    // "convert" to .gif
    SSR.dosOrCShell(
        "convert " + fullGifName + randomInt + ".png" + " " + fullGifName + randomInt + ".gif", 30);
    File2.delete(fullGifName + randomInt + ".png");

    // try fancy color reduction algorithms
    // Image2.saveAsGif(Image2.reduceTo216Colors(bi), fullGifName + randomInt + ".gif");

    // try dithering
    // Image2.saveAsGif216(bi, fullGifName + randomInt + ".gif", true);

    // last step: rename to final gif name
    File2.rename(fullGifName + randomInt + ".gif", fullGifName + ".gif");

    if (verbose)
      String2.log(
          "SgtUtil.saveAsTransparentGif TIME=" + (System.currentTimeMillis() - time) + "\n");
  }
Beispiel #4
0
  /**
   * The default palette (aka color bar) range ([0]=min, [1]=max). The values are also suitable for
   * the axis range on a graph.
   *
   * @param dataMin the raw minimum value of the data
   * @param dataMax the raw maximum value of the data
   * @return the default palette (aka color bar) range ([0]=min, [1]=max).
   */
  public static double[] suggestPaletteRange(double dataMin, double dataMax) {

    double lowHigh[] = Math2.suggestLowHigh(dataMin, dataMax);

    // log axis?
    if (suggestPaletteScale(dataMin, dataMax)
        .equals("Log")) { // yes, use dataMin,dataMax,  not lowHigh
      lowHigh[0] =
          Math2.suggestLowHigh(dataMin, 2 * dataMin)[0]; // trick to get nice suggested min>0
      return lowHigh;
    }

    // axis is linear
    // suggest symmetric around 0 (symbolized by BlueWhiteRed)?
    if (suggestPalette(dataMin, dataMax)
        .equals("BlueWhiteRed")) { // yes, use dataMin,dataMax,  not lowHigh
      double rangeMax = Math.max(-lowHigh[0], lowHigh[1]);
      lowHigh[0] = -rangeMax;
      lowHigh[1] = rangeMax;
    }

    // standard Rainbow Linear
    return lowHigh;
  }
Beispiel #5
0
  /**
   * Saves an image as a png. This will overwrite an existing file.
   *
   * @param bi
   * @param transparent the color to be made transparent (or null if none)
   * @param fullPngName but without the .png at the end
   * @throws Exception if trouble
   */
  public static void saveAsTransparentPng(BufferedImage bi, Color transparent, String fullPngName)
      throws Exception {

    // POLICY: because this procedure may be used in more than one thread,
    // do work on unique temp files names using randomInt, then rename to proper file name.
    // If procedure fails half way through, there won't be a half-finished file.
    int randomInt = Math2.random(Integer.MAX_VALUE);

    // create fileOutputStream
    BufferedOutputStream bos =
        new BufferedOutputStream(new FileOutputStream(fullPngName + randomInt + ".png"));

    // save the image
    saveAsTransparentPng(bi, transparent, bos);
    bos.close();

    // last step: rename to final Png name
    File2.rename(fullPngName + randomInt + ".png", fullPngName + ".png");
  }
Beispiel #6
0
 for (int i = 0; i < n; i++) {
   int tRow = Math2.random(n);
   Test.ensureEqual(pt.readBinaryInt(3, tRow), tRow, "");
 }
Beispiel #7
0
 for (int i = 0; i < n; i++) {
   int tRow = Math2.random(n);
   Test.ensureEqual(pt.readDouble(2, tRow), tRow, "");
 }
Beispiel #8
0
  /**
   * This tests this class.
   *
   * @throws Throwable if trouble.
   */
  public static void test() throws Throwable {
    String2.log("\nPersistentTable.test()");
    verbose = true;
    reallyVerbose = true;
    int n;
    long time;

    // find longest FLOAT_LENGTH
    String s = ("" + Float.MIN_VALUE * -4f / 3f);
    int longest = s.length();
    String longestS = s;
    for (int i = 0; i < 1000; i++) {
      s = "" + ((float) Math.random() / -1e10f);
      if (s.length() > longest) {
        longest = s.length();
        longestS = s;
      }
    }
    String2.log("float longestS=" + longestS + " length=" + longest);
    Test.ensureTrue(longest <= 15, "");

    // find longest DOUBLE_LENGTH
    s = ("" + Double.MIN_VALUE * -4.0 / 3.0);
    longest = s.length();
    longestS = s;
    for (int i = 0; i < 1000; i++) {
      s = "" + (Math.random() / -1e150);
      if (s.length() > longest) {
        longest = s.length();
        longestS = s;
      }
    }
    String2.log("double longestS=" + longestS + " length=" + longest);
    Test.ensureTrue(longest <= 24, "");

    // make a new table
    String name = EDStatic.fullTestCacheDirectory + "testPersistentTable.txt";
    File2.delete(name);
    int widths[] = {
      BOOLEAN_LENGTH,
      BYTE_LENGTH,
      BINARY_BYTE_LENGTH,
      BINARY_CHAR_LENGTH,
      SHORT_LENGTH,
      BINARY_SHORT_LENGTH,
      INT_LENGTH,
      BINARY_INT_LENGTH,
      LONG_LENGTH,
      BINARY_LONG_LENGTH,
      FLOAT_LENGTH,
      BINARY_FLOAT_LENGTH,
      DOUBLE_LENGTH,
      BINARY_DOUBLE_LENGTH,
      20
    };

    PersistentTable pt = new PersistentTable(name, "rw", widths);
    Test.ensureEqual(pt.nRows(), 0, "");
    Test.ensureEqual(pt.addRows(2), 2, "");
    String testS = "Now is the time f\u0F22r all good countrymen to come ...";
    pt.writeBoolean(0, 0, true);
    pt.writeBoolean(0, 1, false);
    pt.writeByte(1, 0, Byte.MIN_VALUE);
    pt.writeByte(1, 1, Byte.MAX_VALUE);
    pt.writeBinaryByte(2, 0, Byte.MIN_VALUE);
    pt.writeBinaryByte(2, 1, Byte.MAX_VALUE);
    pt.writeBinaryChar(3, 0, ' '); // hard because read will trim it to ""
    pt.writeBinaryChar(3, 1, '\u0F22');
    pt.writeShort(4, 0, Short.MIN_VALUE);
    pt.writeShort(4, 1, Short.MAX_VALUE);
    pt.writeBinaryShort(5, 0, Short.MIN_VALUE);
    pt.writeBinaryShort(5, 1, Short.MAX_VALUE);
    pt.writeInt(6, 0, Integer.MIN_VALUE);
    pt.writeInt(6, 1, Integer.MAX_VALUE);
    pt.writeBinaryInt(7, 0, Integer.MIN_VALUE);
    pt.writeBinaryInt(7, 1, Integer.MAX_VALUE);
    pt.writeLong(8, 0, Long.MIN_VALUE);
    pt.writeLong(8, 1, Long.MAX_VALUE);
    pt.writeBinaryLong(9, 0, Long.MIN_VALUE);
    pt.writeBinaryLong(9, 1, Long.MAX_VALUE);
    pt.writeFloat(10, 0, -Float.MAX_VALUE);
    pt.writeFloat(10, 1, Float.NaN);
    pt.writeBinaryFloat(11, 0, -Float.MAX_VALUE);
    pt.writeBinaryFloat(11, 1, Float.NaN);
    pt.writeDouble(12, 0, -Double.MAX_VALUE);
    pt.writeDouble(12, 1, Double.NaN);
    pt.writeBinaryDouble(13, 0, -Double.MAX_VALUE);
    pt.writeBinaryDouble(13, 1, Double.NaN);
    pt.writeString(14, 0, "");
    pt.writeString(14, 1, testS);

    Test.ensureEqual(pt.readBoolean(0, 0), true, "");
    Test.ensureEqual(pt.readBoolean(0, 1), false, "");
    Test.ensureEqual(pt.readByte(1, 0), Byte.MIN_VALUE, "");
    Test.ensureEqual(pt.readByte(1, 1), Byte.MAX_VALUE, "");
    Test.ensureEqual(pt.readBinaryByte(2, 0), Byte.MIN_VALUE, "");
    Test.ensureEqual(pt.readBinaryByte(2, 1), Byte.MAX_VALUE, "");
    Test.ensureEqual(pt.readBinaryChar(3, 0), ' ', "");
    Test.ensureEqual(pt.readBinaryChar(3, 1), '\u0F22', "");
    Test.ensureEqual(pt.readShort(4, 0), Short.MIN_VALUE, "");
    Test.ensureEqual(pt.readShort(4, 1), Short.MAX_VALUE, "");
    Test.ensureEqual(pt.readBinaryShort(5, 0), Short.MIN_VALUE, "");
    Test.ensureEqual(pt.readBinaryShort(5, 1), Short.MAX_VALUE, "");
    Test.ensureEqual(pt.readInt(6, 0), Integer.MIN_VALUE, "");
    Test.ensureEqual(pt.readInt(6, 1), Integer.MAX_VALUE, "");
    Test.ensureEqual(pt.readBinaryInt(7, 0), Integer.MIN_VALUE, "");
    Test.ensureEqual(pt.readBinaryInt(7, 1), Integer.MAX_VALUE, "");
    Test.ensureEqual(pt.readLong(8, 0), Long.MIN_VALUE, "");
    Test.ensureEqual(pt.readLong(8, 1), Long.MAX_VALUE, "");
    Test.ensureEqual(pt.readBinaryLong(9, 0), Long.MIN_VALUE, "");
    Test.ensureEqual(pt.readBinaryLong(9, 1), Long.MAX_VALUE, "");
    Test.ensureEqual(pt.readFloat(10, 0), -Float.MAX_VALUE, "");
    Test.ensureEqual(pt.readFloat(10, 1), Float.NaN, "");
    Test.ensureEqual(pt.readBinaryFloat(11, 0), -Float.MAX_VALUE, "");
    Test.ensureEqual(pt.readBinaryFloat(11, 1), Float.NaN, "");
    Test.ensureEqual(pt.readDouble(12, 0), -Double.MAX_VALUE, "");
    Test.ensureEqual(pt.readDouble(12, 1), Double.NaN, "");
    Test.ensureEqual(pt.readBinaryDouble(13, 0), -Double.MAX_VALUE, "");
    Test.ensureEqual(pt.readBinaryDouble(13, 1), Double.NaN, "");
    Test.ensureEqual(pt.readString(14, 0), "", "");
    // only 18 char returned because one takes 3 bytes in UTF-8
    Test.ensureEqual(pt.readString(14, 1), testS.substring(0, 18), "");
    pt.close();

    // reopen the file   data still there?
    pt = new PersistentTable(name, "rw", widths);
    Test.ensureEqual(pt.nRows(), 2, "");
    Test.ensureEqual(pt.readBoolean(0, 0), true, "");
    Test.ensureEqual(pt.readBoolean(0, 1), false, "");
    Test.ensureEqual(pt.readByte(1, 0), Byte.MIN_VALUE, "");
    Test.ensureEqual(pt.readByte(1, 1), Byte.MAX_VALUE, "");
    Test.ensureEqual(pt.readBinaryByte(2, 0), Byte.MIN_VALUE, "");
    Test.ensureEqual(pt.readBinaryByte(2, 1), Byte.MAX_VALUE, "");
    Test.ensureEqual(pt.readBinaryChar(3, 0), ' ', "");
    Test.ensureEqual(pt.readBinaryChar(3, 1), '\u0F22', "");
    Test.ensureEqual(pt.readShort(4, 0), Short.MIN_VALUE, "");
    Test.ensureEqual(pt.readShort(4, 1), Short.MAX_VALUE, "");
    Test.ensureEqual(pt.readBinaryShort(5, 0), Short.MIN_VALUE, "");
    Test.ensureEqual(pt.readBinaryShort(5, 1), Short.MAX_VALUE, "");
    Test.ensureEqual(pt.readInt(6, 0), Integer.MIN_VALUE, "");
    Test.ensureEqual(pt.readInt(6, 1), Integer.MAX_VALUE, "");
    Test.ensureEqual(pt.readBinaryInt(7, 0), Integer.MIN_VALUE, "");
    Test.ensureEqual(pt.readBinaryInt(7, 1), Integer.MAX_VALUE, "");
    Test.ensureEqual(pt.readLong(8, 0), Long.MIN_VALUE, "");
    Test.ensureEqual(pt.readLong(8, 1), Long.MAX_VALUE, "");
    Test.ensureEqual(pt.readBinaryLong(9, 0), Long.MIN_VALUE, "");
    Test.ensureEqual(pt.readBinaryLong(9, 1), Long.MAX_VALUE, "");
    Test.ensureEqual(pt.readFloat(10, 0), -Float.MAX_VALUE, "");
    Test.ensureEqual(pt.readFloat(10, 1), Float.NaN, "");
    Test.ensureEqual(pt.readBinaryFloat(11, 0), -Float.MAX_VALUE, "");
    Test.ensureEqual(pt.readBinaryFloat(11, 1), Float.NaN, "");
    Test.ensureEqual(pt.readDouble(12, 0), -Double.MAX_VALUE, "");
    Test.ensureEqual(pt.readDouble(12, 1), Double.NaN, "");
    Test.ensureEqual(pt.readBinaryDouble(13, 0), -Double.MAX_VALUE, "");
    Test.ensureEqual(pt.readBinaryDouble(13, 1), Double.NaN, "");
    Test.ensureEqual(pt.readString(14, 0), "", "");
    // only 18 char returned because one takes 3 bytes in UTF-8
    Test.ensureEqual(pt.readString(14, 1), testS.substring(0, 18), "");
    pt.close();

    String modes[] = {"rw", "rw", "rws", "rwd"};
    n = 1000;
    for (int mode = 0; mode < modes.length; mode++) {
      File2.delete(name);
      pt =
          new PersistentTable(
              name,
              modes[mode],
              new int[] {80, BINARY_DOUBLE_LENGTH, DOUBLE_LENGTH, BINARY_INT_LENGTH, INT_LENGTH});
      pt.addRows(n);
      if (mode == 1) String2.log("*** Note: 2nd rw test uses flush()");

      // string speed test
      time = System.currentTimeMillis();
      long modeTime = System.currentTimeMillis();
      for (int i = 0; i < n; i++) pt.writeString(0, i, testS + i);
      if (mode == 1) pt.flush();
      time = System.currentTimeMillis();
      String2.log(
          "\n"
              + modes[mode]
              + " time to write "
              + n
              + " Strings="
              + (System.currentTimeMillis() - time)
              + "   ("
              + new int[] {0, 0, 0, 0}[mode]
              + "ms)"); // java 1.6 0,0,0,0

      for (int i = 0; i < n; i++) {
        int tRow = Math2.random(n);
        Test.ensureEqual(pt.readString(0, tRow), testS + tRow, "");
      }
      String2.log(
          modes[mode]
              + " time to read "
              + n
              + " Strings="
              + (System.currentTimeMillis() - time)
              + "   ("
              + new int[] {15, 16, 47, 15}[mode]
Beispiel #9
0
 /** This reads a short from the file (or Short.MAX_VALUE if trouble). */
 public short readShort(int col, int row) throws IOException {
   return Math2.narrowToShort(String2.parseInt(readString(col, row)));
 }
Beispiel #10
0
 /** This reads a byte from the file (or Byte.MAX_VALUE if trouble). */
 public byte readByte(int col, int row) throws IOException {
   return Math2.narrowToByte(String2.parseInt(readString(col, row)));
 }
Beispiel #11
0
  /**
   * This creates the POST HTML form with the EmaAttributes.
   *
   * @param request is a request from a user
   * @param displayErrorMessage if false (for example, the first time the user sees the page), error
   *     messages aren't displayed
   * @return the HTML code for the form. The line separator is the newline character.
   */
  public String getHTMLForm(HttpServletRequest request, boolean displayErrorMessages) {
    long startTime = System.currentTimeMillis();
    HttpSession session = request.getSession();
    if (verbose)
      String2.log("\ngetHTMLForm session isNew=" + session.isNew() + " id=" + session.getId());
    nRequestsInitiated++;
    int rowNumber = 0;
    StringBuilder sb = new StringBuilder();

    sb.append(getStartOfHTMLForm());
    displayErrorMessages = false; // always

    // create the rows of the table with the attributes

    // title
    sb.append(
        "    <tr>\n"
            + "      <td colspan=\"2\">"
            + title
            + "</td>\n"
            + "      <td rowspan=\"6\"><img border=\"0\" src=\""
            + regionsImage
            + "\"\n"
            + "        width=\"228\" height=\"208\"\n"
            + "        alt=\""
            + regionsImageAlt
            + "\" title=\""
            + regionsImageTitle
            + "\"\n"
            + "        usemap=#regionCoordinates>\n"
            + "        <br><center><small>"
            + regionsImageTitle
            + "</small></center></td>\n"
            + "    </tr>\n");

    // dataset
    String dataSetValue = dataSet.getValue(session);
    int whichDataSet = String2.indexOf(activeDataSetOptions, dataSetValue);
    if (whichDataSet < 0) {
      whichDataSet = 0;
      dataSetValue = activeDataSetOptions[0];
      dataSet.setValue(session, dataSetValue);
    }
    dataSetRequests[String2.indexOf(dataSetOptions, dataSetValue)]++;
    if (verbose) String2.log("dataSetValue = " + dataSetValue);
    setBeginRow(beginRowArray[Math2.odd(rowNumber++) ? 1 : 0]);
    sb.append(dataSet.getTableEntry(dataSetValue, displayErrorMessages));

    // timePeriod
    Object[] object = (Object[]) activeDataSetContents.get(whichDataSet);
    String[] activeTimePeriodOptions = (String[]) object[0];
    String[] activeTimePeriodTitles = (String[]) object[1];
    Vector activeTimePeriodContents = (Vector) object[2];
    String dataSetDirectory = (String) object[3];
    if (verbose) String2.log("dataSetDirectory = " + dataSetDirectory);
    timePeriod.setOptions(activeTimePeriodOptions);
    timePeriod.setTitles(activeTimePeriodTitles);
    String timePeriodValue = timePeriod.getValue(session);
    int whichTimePeriod = String2.indexOf(activeTimePeriodOptions, timePeriodValue);
    if (whichTimePeriod < 0) {
      whichTimePeriod = 0;
      timePeriodValue = activeTimePeriodOptions[0];
      timePeriod.setValue(session, timePeriodValue);
    }
    timePeriodRequests[String2.indexOf(timePeriodOptions, timePeriodValue)]++;
    if (verbose) String2.log("timePeriodValue = " + timePeriodValue);
    setBeginRow(beginRowArray[Math2.odd(rowNumber++) ? 1 : 0]);
    sb.append(timePeriod.getTableEntry(timePeriodValue, displayErrorMessages));

    // region
    object = (Object[]) activeTimePeriodContents.get(whichTimePeriod);
    String[] activeRegionOptions = (String[]) object[0];
    String[] activeRegionTitles = (String[]) object[1];
    String[] activeRegionCoordinates = (String[]) object[2];
    Vector activeRegionContents = (Vector) object[3];
    String timePeriodDirectory = (String) object[4];
    region.setOptions(activeRegionOptions);
    region.setTitles(activeRegionTitles);
    String regionValue = region.getValue(session);
    int whichRegion = String2.indexOf(activeRegionOptions, regionValue);
    if (whichRegion < 0) {
      whichRegion = 0;
      regionValue = activeRegionOptions[0];
      region.setValue(session, regionValue);
    }
    regionRequests[String2.indexOf(regionOptions, regionValue)]++;
    if (verbose) String2.log("regionValue = " + regionValue);
    setBeginRow(beginRowArray[Math2.odd(rowNumber++) ? 1 : 0]);
    sb.append(region.getTableEntry(regionValue, displayErrorMessages));

    // define regionCoordinates for regionImage after activeRegionCoordinates known
    // do in reverse order, so small regions detected before large regions
    // (first match found is used)
    sb.append("    <map name=\"regionCoordinates\">\n");
    for (int i = activeRegionCoordinates.length - 1; i >= 0; i--)
      //        for (int i = 0; i < activeRegionCoordinates.length; i++)
      sb.append(
          "      <area shape=\"rect\" coords=\""
              + activeRegionCoordinates[i]
              + "\"\n"
              + "        title=\""
              + activeRegionTitles[i + 1]
              + "\"\n"
              + // +1 since 0 is main title
              "        href=\"#\" "
              + // was href=\"javascript:
              "onClick=\""
              + "document.forms[0].region["
              + i
              + "].checked=true; document.forms[0].submit();\">\n");
    sb.append("    </map>\n");

    // formSubmitted
    sb.append(formSubmitted.getControl("true"));

    // date
    object = (Object[]) activeRegionContents.get(whichRegion);
    String[] gifs = (String[]) object[0];
    String[] activeTimeOptions = (String[]) object[1];
    int[] getBits = (int[]) object[2];
    // if (verbose)
    //    String2.log("activeTimeOptions = " + String2.toCSSVString(activeTimeOptions));
    date.setOptions(activeTimeOptions);
    String timeValue = date.getValue(session);
    // find exact date match or one past (activeTimeOptions are sorted)
    // date will be off first time, and if user changes above settings and same date not available
    int whichDate = activeTimeOptions.length - 1; // last one
    if (timeValue != null) {
      for (int i = 0; i < activeTimeOptions.length; i++) {
        if (timeValue.compareTo(activeTimeOptions[i]) <= 0) {
          whichDate = i;
          break;
        }
      }
    }
    timeValue = activeTimeOptions[whichDate];
    date.setValue(session, timeValue);
    if (verbose) String2.log("timeValue = " + timeValue);
    setBeginRow(beginRowArray[Math2.odd(rowNumber++) ? 1 : 0]);
    sb.append(date.getTableEntry(timeValue, displayErrorMessages));

    // submitForm
    // may or may not be visible; so always a unique color (light red)
    setBeginRow("<tr bgcolor=\"#FFCCCC\">");
    sb.append(
        "    <noscript>\n"
            + submitForm.getTableEntry(submitForm.getValue(session), displayErrorMessages));
    sb.append("    </noscript>\n");

    // get
    String gifName = gifs[whichDate];
    int bits = getBits[whichDate];
    String currentFileDir =
        dataServer
            + dataSetDirectory
            + "/"
            + // for example, "QS"
            timePeriodDirectory
            + "/"; // for example, "1day"
    setBeginRow(beginRowArray[Math2.odd(rowNumber++) ? 1 : 0]);
    sb.append(
        "    "
            + getBeginRow()
            + "\n"
            + "      <td>"
            + classRB2.getString("get.label", "")
            + "&nbsp;</td>\n"
            + "      <td>");
    int nGetOptions = getOptions.length;
    for (int getI = 0; getI < nGetOptions; getI++)
      if ((bits & Math2.Two[getI]) != 0)
        sb.append(
            "<a href=\""
                + currentFileDir
                + getDirectories[getI]
                + "/"
                + gifName
                + getExtensions[getI]
                + "\"\n        title=\""
                + getTitles[getI + 1]
                + "\">"
                + // +1: title 0 is main title
                getOptions[getI]
                + "</a>\n        ");
    sb.append(
        //                     "<br><small>" + hereIs + "</small>\n" +
        "      </td>\n" + "    </tr>\n");

    // image
    String currentGifName = currentFileDir + getDirectories[0] + "/" + gifName + getExtensions[0];
    // "QN2005001_2005001_curl_westus.gif";
    sb.append(
        //            "    <tr><td>&nbsp;</td></tr>\n" +  //row 6
        //            "    <tr><td colspan=\"2\">" + hereIs + "</td></tr>\n" + //row 7
        "    <tr>\n"
            + // standard
            "      <td colspan=\"3\"><img border=\"0\" src=\""
            + currentGifName
            + "\"\n"
            + // row 8
            "        width=\"650\" height=\"502\"\n"
            + "        title=\""
            + hereIs
            + " "
            + currentGifName
            + "\"\n"
            + "        alt=\""
            + hereIsAlt
            + " "
            + currentGifName
            + "\">\n"
            + "      </td>\n"
            + "    </tr>\n");

    // update requestedFilesMap
    Integer I = (Integer) requestedFilesMap.get(gifName);
    requestedFilesMap.put(gifName, new Integer(I == null ? 1 : I.intValue() + 1));

    // end of table, end of form
    sb.append(getEndOfHTMLForm(startTime, ""));
    nRequestsCompleted++;

    sb.append(
        "<p>DISCLAIMER OF ENDORSEMENT\n"
            + "<br>Any reference obtained from this server to a specific commercial product,\n"
            + "process, or service does not constitute or imply an endorsement by CoastWatch,\n"
            + "NOAA, or the United States Government of the product, process, or service, or \n"
            + "its producer or provider. The views and opinions expressed in any referenced \n"
            + "document do not necessarily state or reflect those of CoastWatch,\n"
            + "NOAA, or the United States Government.\n"
            + "\n"
            + "<p>DISCLAIMER FOR EXTERNAL LINKS\n"
            + "<br>The appearance of external links on this World Wide Web site does not\n"
            + "constitute endorsement by the\n"
            + "<a href=\"http://www.doc.gov\">Department of Commerce</a>/<a href=\"http://www.noaa.gov\">National\n"
            + "Oceanic and Atmospheric Administration</a> of external Web sites or the information,\n"
            + "products or services contained\n"
            + "therein. For other than authorized activities the Department of Commerce/NOAA does not\n"
            + "exercise any editorial control over the information you may find at these locations. These\n"
            + "links are provided consistent with the stated purpose of this Department of Commerce/NOAA\n"
            + "Web site.\n"
            + "\n"
            + "<p>DISCLAIMER OF LIABILITY\n"
            + "<br>Neither the data Contributors, CoastWatch, NOAA, nor the United States Government, \n"
            + "nor any of their employees or contractors, makes any warranty, express or implied, \n"
            + "including warranties of merchantability and fitness for a particular purpose, \n"
            + "or assumes any legal liability for the accuracy, completeness, or usefulness,\n"
            + "of any information at this site.\n"
            + "\n"
            + "<p><font size=\"-1\">Please email questions, comments, or\n"
            + "suggestions regarding this web page to\n"
            + "<A HREF=\"mailto:[email protected]\">[email protected]</A>.</font>");

    return sb.toString();
  }