/**
   * Rectify elevation raster. For best performance each elevation raster must have correct
   * parameters and values set. The <code>rectify()</code> operation validates that correct
   * Elevation min and max values are set or calculated. All values that beyond min/max and voids,
   * must be marked with "Missing Signal" (aka "nodata" value).
   *
   * @param raster A DataRaster to rectify
   * @throws IllegalArgumentException if <code>raster</code> is <code>null</code>
   */
  public static void rectify(ByteBufferRaster raster) throws IllegalArgumentException {
    if (null == raster) {
      String msg = Logging.getMessage("nullValue.RasterIsNull");
      Logging.logger().finest(msg);
      throw new IllegalArgumentException(msg);
    }

    int width = raster.getWidth();
    int height = raster.getHeight();

    if (width == 0 || height == 0) {
      // nothing to do
      return;
    }

    double[] minmax = raster.getExtremes();
    if (null == minmax) {
      // nothing to do
      return;
    }

    Double minValue = minmax[0];
    Double maxValue = minmax[1];

    Double missingDataSignal = AVListImpl.getDoubleValue(raster, AVKey.MISSING_DATA_SIGNAL, null);

    // check if the minimum value is one of the well known NODATA values
    if (ElevationsUtil.isKnownMissingSignal(minValue)
        || (missingDataSignal != null && missingDataSignal.equals(minValue))) {
      missingDataSignal = minValue;
      raster.setTransparentValue(missingDataSignal);

      minmax = raster.getExtremes();
      if (null != minmax) {
        minValue = minmax[0];
        maxValue = minmax[1];
      }
    }

    BufferWrapper bufferWrapper = raster.getBuffer();
    // Allocate a buffer to hold one row of scalar values.
    double[] array = new double[width];

    boolean needsConversion = false;
    double conversionValue = 1d;

    if (raster.hasKey(AVKey.ELEVATION_UNIT)) {
      String unit = raster.getStringValue(AVKey.ELEVATION_UNIT);
      if (AVKey.UNIT_METER.equalsIgnoreCase(unit)) {
        needsConversion = false;
      } else if (AVKey.UNIT_FOOT.equalsIgnoreCase(unit)) {
        needsConversion = true;
        conversionValue = WWMath.convertFeetToMeters(1);
        minValue = WWMath.convertFeetToMeters(minValue);
        maxValue = WWMath.convertFeetToMeters(maxValue);
        raster.setValue(AVKey.ELEVATION_UNIT, AVKey.UNIT_METER);
      } else {
        needsConversion = false;
        String msg = Logging.getMessage("generic.UnrecognizedElevationUnit", unit);
        Logging.logger().warning(msg);
      }
    }

    boolean rasterHasVoids = false;

    for (int j = 0; j < height; j++) {
      bufferWrapper.getDouble(j * width, array, 0, width);
      boolean commitChanges = false;

      for (int i = 0; i < width; i++) {
        double value = array[i];

        if (null != missingDataSignal && value == missingDataSignal) {
          rasterHasVoids = true;
        } else {
          if (needsConversion) {
            value *= conversionValue;
            commitChanges = true;
            array[i] = value;
          }

          if (value < minValue || value > maxValue) {
            rasterHasVoids = true;

            if (null != missingDataSignal) {
              array[i] = missingDataSignal;
              commitChanges = true;
            }
          }
        }
      }

      if (commitChanges) bufferWrapper.putDouble(j * width, array, 0, width);
    }

    if (rasterHasVoids) {
      if (missingDataSignal != null) raster.setValue(AVKey.MISSING_DATA_SIGNAL, missingDataSignal);
    } else {
      raster.removeKey(AVKey.MISSING_DATA_SIGNAL);
    }

    raster.setValue(AVKey.ELEVATION_MIN, minValue);
    raster.setValue(AVKey.ELEVATION_MAX, maxValue);
  }
  private void writeGeographicElevationGeoKeys(ArrayList<TiffIFDEntry> ifds, AVList params)
      throws IOException {
    long offset = this.theChannel.position();

    if (isElevation(params) && isGeographic(params)) {
      int epsg = GeoTiff.GCS.WGS_84;

      if (params.hasKey(AVKey.PROJECTION_EPSG_CODE))
        epsg = (Integer) params.getValue(AVKey.PROJECTION_EPSG_CODE);

      int elevUnits = GeoTiff.Unit.Linear.Meter;
      if (params.hasKey(AVKey.ELEVATION_UNIT)) {
        if (AVKey.UNIT_FOOT.equals(params.getValue(AVKey.ELEVATION_UNIT)))
          elevUnits = GeoTiff.Unit.Linear.Foot;
      }

      int rasterType = GeoTiff.RasterType.RasterPixelIsArea;
      if (params.hasKey(AVKey.RASTER_PIXEL)
          && AVKey.RASTER_PIXEL_IS_POINT.equals(params.getValue(AVKey.RASTER_PIXEL)))
        rasterType = GeoTiff.RasterType.RasterPixelIsPoint;

      short[] values =
          new short[] {
            // GeoKeyDirectory header
            GeoTiff.GeoKeyHeader.KeyDirectoryVersion,
            GeoTiff.GeoKeyHeader.KeyRevision,
            GeoTiff.GeoKeyHeader.MinorRevision,
            0, // IMPORTANT!! we will update count below, after the array initialization
            // end of header -

            // geo keys array

            /* key 1 */
            GeoTiff.GeoKey.ModelType,
            0,
            1,
            GeoTiff.ModelType.Geographic,
            /* key 2 */
            // TODO: Replace GeoTiff.RasterType.RasterPixelIsPoint
            GeoTiff.GeoKey.RasterType,
            0,
            1,
            (short) (0xFFFF & rasterType),
            /* key 3 */
            GeoTiff.GeoKey.GeographicType,
            0,
            1,
            (short) (0xFFFF & epsg),
            /* key 4 */
            GeoTiff.GeoKey.GeogAngularUnits,
            0,
            1,
            GeoTiff.Unit.Angular.Angular_Degree,
            /* key 5 */
            GeoTiff.GeoKey.VerticalCSType,
            0,
            1,
            GeoTiff.VCS.WGS_84_ellipsoid,
            /* key 6 */
            GeoTiff.GeoKey.VerticalUnits,
            0,
            1,
            (short) (0xFFFF & elevUnits),
          };

      // IMPORTANT!! update count - number of geokeys
      values[3] = (short) (values.length / 4);

      byte[] bytes = this.getBytes(values);
      this.theChannel.write(ByteBuffer.wrap(bytes));
      ifds.add(
          new TiffIFDEntry(GeoTiff.Tag.GEO_KEY_DIRECTORY, Tiff.Type.SHORT, values.length, offset));
    }
  }