/**
   * 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;
  }