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);
  }
  @Override
  public Product createProduct() throws IOException {

    final MetadataElement newRoot = addMetaData();
    findImages();

    final MetadataElement absRoot = newRoot.getElement(AbstractMetadata.ABSTRACT_METADATA_ROOT);
    determineProductDimensions(absRoot);

    final int sceneWidth = absRoot.getAttributeInt(AbstractMetadata.num_samples_per_line);
    final int sceneHeight = absRoot.getAttributeInt(AbstractMetadata.num_output_lines);

    final Product product =
        new Product(getProductName(), getProductType(), sceneWidth, sceneHeight);
    updateProduct(product, newRoot);

    addBands(product);
    addGeoCoding(product);

    product.setName(getProductName());
    // product.setProductType(getProductType());
    product.setDescription(getProductDescription());

    ReaderUtils.addMetadataProductSize(product);

    return product;
  }
  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 static void addOrbitStateVectors(
      final MetadataElement absRoot, final MetadataElement orbitList) {
    final MetadataElement orbitVectorListElem =
        absRoot.getElement(AbstractMetadata.orbit_state_vectors);

    final MetadataElement[] stateVectorElems = orbitList.getElements();
    for (int i = 1; i <= stateVectorElems.length; ++i) {
      addVector(AbstractMetadata.orbit_vector, orbitVectorListElem, stateVectorElems[i - 1], i);
    }

    // set state vector time
    if (absRoot
        .getAttributeUTC(AbstractMetadata.STATE_VECTOR_TIME, AbstractMetadata.NO_METADATA_UTC)
        .equalElems(AbstractMetadata.NO_METADATA_UTC)) {

      AbstractMetadata.setAttribute(
          absRoot,
          AbstractMetadata.STATE_VECTOR_TIME,
          ReaderUtils.getTime(stateVectorElems[0], "time", standardDateFormat));
    }
  }
  @Override
  protected void addGeoCoding(final Product product) {

    TiePointGrid latGrid = product.getTiePointGrid(OperatorUtils.TPG_LATITUDE);
    TiePointGrid lonGrid = product.getTiePointGrid(OperatorUtils.TPG_LONGITUDE);
    if (latGrid != null && lonGrid != null) {
      setLatLongMetadata(product, latGrid, lonGrid);

      final TiePointGeoCoding tpGeoCoding = new TiePointGeoCoding(latGrid, lonGrid, Datum.WGS_84);
      product.setGeoCoding(tpGeoCoding);
      return;
    }

    final MetadataElement absRoot = AbstractMetadata.getAbstractedMetadata(product);
    final String acquisitionMode = absRoot.getAttributeString(AbstractMetadata.ACQUISITION_MODE);
    int numOfSubSwath;
    switch (acquisitionMode) {
      case "IW":
        numOfSubSwath = 3;
        break;
      case "EW":
        numOfSubSwath = 5;
        break;
      default:
        return;
    }

    String[] bandNames = product.getBandNames();
    Band firstSWBand = null, lastSWBand = null;
    boolean firstSWBandFound = false, lastSWBandFound = false;
    for (String bandName : bandNames) {
      if (!firstSWBandFound && bandName.contains(acquisitionMode + 1)) {
        firstSWBand = product.getBand(bandName);
        firstSWBandFound = true;
      }

      if (!lastSWBandFound && bandName.contains(acquisitionMode + numOfSubSwath)) {
        lastSWBand = product.getBand(bandName);
        lastSWBandFound = true;
      }
    }
    if (firstSWBand == null && lastSWBand == null) return;

    final GeoCoding firstSWBandGeoCoding = bandGeocodingMap.get(firstSWBand);
    final int firstSWBandHeight = firstSWBand.getRasterHeight();

    final GeoCoding lastSWBandGeoCoding = bandGeocodingMap.get(lastSWBand);
    final int lastSWBandWidth = lastSWBand.getRasterWidth();
    final int lastSWBandHeight = lastSWBand.getRasterHeight();

    final PixelPos ulPix = new PixelPos(0, 0);
    final PixelPos llPix = new PixelPos(0, firstSWBandHeight - 1);
    final GeoPos ulGeo = new GeoPos();
    final GeoPos llGeo = new GeoPos();
    firstSWBandGeoCoding.getGeoPos(ulPix, ulGeo);
    firstSWBandGeoCoding.getGeoPos(llPix, llGeo);

    final PixelPos urPix = new PixelPos(lastSWBandWidth - 1, 0);
    final PixelPos lrPix = new PixelPos(lastSWBandWidth - 1, lastSWBandHeight - 1);
    final GeoPos urGeo = new GeoPos();
    final GeoPos lrGeo = new GeoPos();
    lastSWBandGeoCoding.getGeoPos(urPix, urGeo);
    lastSWBandGeoCoding.getGeoPos(lrPix, lrGeo);

    final float[] latCorners = {
      (float) ulGeo.getLat(), (float) urGeo.getLat(), (float) llGeo.getLat(), (float) lrGeo.getLat()
    };
    final float[] lonCorners = {
      (float) ulGeo.getLon(), (float) urGeo.getLon(), (float) llGeo.getLon(), (float) lrGeo.getLon()
    };

    ReaderUtils.addGeoCoding(product, latCorners, lonCorners);

    AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_near_lat, ulGeo.getLat());
    AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_near_long, ulGeo.getLon());
    AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_far_lat, urGeo.getLat());
    AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_far_long, urGeo.getLon());

    AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_near_lat, llGeo.getLat());
    AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_near_long, llGeo.getLon());
    AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_far_lat, lrGeo.getLat());
    AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_far_long, lrGeo.getLon());

    // add band geocoding
    final Band[] bands = product.getBands();
    for (Band band : bands) {
      band.setGeoCoding(bandGeocodingMap.get(band));
    }
  }
  @Override
  protected void addBands(final Product product) {

    String bandName;
    boolean real = true;
    Band lastRealBand = null;
    String unit;

    final MetadataElement absRoot = AbstractMetadata.getAbstractedMetadata(product);
    for (Map.Entry<String, ImageIOFile> stringImageIOFileEntry : bandImageFileMap.entrySet()) {
      final ImageIOFile img = stringImageIOFileEntry.getValue();
      final String imgName = img.getName().toLowerCase();
      final MetadataElement bandMetadata = absRoot.getElement(imgBandMetadataMap.get(imgName));
      final String swath = bandMetadata.getAttributeString(AbstractMetadata.swath);
      final String pol = bandMetadata.getAttributeString(AbstractMetadata.polarization);
      final int width = bandMetadata.getAttributeInt(AbstractMetadata.num_samples_per_line);
      final int height = bandMetadata.getAttributeInt(AbstractMetadata.num_output_lines);

      String tpgPrefix = "";
      String suffix = pol;
      if (isSLC() && isTOPSAR()) {
        suffix = swath + '_' + pol;
        tpgPrefix = swath;
      }

      int numImages = img.getNumImages();
      if (isSLC()) {
        numImages *= 2; // real + imaginary
      }
      for (int i = 0; i < numImages; ++i) {

        if (isSLC()) {
          for (int b = 0; b < img.getNumBands(); ++b) {
            if (real) {
              bandName = "i" + '_' + suffix;
              unit = Unit.REAL;
            } else {
              bandName = "q" + '_' + suffix;
              unit = Unit.IMAGINARY;
            }

            final Band band = new Band(bandName, ProductData.TYPE_INT16, width, height);
            band.setUnit(unit);

            product.addBand(band);
            bandMap.put(band, new ImageIOFile.BandInfo(band, img, i, b));
            AbstractMetadata.addBandToBandMap(bandMetadata, bandName);

            if (real) {
              lastRealBand = band;
            } else {
              ReaderUtils.createVirtualIntensityBand(product, lastRealBand, band, '_' + suffix);
            }
            real = !real;

            // add tiepointgrids and geocoding for band
            addTiePointGrids(band, imgName, tpgPrefix);

            // reset to null so it doesn't adopt a geocoding from the bands
            product.setGeoCoding(null);
          }
        } else {
          for (int b = 0; b < img.getNumBands(); ++b) {
            bandName = "Amplitude" + '_' + suffix;
            final Band band = new Band(bandName, ProductData.TYPE_INT32, width, height);
            band.setUnit(Unit.AMPLITUDE);

            product.addBand(band);
            bandMap.put(band, new ImageIOFile.BandInfo(band, img, i, b));
            AbstractMetadata.addBandToBandMap(bandMetadata, bandName);

            SARReader.createVirtualIntensityBand(product, band, '_' + suffix);

            // add tiepointgrids and geocoding for band
            addTiePointGrids(band, imgName, tpgPrefix);
          }
        }
      }
    }
  }