protected void validateParameters(AVList list, int srcWidth, int srcHeight)
      throws IllegalArgumentException {
    if (null == list || 0 == list.getValues().size()) {
      String reason = Logging.getMessage("nullValue.AVListIsNull");
      String msg = Logging.getMessage("GeotiffWriter.GeoKeysMissing", reason);
      Logging.logger().finest(msg);
      throw new IllegalArgumentException(msg);
    }

    if (!(srcWidth > 0 && srcHeight > 0)) {
      String msg = Logging.getMessage("generic.InvalidImageSize", srcWidth, srcHeight);
      Logging.logger().finest(msg);
      throw new IllegalArgumentException(msg);
    }

    if (list.hasKey(AVKey.WIDTH)) {
      int width = (Integer) list.getValue(AVKey.WIDTH);
      if (width != srcWidth) {
        String msg = Logging.getMessage("GeotiffWriter.ImageWidthMismatch", width, srcWidth);
        Logging.logger().severe(msg);
        throw new IllegalArgumentException(msg);
      }
    } else list.setValue(AVKey.WIDTH, srcWidth);

    if (list.hasKey(AVKey.HEIGHT)) {
      int height = (Integer) list.getValue(AVKey.HEIGHT);
      if (height != srcHeight) {
        String msg = Logging.getMessage("GeotiffWriter.ImageHeightMismatch", height, srcHeight);
        Logging.logger().severe(msg);
        throw new IllegalArgumentException(msg);
      }
    } else list.setValue(AVKey.HEIGHT, srcHeight);

    Sector sector = null;

    if (list.hasKey(AVKey.SECTOR)) sector = (Sector) list.getValue(AVKey.SECTOR);

    if (null == sector) {
      String msg = Logging.getMessage("GeotiffWriter.NoSectorSpecified");
      Logging.logger().severe(msg);
      throw new IllegalArgumentException(msg);
    }

    if (!list.hasKey(AVKey.COORDINATE_SYSTEM)) {
      String msg = Logging.getMessage("GeotiffWriter.GeoKeysMissing", AVKey.COORDINATE_SYSTEM);
      Logging.logger().finest(msg);
      //            throw new IllegalArgumentException(msg);

      // assume Geodetic Coordinate System
      list.setValue(AVKey.COORDINATE_SYSTEM, AVKey.COORDINATE_SYSTEM_GEOGRAPHIC);
    }

    if (!list.hasKey(AVKey.PROJECTION_EPSG_CODE)) {
      if (isGeographic(list)) {
        // assume WGS84
        list.setValue(AVKey.PROJECTION_EPSG_CODE, GeoTiff.GCS.WGS_84);
      } else {
        String msg = Logging.getMessage("GeotiffWriter.GeoKeysMissing", AVKey.PROJECTION_EPSG_CODE);
        Logging.logger().finest(msg);
        throw new IllegalArgumentException(msg);
      }
    }

    // if PIXEL_WIDTH is specified, we are not overriding it because UTM images
    // will have different pixel size
    if (!list.hasKey(AVKey.PIXEL_WIDTH)) {
      if (isGeographic(list)) {
        double pixelWidth = sector.getDeltaLonDegrees() / (double) srcWidth;
        list.setValue(AVKey.PIXEL_WIDTH, pixelWidth);
      } else {
        String msg = Logging.getMessage("GeotiffWriter.GeoKeysMissing", AVKey.PIXEL_WIDTH);
        Logging.logger().finest(msg);
        throw new IllegalArgumentException(msg);
      }
    }

    // if PIXEL_HEIGHT is specified, we are not overriding it
    // because UTM images will have different pixel size
    if (!list.hasKey(AVKey.PIXEL_HEIGHT)) {
      if (isGeographic(list)) {
        double pixelHeight = sector.getDeltaLatDegrees() / (double) srcHeight;
        list.setValue(AVKey.PIXEL_HEIGHT, pixelHeight);
      } else {
        String msg = Logging.getMessage("GeotiffWriter.GeoKeysMissing", AVKey.PIXEL_HEIGHT);
        Logging.logger().finest(msg);
        throw new IllegalArgumentException(msg);
      }
    }

    if (!list.hasKey(AVKey.PIXEL_FORMAT)) {
      String msg = Logging.getMessage("GeotiffWriter.GeoKeysMissing", AVKey.PIXEL_FORMAT);
      Logging.logger().finest(msg);
      throw new IllegalArgumentException(msg);
    } else {
      String pixelFormat = list.getStringValue(AVKey.PIXEL_FORMAT);
      if (!AVKey.ELEVATION.equals(pixelFormat) && !AVKey.IMAGE.equals(pixelFormat)) {
        String msg =
            Logging.getMessage("Geotiff.UnknownGeoKeyValue", pixelFormat, AVKey.PIXEL_FORMAT);
        Logging.logger().severe(msg);
        throw new IllegalArgumentException(msg);
      }
    }

    // validate elevation parameters
    if (AVKey.ELEVATION.equals(list.getValue(AVKey.PIXEL_FORMAT))) {
      if (!list.hasKey(AVKey.DATA_TYPE)) {
        String msg = Logging.getMessage("GeotiffWriter.GeoKeysMissing", AVKey.DATA_TYPE);
        Logging.logger().finest(msg);
        throw new IllegalArgumentException(msg);
      }

      String type = list.getStringValue(AVKey.DATA_TYPE);
      if (!AVKey.FLOAT32.equals(type) && !AVKey.INT16.equals(type)) {
        String msg = Logging.getMessage("Geotiff.UnknownGeoKeyValue", type, AVKey.DATA_TYPE);
        Logging.logger().severe(msg);
        throw new IllegalArgumentException(msg);
      }
    }

    if (!list.hasKey(AVKey.ORIGIN)) {
      // set UpperLeft corner as the origin, if not specified
      LatLon origin = new LatLon(sector.getMaxLatitude(), sector.getMinLongitude());
      list.setValue(AVKey.ORIGIN, origin);
    }

    if (list.hasKey(AVKey.BYTE_ORDER)
        && !AVKey.BIG_ENDIAN.equals(list.getStringValue(AVKey.BYTE_ORDER))) {
      String msg =
          Logging.getMessage(
              "generic.UnrecognizedByteOrder", list.getStringValue(AVKey.BYTE_ORDER));
      Logging.logger().severe(msg);
      throw new IllegalArgumentException(msg);
    }

    if (!list.hasKey(AVKey.DATE_TIME)) {
      // add NUL (\0) termination as required by TIFF v6 spec (20 bytes length)
      String timestamp = String.format("%1$tY:%1$tm:%1$td %tT\0", Calendar.getInstance());
      list.setValue(AVKey.DATE_TIME, timestamp);
    }

    if (!list.hasKey(AVKey.VERSION)) {
      list.setValue(AVKey.VERSION, Version.getVersion());
    }
  }
  public void writeRaster(BufferWrapperRaster raster) throws IOException, IllegalArgumentException {
    if (raster == null) {
      String msg = Logging.getMessage("nullValue.RasterIsNull");
      Logging.logger().severe(msg);
      throw new IllegalArgumentException(msg);
    }

    if (0 == raster.getWidth() || 0 == raster.getHeight()) {
      String msg =
          Logging.getMessage("generic.InvalidImageSize", raster.getWidth(), raster.getHeight());
      Logging.logger().severe(msg);
      throw new IllegalArgumentException(msg);
    }

    this.validateParameters(raster, raster.getWidth(), raster.getHeight());

    int bitsPerSample, samplesPerPixel, sampleFormat, photometric, numBands;

    if (AVKey.ELEVATION.equals(raster.getValue(AVKey.PIXEL_FORMAT))) {
      if (AVKey.FLOAT32.equals(raster.getValue(AVKey.DATA_TYPE))) {
        numBands = 1;
        samplesPerPixel = Tiff.SamplesPerPixel.MONOCHROME;
        sampleFormat = Tiff.SampleFormat.IEEEFLOAT;
        photometric = Tiff.Photometric.Grayscale_BlackIsZero;
        bitsPerSample = Tiff.BitsPerSample.ELEVATIONS_FLOAT32;
      } else if (AVKey.INT16.equals(raster.getValue(AVKey.DATA_TYPE))) {
        numBands = 1;
        samplesPerPixel = Tiff.SamplesPerPixel.MONOCHROME;
        sampleFormat = Tiff.SampleFormat.SIGNED;
        photometric = Tiff.Photometric.Grayscale_BlackIsZero;
        bitsPerSample = Tiff.BitsPerSample.ELEVATIONS_INT16;
      } else {
        String msg =
            Logging.getMessage("GeotiffWriter.UnsupportedType", raster.getValue(AVKey.DATA_TYPE));
        Logging.logger().severe(msg);
        throw new IllegalArgumentException(msg);
      }
    } else if (AVKey.IMAGE.equals(raster.getValue(AVKey.PIXEL_FORMAT))) {
      if (AVKey.INT8.equals(raster.getValue(AVKey.DATA_TYPE))) {
        numBands = 1;
        samplesPerPixel = Tiff.SamplesPerPixel.MONOCHROME;
        sampleFormat = Tiff.SampleFormat.UNSIGNED;
        photometric = Tiff.Photometric.Grayscale_BlackIsZero;
        bitsPerSample = Tiff.BitsPerSample.MONOCHROME_UINT8;
      } else if (AVKey.INT16.equals(raster.getValue(AVKey.DATA_TYPE))) {
        numBands = 1;
        samplesPerPixel = Tiff.SamplesPerPixel.MONOCHROME;
        sampleFormat = Tiff.SampleFormat.UNSIGNED;
        photometric = Tiff.Photometric.Grayscale_BlackIsZero;
        bitsPerSample = Tiff.BitsPerSample.MONOCHROME_UINT16;
      } else if (AVKey.INT32.equals(raster.getValue(AVKey.DATA_TYPE))) {
        numBands = 3;
        // TODO check ALPHA / Transparency
        samplesPerPixel = Tiff.SamplesPerPixel.RGB;
        sampleFormat = Tiff.SampleFormat.UNSIGNED;
        photometric = Tiff.Photometric.Color_RGB;
        bitsPerSample = Tiff.BitsPerSample.RGB;
      } else {
        String msg =
            Logging.getMessage("GeotiffWriter.UnsupportedType", raster.getValue(AVKey.DATA_TYPE));
        Logging.logger().severe(msg);
        throw new IllegalArgumentException(msg);
      }
    } else {
      String msg =
          Logging.getMessage("GeotiffWriter.UnsupportedType", raster.getValue(AVKey.PIXEL_FORMAT));
      Logging.logger().severe(msg);
      throw new IllegalArgumentException(msg);
    }

    int bytesPerSample = numBands * bitsPerSample / Byte.SIZE;

    this.writeTiffHeader();

    // write the image data...
    int numRows = raster.getHeight();
    int numCols = raster.getWidth();
    int[] stripCounts = new int[numRows];
    int[] stripOffsets = new int[numRows];

    BufferWrapper srcBuffer = raster.getBuffer();

    ByteBuffer dataBuff = ByteBuffer.allocateDirect(numCols * bytesPerSample);

    switch (bitsPerSample) {
        //            case Tiff.BitsPerSample.MONOCHROME_BYTE:
      case Tiff.BitsPerSample.MONOCHROME_UINT8:
        {
          for (int y = 0; y < numRows; y++) {
            stripOffsets[y] = (int) this.theChannel.position();
            stripCounts[y] = numCols * bytesPerSample;

            dataBuff.clear();

            for (int x = 0; x < numCols * numBands; x++) {
              dataBuff.put(srcBuffer.getByte(x + y * numCols));
            }

            dataBuff.flip();
            this.theChannel.write(dataBuff);
          }
        }
        break;

        //            case Tiff.BitsPerSample.MONOCHROME_UINT16:
      case Tiff.BitsPerSample.ELEVATIONS_INT16:
        {
          for (int y = 0; y < numRows; y++) {
            stripOffsets[y] = (int) this.theChannel.position();
            stripCounts[y] = numCols * bytesPerSample;

            dataBuff.clear();

            for (int x = 0; x < numCols * numBands; x++) {
              dataBuff.putShort(srcBuffer.getShort(x + y * numCols));
            }

            dataBuff.flip();
            this.theChannel.write(dataBuff);
          }
        }
        break;

      case Tiff.BitsPerSample.ELEVATIONS_FLOAT32:
        {
          for (int y = 0; y < numRows; y++) {
            stripOffsets[y] = (int) this.theChannel.position();
            stripCounts[y] = numCols * bytesPerSample;

            dataBuff.clear();

            for (int x = 0; x < numCols * numBands; x++) {
              dataBuff.putFloat(srcBuffer.getFloat(x + y * numCols));
            }

            dataBuff.flip();
            this.theChannel.write(dataBuff);
          }
        }
        break;

      case Tiff.BitsPerSample.RGB:
        {
          for (int y = 0; y < numRows; y++) {
            stripOffsets[y] = (int) this.theChannel.position();
            stripCounts[y] = numCols * bytesPerSample;

            dataBuff.clear();

            for (int x = 0; x < numCols; x++) {
              int color = srcBuffer.getInt(x + y * numCols);
              byte red = (byte) (0xFF & (color >> 16));
              byte green = (byte) (0xFF & (color >> 8));
              byte blue = (byte) (0xFF & color);

              //                        dataBuff.put(0xFF & (color >> 24)); // alpha
              dataBuff.put(red).put(green).put(blue);
            }
            dataBuff.flip();
            this.theChannel.write(dataBuff);
          }
        }
        break;
    }

    // write out values for the tiff tags and build up the IFD. These are supposed to be sorted; for
    // now
    // do this manually here.
    ArrayList<TiffIFDEntry> ifds = new ArrayList<TiffIFDEntry>(10);

    ifds.add(new TiffIFDEntry(Tiff.Tag.IMAGE_WIDTH, Tiff.Type.LONG, 1, numCols));
    ifds.add(new TiffIFDEntry(Tiff.Tag.IMAGE_LENGTH, Tiff.Type.LONG, 1, numRows));

    long offset = this.theChannel.position();
    if (Tiff.BitsPerSample.RGB == bitsPerSample) {
      short[] bps = new short[numBands];
      for (int i = 0; i < numBands; i++) {
        bps[i] = Tiff.BitsPerSample.MONOCHROME_BYTE;
      }
      this.theChannel.write(ByteBuffer.wrap(this.getBytes(bps)));
      ifds.add(new TiffIFDEntry(Tiff.Tag.BITS_PER_SAMPLE, Tiff.Type.SHORT, numBands, offset));
    } else ifds.add(new TiffIFDEntry(Tiff.Tag.BITS_PER_SAMPLE, Tiff.Type.SHORT, 1, bitsPerSample));

    ifds.add(new TiffIFDEntry(Tiff.Tag.COMPRESSION, Tiff.Type.LONG, 1, Tiff.Compression.NONE));
    ifds.add(new TiffIFDEntry(Tiff.Tag.PHOTO_INTERPRETATION, Tiff.Type.SHORT, 1, photometric));
    ifds.add(new TiffIFDEntry(Tiff.Tag.SAMPLES_PER_PIXEL, Tiff.Type.SHORT, 1, samplesPerPixel));
    ifds.add(new TiffIFDEntry(Tiff.Tag.ORIENTATION, Tiff.Type.SHORT, 1, Tiff.Orientation.DEFAULT));
    ifds.add(
        new TiffIFDEntry(
            Tiff.Tag.PLANAR_CONFIGURATION, Tiff.Type.SHORT, 1, Tiff.PlanarConfiguration.CHUNKY));
    ifds.add(new TiffIFDEntry(Tiff.Tag.SAMPLE_FORMAT, Tiff.Type.SHORT, 1, sampleFormat));

    offset = this.theChannel.position();
    dataBuff = ByteBuffer.allocateDirect(stripOffsets.length * INTEGER_SIZEOF);
    for (int stripOffset : stripOffsets) {
      dataBuff.putInt(stripOffset);
    }
    dataBuff.flip();
    this.theChannel.write(dataBuff);

    ifds.add(new TiffIFDEntry(Tiff.Tag.STRIP_OFFSETS, Tiff.Type.LONG, stripOffsets.length, offset));
    ifds.add(new TiffIFDEntry(Tiff.Tag.ROWS_PER_STRIP, Tiff.Type.LONG, 1, 1));

    offset = this.theChannel.position();
    dataBuff.clear(); // stripOffsets and stripCounts are same length by design; can reuse the
    // ByteBuffer...
    for (int stripCount : stripCounts) {
      dataBuff.putInt(stripCount);
    }
    dataBuff.flip();
    this.theChannel.write(dataBuff);

    ifds.add(
        new TiffIFDEntry(Tiff.Tag.STRIP_BYTE_COUNTS, Tiff.Type.LONG, stripCounts.length, offset));

    this.appendGeoTiff(ifds, raster);

    this.writeIFDs(ifds);
  }