/**
   * Converts grid-relative wind to true (or absolute) wind. The U and V components of true wind are
   * {@link WesterlyWind} and {@link SoutherlyWind}, respectively. The domain of the input {@link
   * visad.FlatField} must have a manifold dimension of two or greater and it 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 input {@link visad.FlatField} 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 {@link visad.MathType} of the range of the
   * returned {@link visad.FlatField} will be <code>CartesianHorizontalWind.getEarthVectorType()
   * </code> and the domain will be the same as the input domain.
   *
   * @param rel The field of grid-relative wind.
   * @return The field of true wind corresponding to the input field.
   * @throws NullPointerException if <code>rel</code> is <code>null</code>.
   * @throws IllegalArgumentException if the input field doesn't have two and only two components in
   *     its range, or if the default units of the input range aren't equal, or if the domain of the
   *     input field doesn't have a transformation to latitude and longitude, or the grid is
   *     irregular or has too few points.
   * @throws VisADException if a VisAD failure occurs.
   * @throws RemoteException if a Java RMI failure occurs.
   * @see CartesianHorizontalWind
   */
  public static FlatField cartesianHorizontalWind(FlatField rel)
      throws VisADException, RemoteException {

    FunctionType funcType = (FunctionType) rel.getType();
    MathType rangeType = funcType.getRange();

    if (rel.getRangeDimension() != 2) {
      throw new IllegalArgumentException(rangeType.toString());
    }

    Unit[] units = rel.getDefaultRangeUnits();

    if (!units[0].equals(units[1])) {
      throw new IllegalArgumentException(units.toString());
    }

    SampledSet grid = (SampledSet) rel.getDomainSet();
    // check for single point grid
    if (grid instanceof SingletonSet) {
      return rel;
    } else if (grid instanceof GriddedSet) {
      int[] lengths = ((GriddedSet) grid).getLengths();
      if ((lengths[0] == 1) && (lengths[1] == 1)) {
        return rel;
      }
    }
    FlatField abs =
        new FlatField(
            new FunctionType(funcType.getDomain(), CartesianHorizontalWind.getEarthVectorType()),
            grid,
            (CoordinateSystem[]) null,
            rel.getRangeSets(),
            units);

    abs.setSamples(trueWind(rel.getFloats(), grid), false);

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