private void writeGeographicImageGeoKeys(ArrayList<TiffIFDEntry> ifds, AVList params)
      throws IOException {
    long offset = this.theChannel.position();

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

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

      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 */
            GeoTiff.GeoKey.RasterType,
            0,
            1,
            (short) (0xFFFF & GeoTiff.RasterType.RasterPixelIsArea),
            /* key 3 */
            GeoTiff.GeoKey.GeographicType,
            0,
            1,
            (short) (0xFFFF & epsg),
            /* key 4 */
            GeoTiff.GeoKey.GeogAngularUnits,
            0,
            1,
            GeoTiff.Unit.Angular.Angular_Degree
          };

      // 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));
    }
  }
  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));
    }
  }
 private static boolean isGeographic(AVList params) {
   return (null != params
       && params.hasKey(AVKey.COORDINATE_SYSTEM)
       && AVKey.COORDINATE_SYSTEM_GEOGRAPHIC.equals(params.getValue(AVKey.COORDINATE_SYSTEM)));
 }
 private static boolean isProjected(AVList params) {
   return (null != params
       && params.hasKey(AVKey.COORDINATE_SYSTEM)
       && AVKey.COORDINATE_SYSTEM_PROJECTED.equals(params.getValue(AVKey.COORDINATE_SYSTEM)));
 }
 private static boolean isImage(AVList params) {
   return (null != params
       && params.hasKey(AVKey.PIXEL_FORMAT)
       && AVKey.IMAGE.equals(params.getValue(AVKey.PIXEL_FORMAT)));
 }
 private static boolean isElevation(AVList params) {
   return (null != params
       && params.hasKey(AVKey.PIXEL_FORMAT)
       && AVKey.ELEVATION.equals(params.getValue(AVKey.PIXEL_FORMAT)));
 }
  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());
    }
  }
  private void appendGeoTiff(ArrayList<TiffIFDEntry> ifds, AVList params)
      throws IOException, IllegalArgumentException {
    if (null == params || 0 == params.getEntries().size()) {
      String reason = Logging.getMessage("nullValue.AVListIsNull");
      Logging.logger().finest(Logging.getMessage("GeotiffWriter.GeoKeysMissing", reason));
      return;
    }

    long offset = this.theChannel.position();

    if (params.hasKey(AVKey.DISPLAY_NAME)) {
      String value = params.getStringValue(AVKey.DISPLAY_NAME);
      if (null != value && 0 < value.trim().length()) {
        offset = this.theChannel.position();
        byte[] bytes = value.trim().getBytes();
        this.theChannel.write(ByteBuffer.wrap(bytes));
        ifds.add(new TiffIFDEntry(Tiff.Tag.DOCUMENT_NAME, Tiff.Type.ASCII, bytes.length, offset));
      }
    }

    if (params.hasKey(AVKey.DESCRIPTION)) {
      String value = params.getStringValue(AVKey.DESCRIPTION);
      if (null != value && 0 < value.trim().length()) {
        offset = this.theChannel.position();
        byte[] bytes = value.trim().getBytes();
        this.theChannel.write(ByteBuffer.wrap(bytes));
        ifds.add(
            new TiffIFDEntry(Tiff.Tag.IMAGE_DESCRIPTION, Tiff.Type.ASCII, bytes.length, offset));
      }
    }

    if (params.hasKey(AVKey.VERSION)) {
      String value = params.getStringValue(AVKey.VERSION);
      if (null != value && 0 < value.trim().length()) {
        offset = this.theChannel.position();
        byte[] bytes = value.trim().getBytes();
        this.theChannel.write(ByteBuffer.wrap(bytes));
        ifds.add(
            new TiffIFDEntry(Tiff.Tag.SOFTWARE_VERSION, Tiff.Type.ASCII, bytes.length, offset));
      }
    }

    if (params.hasKey(AVKey.DATE_TIME)) {
      String value = params.getStringValue(AVKey.DATE_TIME);
      if (null != value && 0 < value.trim().length()) {
        offset = this.theChannel.position();
        byte[] bytes = value.getBytes();
        this.theChannel.write(ByteBuffer.wrap(bytes));
        ifds.add(new TiffIFDEntry(Tiff.Tag.DATE_TIME, Tiff.Type.ASCII, bytes.length, offset));
      }
    }

    if (params.hasKey(AVKey.SECTOR)) {
      if (params.hasKey(AVKey.PIXEL_WIDTH) && params.hasKey(AVKey.PIXEL_HEIGHT)) {
        offset = this.theChannel.position();
        double[] values =
            new double[] {
              (Double) params.getValue(AVKey.PIXEL_WIDTH),
              (Double) params.getValue(AVKey.PIXEL_HEIGHT),
              isElevation(params) ? 1d : 0d
            };
        byte[] bytes = this.getBytes(values);
        this.theChannel.write(ByteBuffer.wrap(bytes));
        ifds.add(
            new TiffIFDEntry(
                GeoTiff.Tag.MODEL_PIXELSCALE, Tiff.Type.DOUBLE, values.length, offset));
      }

      if (params.hasKey(AVKey.WIDTH) && params.hasKey(AVKey.HEIGHT)) {
        offset = this.theChannel.position();

        double w = (Integer) params.getValue(AVKey.WIDTH);
        double h = (Integer) params.getValue(AVKey.HEIGHT);

        Sector sec = (Sector) params.getValue(AVKey.SECTOR);

        double[] values =
            new double[] { // i ,  j, k=0, x, y, z=0
              0d,
              0d,
              0d,
              sec.getMinLongitude().degrees,
              sec.getMaxLatitude().degrees,
              0d,
              w - 1,
              0d,
              0d,
              sec.getMaxLongitude().degrees,
              sec.getMaxLatitude().degrees,
              0d,
              w - 1,
              h - 1,
              0d,
              sec.getMaxLongitude().degrees,
              sec.getMinLatitude().degrees,
              0d,
              0d,
              h - 1,
              0d,
              sec.getMinLongitude().degrees,
              sec.getMinLatitude().degrees,
              0d,
            };

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

      // Tiff.Tag.MODEL_TRANSFORMATION excludes Tiff.Tag.MODEL_TIEPOINT & Tiff.Tag.MODEL_PIXELSCALE

      if (params.hasKey(AVKey.MISSING_DATA_SIGNAL)
          || params.hasKey(AVKey.MISSING_DATA_REPLACEMENT)) {
        offset = this.theChannel.position();

        Object nodata =
            params.hasKey(AVKey.MISSING_DATA_SIGNAL)
                ? params.getValue(AVKey.MISSING_DATA_SIGNAL)
                : params.getValue(AVKey.MISSING_DATA_REPLACEMENT);

        String value = "" + nodata + "\0";
        byte[] bytes = value.getBytes();
        this.theChannel.write(ByteBuffer.wrap(bytes));
        ifds.add(new TiffIFDEntry(GeoTiff.Tag.GDAL_NODATA, Tiff.Type.ASCII, bytes.length, offset));
      }

      if (params.hasKey(AVKey.COORDINATE_SYSTEM)) {
        String cs = params.getStringValue(AVKey.COORDINATE_SYSTEM);

        if (AVKey.COORDINATE_SYSTEM_GEOGRAPHIC.equals(cs)) {
          if (isElevation(params)) this.writeGeographicElevationGeoKeys(ifds, params);
          else this.writeGeographicImageGeoKeys(ifds, params);
        } else if (AVKey.COORDINATE_SYSTEM_PROJECTED.equals(cs)) {
          String msg = Logging.getMessage("GeotiffWriter.FeatureNotImplementedd", cs);
          Logging.logger().severe(msg);
          throw new IllegalArgumentException(msg);
          // TODO extract PCS (Projection Coordinate System)
        } else {
          String msg = Logging.getMessage("GeotiffWriter.UnknownCoordinateSystem", cs);
          Logging.logger().severe(msg);
          throw new IllegalArgumentException(msg);
        }
      }
    }
  }
  public void write(BufferedImage image, AVList params) throws IOException {
    if (image == null) {
      String msg = Logging.getMessage("nullValue.ImageSource");
      Logging.logger().severe(msg);
      throw new IllegalArgumentException(msg);
    }

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

    if (null == params || 0 == params.getValues().size()) {
      String reason = Logging.getMessage("nullValue.AVListIsNull");
      Logging.logger().finest(Logging.getMessage("GeotiffWriter.GeoKeysMissing", reason));
      params = new AVListImpl();
    } else {
      this.validateParameters(params, image.getWidth(), image.getHeight());
    }

    // how we proceed in part depends upon the image type...
    int type = image.getType();

    // handle CUSTOM type which comes from our GeoTiffreader (for now)
    if (BufferedImage.TYPE_CUSTOM == type) {
      int numColorComponents = 0, numComponents = 0, pixelSize = 0, dataType = 0, csType = 0;
      boolean hasAlpha = false;

      if (null != image.getColorModel()) {
        ColorModel cm = image.getColorModel();

        numColorComponents = cm.getNumColorComponents();
        numComponents = cm.getNumComponents();
        pixelSize = cm.getPixelSize();
        hasAlpha = cm.hasAlpha();

        ColorSpace cs = cm.getColorSpace();
        if (null != cs) csType = cs.getType();
      }

      if (null != image.getSampleModel()) {
        SampleModel sm = image.getSampleModel();
        dataType = sm.getDataType();
      }

      if (dataType == DataBuffer.TYPE_FLOAT && pixelSize == Float.SIZE && numComponents == 1) {
        type = BufferedImage_TYPE_ELEVATION_FLOAT32;
      } else if (dataType == DataBuffer.TYPE_SHORT
          && pixelSize == Short.SIZE
          && numComponents == 1) {
        type = BufferedImage_TYPE_ELEVATION_SHORT16;
      } else if (ColorSpace.CS_GRAY == csType && pixelSize == Byte.SIZE) {
        type = BufferedImage.TYPE_BYTE_GRAY;
      } else if (dataType == DataBuffer.TYPE_USHORT
          && ColorSpace.CS_GRAY == csType
          && pixelSize == Short.SIZE) {
        type = BufferedImage.TYPE_USHORT_GRAY;
      } else if (ColorSpace.TYPE_RGB == csType
          && pixelSize == 3 * Byte.SIZE
          && numColorComponents == 3) {
        type = BufferedImage.TYPE_3BYTE_BGR;
      } else if (ColorSpace.TYPE_RGB == csType
          && hasAlpha
          && pixelSize == 4 * Byte.SIZE
          && numComponents == 4) {
        type = BufferedImage.TYPE_4BYTE_ABGR;
      }
    }

    switch (type) {
      case BufferedImage.TYPE_3BYTE_BGR:
      case BufferedImage.TYPE_4BYTE_ABGR:
      case BufferedImage.TYPE_4BYTE_ABGR_PRE:
      case BufferedImage.TYPE_INT_RGB:
      case BufferedImage.TYPE_INT_BGR:
      case BufferedImage.TYPE_INT_ARGB:
      case BufferedImage.TYPE_INT_ARGB_PRE:
        {
          this.writeColorImage(image, params);
        }
        break;

      case BufferedImage.TYPE_USHORT_GRAY:
      case BufferedImage.TYPE_BYTE_GRAY:
        {
          this.writeGrayscaleImage(image, params);
        }
        break;

      case BufferedImage_TYPE_ELEVATION_SHORT16:
      case BufferedImage_TYPE_ELEVATION_FLOAT32:
        {
          String msg = Logging.getMessage("GeotiffWriter.FeatureNotImplementedd", type);
          Logging.logger().severe(msg);
          throw new IllegalArgumentException(msg);
        }
        //            break;

      case BufferedImage.TYPE_CUSTOM:
      default:
        {
          ColorModel cm = image.getColorModel();
          SampleModel sm = image.getSampleModel();

          StringBuffer sb =
              new StringBuffer(Logging.getMessage("GeotiffWriter.UnsupportedType", type));

          sb.append("\n");
          sb.append("NumBands=").append(sm.getNumBands()).append("\n");
          sb.append("NumDataElements=").append(sm.getNumDataElements()).append("\n");
          sb.append("NumColorComponents=").append(cm.getNumColorComponents()).append("\n");
          sb.append("NumComponents=").append(cm.getNumComponents()).append("\n");
          sb.append("PixelSize=").append(cm.getPixelSize()).append("\n");
          sb.append("hasAlpha=").append(cm.hasAlpha());

          String msg = sb.toString();
          Logging.logger().severe(msg);
          throw new IllegalArgumentException(msg);
        }
    }
  }