/**
     * Sets the set of times of all the profiles. The set will contain one or more times as double
     * values, in order, from earliest to latest.
     *
     * @param times The times of all the profiles.
     * @param source
     * @throws VisADException if a VisAD failure occurs.
     * @throws RemoteException if a Java RMI failure occurs.
     */
    public void setTimes(SampledSet times, SoundingDataNode source)
        throws VisADException, RemoteException {

      RealType timeType = (RealType) ((SetType) times.getType()).getDomain().getComponent(0);

      // use a LineDrawing because it's the simplest DisplayableData
      if (timesHolder == null) {
        timesHolder = new LineDrawing("times ref");
      }

      /*
       * Add a data object to the display that has the right
       * time-centers.
       */
      Field dummy =
          new FieldImpl(new FunctionType(timeType, AirTemperatureProfile.instance()), times);

      for (int i = 0, n = times.getLength(); i < n; i++) {
        dummy.setSample(i, AirTemperatureProfile.instance().missingData());
      }

      timesHolder.setData(dummy);
      if (widget == null) {
        if (times.getLength() == 1) {
          DateTime time =
              new DateTime(
                  new Real(
                      timeType, times.indexToDouble(new int[] {0})[0][0], times.getSetUnits()[0]));

          widget = GuiUtils.wrap(new JLabel(time.toString()));
          dataNode.setTime(time);
          setSounding(0);
        } else {
          //
          // Set the animation.
          //
          Animation animation = getInternalAnimation(timeType);
          getSoundingView().setExternalAnimation(animation, getAnimationWidget());
          aeroDisplay.addDisplayable(animation);
          aeroDisplay.addDisplayable(timesHolder);

          Container container = Box.createHorizontalBox();
          // Wrap these components so they don't get stretched in the Y direction
          container.add(GuiUtils.wrap(getAnimationWidget().getContents(false)));
          // container.add(GuiUtils.wrap (animationWidget.getIndicatorComponent()));

          widget = container;
        }
      }
    }
  /**
   * Converts a time-series of grid-relative winds to a time-series of true (or absolute) winds. The
   * U and V components of true wind are {@link WesterlyWind} and {@link SoutherlyWind},
   * respectively. The domain of the input {@link visad.Field} must be a temporal {@link
   * visad.Gridded1DSet} or a {@link visad.SingletonSet}. The range values of the input {@link
   * visad.Field} must be {@link visad.FlatField}s. The domains of the range {@link
   * visad.FlatField}s must have a manifold dimension of two or greater and they must have a
   * reference system which contains {@link visad.RealType#Latitude} and {@link
   * visad.RealType#Longitude}. The number of components in the range of the {@link
   * visad.FlatField}s must be two. Both components must have units convertible with {@link
   * #DEFAULT_SPEED_UNIT}. The first and second components are assumed to be the wind components in
   * the direction of increasing first and second manifold dimension indexes, respectively. The
   * domains of the {@link visad.FlatField}s must be equal. The {@link visad.Field} returned by this
   * method has the same domain as the input {@link visad.Field}. The range values of the returned
   * {@link visad.Field} are {@link visad.FlatField}s that have the same domain as the input {@link
   * visad.FlatField}s. The {@link visad.MathType} of the range of the returned {@link
   * visad.FlatField}s will be <code>CartesianHorizontalWind.getEarthVectorType()</code>.
   *
   * @param rel The time-series of grid-relative wind.
   * @return The time-series of true wind corresponding to the input.
   * @throws NullPointerException if <code>rel</code> is <code>null</code>.
   * @throws IllegalArgumentException if the input field doesn't have a time-series domain, or if
   *     the range values aren't {@link visad.FlatField} with the same domain, or if the domain of
   *     the {@link visad.FlatField}s doesn't have a transformation to latitude and longitude, or if
   *     the domain is irregular or has too few points, or if the {@link visad.FlatField}s don't
   *     have two and only two components in their range, or if the default units of the {@link
   *     visad.FlatField}s range aren't equal.
   * @throws VisADException if a VisAD failure occurs.
   * @throws RemoteException if a Java RMI failure occurs.
   * @see CartesianHorizontalWind
   */
  public static Field timeSeriesCartesianHorizontalWind(Field rel)
      throws VisADException, RemoteException {

    FunctionType outerFuncType = (FunctionType) rel.getType();
    RealTupleType outerDomType = outerFuncType.getDomain();

    if (!(RealType.Time.equalsExceptNameButUnits(outerDomType)
        || !RealType.TimeInterval.equalsExceptNameButUnits(outerDomType))) {
      throw new IllegalArgumentException(outerDomType.toString());
    }

    MathType innerFuncType = outerFuncType.getRange();

    if (!(innerFuncType instanceof FunctionType)) {
      throw new IllegalArgumentException(innerFuncType.toString());
    }

    Field innerField = (Field) rel.getSample(0);
    Set innerDom = innerField.getDomainSet();
    if (innerDom instanceof SingletonSet) {
      return rel;
    } else if (innerDom instanceof GriddedSet) {
      int[] lengths = ((GriddedSet) innerDom).getLengths();
      if ((lengths[0] == 1) && (lengths[1] == 1)) {
        return rel;
      }
    }

    // account for null units, assume m/sec
    Unit[] rangeUnits = innerField.getDefaultRangeUnits();
    if ((rangeUnits == null) || (rangeUnits[0] == null) || rangeUnits[0].isDimensionless()) {
      rangeUnits = CartesianHorizontalWind.getEarthVectorType().getDefaultUnits();
    }
    FunctionType innerType =
        new FunctionType(
            ((SetType) innerDom.getType()).getDomain(),
            CartesianHorizontalWind.getEarthVectorType());

    FlatField uvField =
        new FlatField(innerType, innerDom, (CoordinateSystem) null, (Set[]) null, rangeUnits);

    Field result =
        new FieldImpl(new FunctionType(outerDomType, uvField.getType()), rel.getDomainSet());

    // System.out.println("making rHatField");
    Field rHatField = (doNewCode ? hatFieldNew(innerDom, 0) : hatFieldOld(innerDom, 0));
    // System.out.println("making sHatField");
    Field sHatField = (doNewCode ? hatFieldNew(innerDom, 1) : hatFieldOld(innerDom, 1));

    float[][] rHats = rHatField.getFloats(false);
    // ucar.unidata.util.Misc.printArray("rHats[0]", rHats[0]);
    // ucar.unidata.util.Misc.printArray("rHats[1]", rHats[1]);
    // System.out.println("\n");
    float[][] sHats = sHatField.getFloats(false);
    // ucar.unidata.util.Misc.printArray("sHats[0]", sHats[0]);
    // ucar.unidata.util.Misc.printArray("sHats[1]", sHats[1]);
    // System.out.println("\n");
    float[] us = new float[innerDom.getLength()];
    float[] vs = new float[us.length];

    for (int i = 0, n = rel.getLength(); i < n; i++) {
      if (i > 0) {
        innerField = (Field) rel.getSample(i);
        Set dom = innerField.getDomainSet();
        if (!innerDom.equals(dom)) {
          // System.out.println("new domain");
          innerDom = dom;
          rHatField = (doNewCode ? hatFieldNew(innerDom, 0) : hatFieldOld(innerDom, 0));
          sHatField = (doNewCode ? hatFieldNew(innerDom, 1) : hatFieldOld(innerDom, 1));

          rHats = rHatField.getFloats(false);
          sHats = sHatField.getFloats(false);
          /*
          throw new IllegalArgumentException("template="
                  + innerDom.toString() + "; domain="
                  + dom.toString());
          */
        }
        uvField =
            new FlatField(innerType, innerDom, (CoordinateSystem) null, (Set[]) null, rangeUnits);
        us = new float[innerDom.getLength()];
        vs = new float[us.length];
      }

      float[][] rsWinds = innerField.getFloats(false);
      float[] rWinds = rsWinds[0];
      float[] sWinds = rsWinds[1];
      // ucar.unidata.util.Misc.printArray("rWinds", rWinds);
      // System.out.println("\n");
      // ucar.unidata.util.Misc.printArray("sWinds", sWinds);
      // System.out.println("\n");

      for (int j = 0; j < us.length; j++) {
        us[j] = rWinds[j] * rHats[0][j] + sWinds[j] * sHats[0][j];
        vs[j] = rWinds[j] * rHats[1][j] + sWinds[j] * sHats[1][j];
      }
      // ucar.unidata.util.Misc.printArray("us", us);
      // System.out.println("\n");
      // ucar.unidata.util.Misc.printArray("vs", vs);
      // System.out.println("\n");

      uvField.setSamples(new float[][] {us, vs}, false);
      result.setSample(i, uvField, false);
    }

    return result;
  }
  /**
   * run 'java visad.bom.ImageRendererJ3D len step' to test animation behavior of ImageRendererJ3D
   * renders a loop of len at step ms per frame then updates loop by deleting first time and adding
   * a new last time
   */
  public static void main(String args[]) throws VisADException, RemoteException, IOException {

    int step = 1000;
    int len = 3;
    if (args.length > 0) {
      try {
        len = Integer.parseInt(args[0]);
      } catch (NumberFormatException e) {
        len = 3;
      }
    }
    if (len < 1) len = 1;
    if (args.length > 1) {
      try {
        step = Integer.parseInt(args[1]);
      } catch (NumberFormatException e) {
        step = 1000;
      }
    }
    if (step < 1) step = 1;

    // create a netCDF reader
    Plain plain = new Plain();

    // open a netCDF file containing an image sequence and adapt
    // it to a Field Data object
    Field raw_image_sequence = null;
    try {
      // raw_image_sequence = (Field) plain.open("images256x256.nc");
      raw_image_sequence = (Field) plain.open("images.nc");
    } catch (IOException exc) {
      String s =
          "To run this example, the images.nc file must be "
              + "present in\nthe current directory."
              + "You can obtain this file from:\n"
              + "  ftp://www.ssec.wisc.edu/pub/visad-2.0/images.nc.Z";
      System.out.println(s);
      System.exit(0);
    }

    // just take first half of raw_image_sequence
    FunctionType image_sequence_type = (FunctionType) raw_image_sequence.getType();
    Set raw_set = raw_image_sequence.getDomainSet();
    float[][] raw_times = raw_set.getSamples();
    int raw_len = raw_times[0].length;
    if (raw_len != 4) {
      throw new VisADException("wrong number of images in sequence");
    }
    float raw_span = (4.0f / 3.0f) * (raw_times[0][3] - raw_times[0][0]);

    double[][] times = new double[1][len];
    for (int i = 0; i < len; i++) {
      times[0][i] = raw_times[0][i % raw_len] + raw_span * (i / raw_len);
    }
    Gridded1DDoubleSet set = new Gridded1DDoubleSet(raw_set.getType(), times, len);
    Field image_sequence = new FieldImpl(image_sequence_type, set);
    for (int i = 0; i < len; i++) {
      image_sequence.setSample(i, raw_image_sequence.getSample(i % raw_len));
    }

    // create a DataReference for image sequence
    final DataReference image_ref = new DataReferenceImpl("image");
    image_ref.setData(image_sequence);

    // create a Display using Java3D
    DisplayImpl display = new DisplayImplJ3D("image display");

    // extract the type of image and use
    // it to determine how images are displayed
    FunctionType image_type = (FunctionType) image_sequence_type.getRange();
    RealTupleType domain_type = image_type.getDomain();
    // map image coordinates to display coordinates
    display.addMap(new ScalarMap((RealType) domain_type.getComponent(0), Display.XAxis));
    display.addMap(new ScalarMap((RealType) domain_type.getComponent(1), Display.YAxis));
    // map image brightness values to RGB (default is grey scale)
    display.addMap(new ScalarMap((RealType) image_type.getRange(), Display.RGB));
    RealType hour_type = (RealType) image_sequence_type.getDomain().getComponent(0);
    ScalarMap animation_map = new ScalarMap(hour_type, Display.Animation);
    display.addMap(animation_map);
    AnimationControl animation_control = (AnimationControl) animation_map.getControl();
    animation_control.setStep(step);
    animation_control.setOn(true);

    /*
        // link the Display to image_ref
        ImageRendererJ3D renderer = new ImageRendererJ3D();
        display.addReferences(renderer, image_ref);
        // display.addReference(image_ref);
    */

    // create JFrame (i.e., a window) for display and slider
    JFrame frame = new JFrame("ImageRendererJ3D test");
    frame.addWindowListener(
        new WindowAdapter() {
          public void windowClosing(WindowEvent e) {
            System.exit(0);
          }
        });

    // create JPanel in JFrame
    JPanel panel = new JPanel();
    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
    frame.getContentPane().add(panel);

    // add display to JPanel
    panel.add(display.getComponent());

    // set size of JFrame and make it visible
    frame.setSize(500, 500);
    frame.setVisible(true);

    System.out.println("first animation sequence");
    // link the Display to image_ref
    ImageRendererJ3D renderer = new ImageRendererJ3D();
    display.addReferences(renderer, image_ref);
    // display.addReference(image_ref);

    // wait 4 * len seconds
    new Delay(len * 4000);

    // substitute a new image sequence for the old one
    for (int i = 0; i < len; i++) {
      times[0][i] = raw_times[0][(i + 1) % raw_len] + raw_span * ((i + 1) / raw_len);
    }
    set = new Gridded1DDoubleSet(raw_set.getType(), times, len);
    FieldImpl new_image_sequence = new FieldImpl(image_sequence_type, set);
    for (int i = 0; i < len; i++) {
      new_image_sequence.setSample(i, raw_image_sequence.getSample((i + 1) % raw_len));
    }

    System.out.println("second animation sequence");

    // tell renderer to resue frames in its scene graph
    renderer.setReUseFrames(true);
    image_ref.setData(new_image_sequence);
  }