private void applyGeoCoding(TiffFileInfo info, TIFFImageMetadata metadata, Product product) {
    if (info.containsField(GeoTIFFTagSet.TAG_MODEL_TIE_POINT)) {

      double[] tiePoints = info.getField(GeoTIFFTagSet.TAG_MODEL_TIE_POINT).getAsDoubles();

      boolean isGlobal = isGlobal(product, info);

      // check if we have a global geographic lat/lon with lon from 0..360 instead of -180..180
      final double deltaX = Math.ceil(360. / product.getSceneRasterWidth());
      if (isGlobal && tiePoints.length == 6 && Math.abs(tiePoints[3]) < deltaX) {
        // e.g. tiePoints[3] = -0.5, productWidth=722 --> we have a lon range of 361 which should
        // start
        // at or near -180 but not at zero
        isGlobalShifted180 = true;
        // subtract 180 from the longitudes
        tiePoints[3] -= 180.0;
      }

      if (canCreateTiePointGeoCoding(tiePoints)) {
        applyTiePointGeoCoding(info, tiePoints, product);
      } else if (canCreateGcpGeoCoding(tiePoints)) {
        applyGcpGeoCoding(info, tiePoints, product);
      }
    }

    if (product.getGeoCoding() == null) {
      try {
        applyGeoCodingFromGeoTiff(metadata, product);
      } catch (Exception ignored) {
      }
    }
  }
  private static void applyTiePointGeoCoding(
      TiffFileInfo info, double[] tiePoints, Product product) {
    final SortedSet<Double> xSet = new TreeSet<>();
    final SortedSet<Double> ySet = new TreeSet<>();
    for (int i = 0; i < tiePoints.length; i += 6) {
      xSet.add(tiePoints[i]);
      ySet.add(tiePoints[i + 1]);
    }
    final double xMin = xSet.first();
    final double xMax = xSet.last();
    final double xDiff = (xMax - xMin) / (xSet.size() - 1);
    final double yMin = ySet.first();
    final double yMax = ySet.last();
    final double yDiff = (yMax - yMin) / (ySet.size() - 1);

    final int width = xSet.size();
    final int height = ySet.size();

    int idx = 0;
    final Map<Double, Integer> xIdx = new HashMap<>();
    for (Double val : xSet) {
      xIdx.put(val, idx);
      idx++;
    }
    idx = 0;
    final Map<Double, Integer> yIdx = new HashMap<>();
    for (Double val : ySet) {
      yIdx.put(val, idx);
      idx++;
    }

    final float[] lats = new float[width * height];
    final float[] lons = new float[width * height];

    for (int i = 0; i < tiePoints.length; i += 6) {
      final int idxX = xIdx.get(tiePoints[i + 0]);
      final int idxY = yIdx.get(tiePoints[i + 1]);
      final int arrayIdx = idxY * width + idxX;
      lons[arrayIdx] = (float) tiePoints[i + 3];
      lats[arrayIdx] = (float) tiePoints[i + 4];
    }

    String[] names = Utils.findSuitableLatLonNames(product);
    final TiePointGrid latGrid =
        new TiePointGrid(names[0], width, height, xMin, yMin, xDiff, yDiff, lats);
    final TiePointGrid lonGrid =
        new TiePointGrid(names[1], width, height, xMin, yMin, xDiff, yDiff, lons);

    product.addTiePointGrid(latGrid);
    product.addTiePointGrid(lonGrid);
    final SortedMap<Integer, GeoKeyEntry> geoKeyEntries = info.getGeoKeyEntries();
    final Datum datum = getDatum(geoKeyEntries);
    product.setGeoCoding(new TiePointGeoCoding(latGrid, lonGrid, datum));
  }
  private static boolean isGlobal(Product product, TiffFileInfo info) {
    boolean isGlobal = false;
    final TIFFField pixelScaleField = info.getField(GeoTIFFTagSet.TAG_MODEL_PIXEL_SCALE);
    if (pixelScaleField != null) {
      double[] pixelScales = pixelScaleField.getAsDoubles();

      if (isPixelScaleValid(pixelScales)) {
        final double widthInDegree = pixelScales[0] * product.getSceneRasterWidth();
        isGlobal = Math.ceil(widthInDegree) >= 360;
      }
    }

    return isGlobal;
  }
  private static void applyGcpGeoCoding(
      final TiffFileInfo info, final double[] tiePoints, final Product product) {

    int numTiePoints = tiePoints.length / 6;

    final GcpGeoCoding.Method method;
    if (numTiePoints >= GcpGeoCoding.Method.POLYNOMIAL3.getTermCountP()) {
      method = GcpGeoCoding.Method.POLYNOMIAL3;
    } else if (numTiePoints >= GcpGeoCoding.Method.POLYNOMIAL2.getTermCountP()) {
      method = GcpGeoCoding.Method.POLYNOMIAL2;
    } else if (numTiePoints >= GcpGeoCoding.Method.POLYNOMIAL1.getTermCountP()) {
      method = GcpGeoCoding.Method.POLYNOMIAL1;
    } else {
      return; // not able to apply GCP geo coding; not enough tie points
    }

    final int width = product.getSceneRasterWidth();
    final int height = product.getSceneRasterHeight();

    final GcpDescriptor gcpDescriptor = GcpDescriptor.getInstance();
    final ProductNodeGroup<Placemark> gcpGroup = product.getGcpGroup();
    for (int i = 0; i < numTiePoints; i++) {
      final int offset = i * 6;

      final float x = (float) tiePoints[offset + 0];
      final float y = (float) tiePoints[offset + 1];
      final float lon = (float) tiePoints[offset + 3];
      final float lat = (float) tiePoints[offset + 4];

      if (Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(lon) || Double.isNaN(lat)) {
        continue;
      }
      final PixelPos pixelPos = new PixelPos(x, y);
      final GeoPos geoPos = new GeoPos(lat, lon);

      final Placemark gcp =
          Placemark.createPointPlacemark(
              gcpDescriptor, "gcp_" + i, "GCP_" + i, "", pixelPos, geoPos, product.getGeoCoding());
      gcpGroup.add(gcp);
    }

    final Placemark[] gcps = gcpGroup.toArray(new Placemark[gcpGroup.getNodeCount()]);
    final SortedMap<Integer, GeoKeyEntry> geoKeyEntries = info.getGeoKeyEntries();
    final Datum datum = getDatum(geoKeyEntries);
    product.setGeoCoding(new GcpGeoCoding(method, gcps, width, height, datum));
  }
 private void addBandsToProduct(TiffFileInfo tiffInfo, Product product) throws IOException {
   final ImageReadParam readParam = imageReader.getDefaultReadParam();
   TIFFRenderedImage baseImage =
       (TIFFRenderedImage) imageReader.readAsRenderedImage(FIRST_IMAGE, readParam);
   SampleModel sampleModel = baseImage.getSampleModel();
   final int numBands = sampleModel.getNumBands();
   final int productDataType = ImageManager.getProductDataType(sampleModel.getDataType());
   bandMap = new HashMap<>(numBands);
   for (int i = 0; i < numBands; i++) {
     final String bandName = String.format("band_%d", i + 1);
     final Band band = product.addBand(bandName, productDataType);
     if (tiffInfo.containsField(BaselineTIFFTagSet.TAG_COLOR_MAP)
         && baseImage.getColorModel() instanceof IndexColorModel) {
       band.setImageInfo(createIndexedImageInfo(product, baseImage, band));
     }
     bandMap.put(band, i);
   }
 }
  Product readGeoTIFFProduct(final ImageInputStream stream, final File inputFile)
      throws IOException {
    Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(stream);
    while (imageReaders.hasNext()) {
      final ImageReader reader = imageReaders.next();
      if (reader instanceof TIFFImageReader) {
        imageReader = (TIFFImageReader) reader;
        break;
      }
    }
    if (imageReader == null) {
      throw new IOException("GeoTiff imageReader not found");
    }

    imageReader.setInput(stream);

    Product product = null;

    final TIFFImageMetadata imageMetadata =
        (TIFFImageMetadata) imageReader.getImageMetadata(FIRST_IMAGE);
    final TiffFileInfo tiffInfo = new TiffFileInfo(imageMetadata.getRootIFD());
    final TIFFField field = tiffInfo.getField(Utils.PRIVATE_BEAM_TIFF_TAG_NUMBER);
    if (field != null && field.getType() == TIFFTag.TIFF_ASCII) {
      final String s = field.getAsString(0).trim();
      if (s.contains("<Dimap_Document")) { // with DIMAP header
        InputStream is = null;
        try {
          final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
          final DocumentBuilder builder = factory.newDocumentBuilder();
          is = new ByteArrayInputStream(s.getBytes());
          final Document document = new DOMBuilder().build(builder.parse(is));
          product = DimapProductHelpers.createProduct(document);
          removeGeoCodingAndTiePointGrids(product);
          initBandsMap(product);
        } catch (ParserConfigurationException | SAXException ignore) {
          // ignore if it can not be read
        } finally {
          if (is != null) {
            is.close();
          }
        }
      }
    }

    if (product == null) { // without DIMAP header
      final String productName;
      if (tiffInfo.containsField(BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION)) {
        final TIFFField field1 = tiffInfo.getField(BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION);
        productName = field1.getAsString(0);
      } else if (inputFile != null) {
        productName = FileUtils.getFilenameWithoutExtension(inputFile);
      } else {
        productName = "geotiff";
      }
      final String productType = getReaderPlugIn().getFormatNames()[0];

      final int width = imageReader.getWidth(FIRST_IMAGE);
      final int height = imageReader.getHeight(FIRST_IMAGE);
      product = new Product(productName, productType, width, height, this);
      addBandsToProduct(tiffInfo, product);
    }

    if (tiffInfo.isGeotiff()) {
      applyGeoCoding(tiffInfo, imageMetadata, product);
    }

    TiffTagToMetadataConverter.addTiffTagsToMetadata(
        imageMetadata, tiffInfo, product.getMetadataRoot());

    if (inputFile != null) {
      product.setFileLocation(inputFile);
    }
    setPreferredTiling(product);

    return product;
  }