private static void addVector(
      final String name,
      final MetadataElement orbitVectorListElem,
      final MetadataElement orbitElem,
      final int num) {
    final MetadataElement orbitVectorElem = new MetadataElement(name + num);

    final MetadataElement positionElem = orbitElem.getElement("position");
    final MetadataElement velocityElem = orbitElem.getElement("velocity");

    orbitVectorElem.setAttributeUTC(
        AbstractMetadata.orbit_vector_time,
        ReaderUtils.getTime(orbitElem, "time", standardDateFormat));

    orbitVectorElem.setAttributeDouble(
        AbstractMetadata.orbit_vector_x_pos, positionElem.getAttributeDouble("x", 0));
    orbitVectorElem.setAttributeDouble(
        AbstractMetadata.orbit_vector_y_pos, positionElem.getAttributeDouble("y", 0));
    orbitVectorElem.setAttributeDouble(
        AbstractMetadata.orbit_vector_z_pos, positionElem.getAttributeDouble("z", 0));
    orbitVectorElem.setAttributeDouble(
        AbstractMetadata.orbit_vector_x_vel, velocityElem.getAttributeDouble("x", 0));
    orbitVectorElem.setAttributeDouble(
        AbstractMetadata.orbit_vector_y_vel, velocityElem.getAttributeDouble("y", 0));
    orbitVectorElem.setAttributeDouble(
        AbstractMetadata.orbit_vector_z_vel, velocityElem.getAttributeDouble("z", 0));

    orbitVectorListElem.addElement(orbitVectorElem);
  }
  private static void addSRGRCoefficients(
      final MetadataElement absRoot, final MetadataElement coordinateConversion) {
    if (coordinateConversion == null) return;
    final MetadataElement coordinateConversionList =
        coordinateConversion.getElement("coordinateConversionList");
    if (coordinateConversionList == null) return;

    final MetadataElement srgrCoefficientsElem =
        absRoot.getElement(AbstractMetadata.srgr_coefficients);

    int listCnt = 1;
    for (MetadataElement elem : coordinateConversionList.getElements()) {
      final MetadataElement srgrListElem =
          new MetadataElement(AbstractMetadata.srgr_coef_list + '.' + listCnt);
      srgrCoefficientsElem.addElement(srgrListElem);
      ++listCnt;

      final ProductData.UTC utcTime = ReaderUtils.getTime(elem, "azimuthTime", standardDateFormat);
      srgrListElem.setAttributeUTC(AbstractMetadata.srgr_coef_time, utcTime);

      final double grOrigin = elem.getAttributeDouble("gr0", 0);
      AbstractMetadata.addAbstractedAttribute(
          srgrListElem,
          AbstractMetadata.ground_range_origin,
          ProductData.TYPE_FLOAT64,
          "m",
          "Ground Range Origin");
      AbstractMetadata.setAttribute(srgrListElem, AbstractMetadata.ground_range_origin, grOrigin);

      final String coeffStr =
          elem.getElement("grsrCoefficients").getAttributeString("grsrCoefficients", "");
      if (!coeffStr.isEmpty()) {
        final StringTokenizer st = new StringTokenizer(coeffStr);
        int cnt = 1;
        while (st.hasMoreTokens()) {
          final double coefValue = Double.parseDouble(st.nextToken());

          final MetadataElement coefElem =
              new MetadataElement(AbstractMetadata.coefficient + '.' + cnt);
          srgrListElem.addElement(coefElem);
          ++cnt;
          AbstractMetadata.addAbstractedAttribute(
              coefElem,
              AbstractMetadata.srgr_coef,
              ProductData.TYPE_FLOAT64,
              "",
              "SRGR Coefficient");
          AbstractMetadata.setAttribute(coefElem, AbstractMetadata.srgr_coef, coefValue);
        }
      }
    }
  }
  private static void addDopplerCentroidCoefficients(
      final MetadataElement absRoot, final MetadataElement dopplerCentroid) {
    if (dopplerCentroid == null) return;
    final MetadataElement dcEstimateList = dopplerCentroid.getElement("dcEstimateList");
    if (dcEstimateList == null) return;

    final MetadataElement dopplerCentroidCoefficientsElem =
        absRoot.getElement(AbstractMetadata.dop_coefficients);

    int listCnt = 1;
    for (MetadataElement elem : dcEstimateList.getElements()) {
      final MetadataElement dopplerListElem =
          new MetadataElement(AbstractMetadata.dop_coef_list + '.' + listCnt);
      dopplerCentroidCoefficientsElem.addElement(dopplerListElem);
      ++listCnt;

      final ProductData.UTC utcTime = ReaderUtils.getTime(elem, "azimuthTime", standardDateFormat);
      dopplerListElem.setAttributeUTC(AbstractMetadata.dop_coef_time, utcTime);

      final double refTime = elem.getAttributeDouble("t0", 0) * 1e9; // s to ns
      AbstractMetadata.addAbstractedAttribute(
          dopplerListElem,
          AbstractMetadata.slant_range_time,
          ProductData.TYPE_FLOAT64,
          "ns",
          "Slant Range Time");
      AbstractMetadata.setAttribute(dopplerListElem, AbstractMetadata.slant_range_time, refTime);

      final String coeffStr =
          elem.getElement("geometryDcPolynomial").getAttributeString("geometryDcPolynomial", "");
      if (!coeffStr.isEmpty()) {
        final StringTokenizer st = new StringTokenizer(coeffStr);
        int cnt = 1;
        while (st.hasMoreTokens()) {
          final double coefValue = Double.parseDouble(st.nextToken());

          final MetadataElement coefElem =
              new MetadataElement(AbstractMetadata.coefficient + '.' + cnt);
          dopplerListElem.addElement(coefElem);
          ++cnt;
          AbstractMetadata.addAbstractedAttribute(
              coefElem,
              AbstractMetadata.dop_coef,
              ProductData.TYPE_FLOAT64,
              "",
              "Doppler Centroid Coefficient");
          AbstractMetadata.setAttribute(coefElem, AbstractMetadata.dop_coef, coefValue);
        }
      }
    }
  }
  private double getBandTerrainHeight(final MetadataElement prodElem) {
    final MetadataElement generalAnnotation = prodElem.getElement("generalAnnotation");
    final MetadataElement terrainHeightList = generalAnnotation.getElement("terrainHeightList");

    double heightSum = 0.0;

    final MetadataElement[] heightList = terrainHeightList.getElements();
    int cnt = 0;
    for (MetadataElement terrainHeight : heightList) {
      heightSum += terrainHeight.getAttributeDouble("value");
      ++cnt;
    }
    return heightSum / cnt;
  }
  private void addTiePointGrids(final Band band, final String imgXMLName, final String tpgPrefix) {

    // System.out.println("S1L1Dir.addTiePointGrids: band = " + band.getName() + " imgXMLName = " +
    // imgXMLName + " tpgPrefix = " + tpgPrefix);

    final Product product = band.getProduct();
    String pre = "";
    if (!tpgPrefix.isEmpty()) pre = tpgPrefix + '_';

    final TiePointGrid existingLatTPG = product.getTiePointGrid(pre + OperatorUtils.TPG_LATITUDE);
    final TiePointGrid existingLonTPG = product.getTiePointGrid(pre + OperatorUtils.TPG_LONGITUDE);
    if (existingLatTPG != null && existingLonTPG != null) {
      // System.out.println("for band = " + band.getName() + ", use existing TPG");
      // reuse geocoding
      final TiePointGeoCoding tpGeoCoding =
          new TiePointGeoCoding(existingLatTPG, existingLonTPG, Datum.WGS_84);
      band.setGeoCoding(tpGeoCoding);
      return;
    }
    // System.out.println("add new TPG for band = " + band.getName());
    final String annotation = FileUtils.exchangeExtension(imgXMLName, ".xml");
    final MetadataElement origProdRoot = AbstractMetadata.getOriginalProductMetadata(product);
    final MetadataElement annotationElem = origProdRoot.getElement("annotation");
    final MetadataElement imgElem = annotationElem.getElement(annotation);
    final MetadataElement productElem = imgElem.getElement("product");
    final MetadataElement geolocationGrid = productElem.getElement("geolocationGrid");
    final MetadataElement geolocationGridPointList =
        geolocationGrid.getElement("geolocationGridPointList");

    final MetadataElement[] geoGrid = geolocationGridPointList.getElements();

    // System.out.println("geoGrid.length = " + geoGrid.length);

    final double[] latList = new double[geoGrid.length];
    final double[] lngList = new double[geoGrid.length];
    final double[] incidenceAngleList = new double[geoGrid.length];
    final double[] elevAngleList = new double[geoGrid.length];
    final double[] rangeTimeList = new double[geoGrid.length];
    final int[] x = new int[geoGrid.length];
    final int[] y = new int[geoGrid.length];

    // Loop through the list of geolocation grid points, assuming that it represents a row-major
    // rectangular grid.
    int gridWidth = 0, gridHeight = 0;
    int i = 0;
    for (MetadataElement ggPoint : geoGrid) {
      latList[i] = ggPoint.getAttributeDouble("latitude", 0);
      lngList[i] = ggPoint.getAttributeDouble("longitude", 0);
      incidenceAngleList[i] = ggPoint.getAttributeDouble("incidenceAngle", 0);
      elevAngleList[i] = ggPoint.getAttributeDouble("elevationAngle", 0);
      rangeTimeList[i] =
          ggPoint.getAttributeDouble("slantRangeTime", 0) * Constants.oneBillion; // s to ns

      x[i] = (int) ggPoint.getAttributeDouble("pixel", 0);
      y[i] = (int) ggPoint.getAttributeDouble("line", 0);
      if (x[i] == 0) {
        // This means we are at the start of a new line
        if (gridWidth
            == 0) // Here we are implicitly assuming that the pixel horizontal spacing is assumed to
                  // be the same from line to line.
        gridWidth = i;
        ++gridHeight;
      }
      ++i;
    }

    // System.out.println("geoGrid w = " + gridWidth + "; h = " + gridHeight);

    final int newGridWidth = gridWidth;
    final int newGridHeight = gridHeight;
    final float[] newLatList = new float[newGridWidth * newGridHeight];
    final float[] newLonList = new float[newGridWidth * newGridHeight];
    final float[] newIncList = new float[newGridWidth * newGridHeight];
    final float[] newElevList = new float[newGridWidth * newGridHeight];
    final float[] newslrtList = new float[newGridWidth * newGridHeight];
    final int sceneRasterWidth = band.getSceneRasterWidth();
    final int sceneRasterHeight = band.getSceneRasterHeight();
    final double subSamplingX = (double) sceneRasterWidth / (newGridWidth - 1);
    final double subSamplingY = (double) sceneRasterHeight / (newGridHeight - 1);

    getListInEvenlySpacedGrid(
        sceneRasterWidth,
        sceneRasterHeight,
        gridWidth,
        gridHeight,
        x,
        y,
        latList,
        newGridWidth,
        newGridHeight,
        subSamplingX,
        subSamplingY,
        newLatList);

    getListInEvenlySpacedGrid(
        sceneRasterWidth,
        sceneRasterHeight,
        gridWidth,
        gridHeight,
        x,
        y,
        lngList,
        newGridWidth,
        newGridHeight,
        subSamplingX,
        subSamplingY,
        newLonList);

    getListInEvenlySpacedGrid(
        sceneRasterWidth,
        sceneRasterHeight,
        gridWidth,
        gridHeight,
        x,
        y,
        incidenceAngleList,
        newGridWidth,
        newGridHeight,
        subSamplingX,
        subSamplingY,
        newIncList);

    getListInEvenlySpacedGrid(
        sceneRasterWidth,
        sceneRasterHeight,
        gridWidth,
        gridHeight,
        x,
        y,
        elevAngleList,
        newGridWidth,
        newGridHeight,
        subSamplingX,
        subSamplingY,
        newElevList);

    getListInEvenlySpacedGrid(
        sceneRasterWidth,
        sceneRasterHeight,
        gridWidth,
        gridHeight,
        x,
        y,
        rangeTimeList,
        newGridWidth,
        newGridHeight,
        subSamplingX,
        subSamplingY,
        newslrtList);

    TiePointGrid latGrid = product.getTiePointGrid(pre + OperatorUtils.TPG_LATITUDE);
    if (latGrid == null) {
      latGrid =
          new TiePointGrid(
              pre + OperatorUtils.TPG_LATITUDE,
              newGridWidth,
              newGridHeight,
              0.5f,
              0.5f,
              subSamplingX,
              subSamplingY,
              newLatList);
      latGrid.setUnit(Unit.DEGREES);
      product.addTiePointGrid(latGrid);
    }

    TiePointGrid lonGrid = product.getTiePointGrid(pre + OperatorUtils.TPG_LONGITUDE);
    if (lonGrid == null) {
      lonGrid =
          new TiePointGrid(
              pre + OperatorUtils.TPG_LONGITUDE,
              newGridWidth,
              newGridHeight,
              0.5f,
              0.5f,
              subSamplingX,
              subSamplingY,
              newLonList,
              TiePointGrid.DISCONT_AT_180);
      lonGrid.setUnit(Unit.DEGREES);
      product.addTiePointGrid(lonGrid);
    }

    if (product.getTiePointGrid(pre + OperatorUtils.TPG_INCIDENT_ANGLE) == null) {
      final TiePointGrid incidentAngleGrid =
          new TiePointGrid(
              pre + OperatorUtils.TPG_INCIDENT_ANGLE,
              newGridWidth,
              newGridHeight,
              0.5f,
              0.5f,
              subSamplingX,
              subSamplingY,
              newIncList);
      incidentAngleGrid.setUnit(Unit.DEGREES);
      product.addTiePointGrid(incidentAngleGrid);
    }

    if (product.getTiePointGrid(pre + OperatorUtils.TPG_ELEVATION_ANGLE) == null) {
      final TiePointGrid elevAngleGrid =
          new TiePointGrid(
              pre + OperatorUtils.TPG_ELEVATION_ANGLE,
              newGridWidth,
              newGridHeight,
              0.5f,
              0.5f,
              subSamplingX,
              subSamplingY,
              newElevList);
      elevAngleGrid.setUnit(Unit.DEGREES);
      product.addTiePointGrid(elevAngleGrid);
    }

    if (product.getTiePointGrid(pre + OperatorUtils.TPG_SLANT_RANGE_TIME) == null) {
      final TiePointGrid slantRangeGrid =
          new TiePointGrid(
              pre + OperatorUtils.TPG_SLANT_RANGE_TIME,
              newGridWidth,
              newGridHeight,
              0.5f,
              0.5f,
              subSamplingX,
              subSamplingY,
              newslrtList);
      slantRangeGrid.setUnit(Unit.NANOSECONDS);
      product.addTiePointGrid(slantRangeGrid);
    }

    final TiePointGeoCoding tpGeoCoding = new TiePointGeoCoding(latGrid, lonGrid, Datum.WGS_84);
    bandGeocodingMap.put(band, tpGeoCoding);
  }
  private void addBandAbstractedMetadata(
      final MetadataElement absRoot, final MetadataElement origProdRoot) throws IOException {

    MetadataElement annotationElement = origProdRoot.getElement("annotation");
    if (annotationElement == null) {
      annotationElement = new MetadataElement("annotation");
      origProdRoot.addElement(annotationElement);
    }

    // collect range and azimuth spacing
    double rangeSpacingTotal = 0;
    double azimuthSpacingTotal = 0;
    boolean commonMetadataRetrieved = false;

    double heightSum = 0.0;

    int numBands = 0;
    final String annotFolder = getRootFolder() + "annotation";
    final String[] filenames = listFiles(annotFolder);
    if (filenames != null) {
      for (String metadataFile : filenames) {

        final Document xmlDoc =
            XMLSupport.LoadXML(getInputStream(annotFolder + '/' + metadataFile));
        final Element rootElement = xmlDoc.getRootElement();
        final MetadataElement nameElem = new MetadataElement(metadataFile);
        annotationElement.addElement(nameElem);
        AbstractMetadataIO.AddXMLMetadata(rootElement, nameElem);

        final MetadataElement prodElem = nameElem.getElement("product");
        final MetadataElement adsHeader = prodElem.getElement("adsHeader");

        final String swath = adsHeader.getAttributeString("swath");
        final String pol = adsHeader.getAttributeString("polarisation");

        final ProductData.UTC startTime = getTime(adsHeader, "startTime");
        final ProductData.UTC stopTime = getTime(adsHeader, "stopTime");

        final String bandRootName = AbstractMetadata.BAND_PREFIX + swath + '_' + pol;
        final MetadataElement bandAbsRoot =
            AbstractMetadata.addBandAbstractedMetadata(absRoot, bandRootName);
        final String imgName = FileUtils.exchangeExtension(metadataFile, ".tiff");
        imgBandMetadataMap.put(imgName, bandRootName);

        AbstractMetadata.setAttribute(bandAbsRoot, AbstractMetadata.SWATH, swath);
        AbstractMetadata.setAttribute(bandAbsRoot, AbstractMetadata.polarization, pol);
        AbstractMetadata.setAttribute(bandAbsRoot, AbstractMetadata.annotation, metadataFile);
        AbstractMetadata.setAttribute(bandAbsRoot, AbstractMetadata.first_line_time, startTime);
        AbstractMetadata.setAttribute(bandAbsRoot, AbstractMetadata.last_line_time, stopTime);

        if (AbstractMetadata.isNoData(absRoot, AbstractMetadata.mds1_tx_rx_polar)) {
          AbstractMetadata.setAttribute(absRoot, AbstractMetadata.mds1_tx_rx_polar, pol);
        } else {
          AbstractMetadata.setAttribute(absRoot, AbstractMetadata.mds2_tx_rx_polar, pol);
        }

        final MetadataElement imageAnnotation = prodElem.getElement("imageAnnotation");
        final MetadataElement imageInformation = imageAnnotation.getElement("imageInformation");

        AbstractMetadata.setAttribute(
            absRoot,
            AbstractMetadata.data_take_id,
            Integer.parseInt(adsHeader.getAttributeString("missionDataTakeId")));
        AbstractMetadata.setAttribute(
            absRoot,
            AbstractMetadata.slice_num,
            Integer.parseInt(imageInformation.getAttributeString("sliceNumber")));

        rangeSpacingTotal += imageInformation.getAttributeDouble("rangePixelSpacing");
        azimuthSpacingTotal += imageInformation.getAttributeDouble("azimuthPixelSpacing");

        AbstractMetadata.setAttribute(
            bandAbsRoot,
            AbstractMetadata.line_time_interval,
            imageInformation.getAttributeDouble("azimuthTimeInterval"));
        AbstractMetadata.setAttribute(
            bandAbsRoot,
            AbstractMetadata.num_samples_per_line,
            imageInformation.getAttributeInt("numberOfSamples"));
        AbstractMetadata.setAttribute(
            bandAbsRoot,
            AbstractMetadata.num_output_lines,
            imageInformation.getAttributeInt("numberOfLines"));
        AbstractMetadata.setAttribute(
            bandAbsRoot,
            AbstractMetadata.sample_type,
            imageInformation.getAttributeString("pixelValue").toUpperCase());

        heightSum += getBandTerrainHeight(prodElem);

        if (!commonMetadataRetrieved) {
          // these should be the same for all swaths
          // set to absRoot

          final MetadataElement generalAnnotation = prodElem.getElement("generalAnnotation");
          final MetadataElement productInformation =
              generalAnnotation.getElement("productInformation");
          final MetadataElement processingInformation =
              imageAnnotation.getElement("processingInformation");
          final MetadataElement swathProcParamsList =
              processingInformation.getElement("swathProcParamsList");
          final MetadataElement swathProcParams = swathProcParamsList.getElement("swathProcParams");
          final MetadataElement rangeProcessing = swathProcParams.getElement("rangeProcessing");
          final MetadataElement azimuthProcessing = swathProcParams.getElement("azimuthProcessing");

          AbstractMetadata.setAttribute(
              absRoot,
              AbstractMetadata.range_sampling_rate,
              productInformation.getAttributeDouble("rangeSamplingRate") / Constants.oneMillion);
          AbstractMetadata.setAttribute(
              absRoot,
              AbstractMetadata.radar_frequency,
              productInformation.getAttributeDouble("radarFrequency") / Constants.oneMillion);
          AbstractMetadata.setAttribute(
              absRoot,
              AbstractMetadata.line_time_interval,
              imageInformation.getAttributeDouble("azimuthTimeInterval"));

          AbstractMetadata.setAttribute(
              absRoot,
              AbstractMetadata.slant_range_to_first_pixel,
              imageInformation.getAttributeDouble("slantRangeTime") * Constants.halfLightSpeed);

          final MetadataElement downlinkInformationList =
              generalAnnotation.getElement("downlinkInformationList");
          final MetadataElement downlinkInformation =
              downlinkInformationList.getElement("downlinkInformation");

          AbstractMetadata.setAttribute(
              absRoot,
              AbstractMetadata.pulse_repetition_frequency,
              downlinkInformation.getAttributeDouble("prf"));

          AbstractMetadata.setAttribute(
              absRoot,
              AbstractMetadata.range_bandwidth,
              rangeProcessing.getAttributeDouble("processingBandwidth") / Constants.oneMillion);
          AbstractMetadata.setAttribute(
              absRoot,
              AbstractMetadata.azimuth_bandwidth,
              azimuthProcessing.getAttributeDouble("processingBandwidth"));

          AbstractMetadata.setAttribute(
              absRoot,
              AbstractMetadata.range_looks,
              rangeProcessing.getAttributeDouble("numberOfLooks"));
          AbstractMetadata.setAttribute(
              absRoot,
              AbstractMetadata.azimuth_looks,
              azimuthProcessing.getAttributeDouble("numberOfLooks"));

          if (!isTOPSAR() || !isSLC()) {
            AbstractMetadata.setAttribute(
                absRoot,
                AbstractMetadata.num_output_lines,
                imageInformation.getAttributeInt("numberOfLines"));
            AbstractMetadata.setAttribute(
                absRoot,
                AbstractMetadata.num_samples_per_line,
                imageInformation.getAttributeInt("numberOfSamples"));
          }

          addOrbitStateVectors(absRoot, generalAnnotation.getElement("orbitList"));
          addSRGRCoefficients(absRoot, prodElem.getElement("coordinateConversion"));
          addDopplerCentroidCoefficients(absRoot, prodElem.getElement("dopplerCentroid"));

          commonMetadataRetrieved = true;
        }

        ++numBands;
      }
    }

    // set average to absRoot
    AbstractMetadata.setAttribute(
        absRoot, AbstractMetadata.range_spacing, rangeSpacingTotal / (double) numBands);
    AbstractMetadata.setAttribute(
        absRoot, AbstractMetadata.azimuth_spacing, azimuthSpacingTotal / (double) numBands);

    AbstractMetadata.setAttribute(
        absRoot, AbstractMetadata.avg_scene_height, heightSum / filenames.length);
  }