org.esa.nest.dataio.ceos.basic.BasicCeosProductDirectory.java Source code

Java tutorial

Introduction

Here is the source code for org.esa.nest.dataio.ceos.basic.BasicCeosProductDirectory.java

Source

/*
 * Copyright (C) 2014 by Array Systems Computing Inc. http://www.array.ca
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 3 of the License, or (at your option)
 * any later version.
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, see http://www.gnu.org/licenses/
 */
package org.esa.nest.dataio.ceos.basic;

import Jama.Matrix;
import org.apache.commons.math3.util.FastMath;
import org.esa.beam.framework.datamodel.*;
import org.esa.beam.framework.dataop.maptransf.Datum;
import org.esa.beam.util.Debug;
import org.esa.beam.util.Guardian;
import org.esa.beam.visat.VisatApp;
import org.esa.nest.dataio.SARReader;
import org.esa.nest.dataio.binary.BinaryRecord;
import org.esa.nest.dataio.binary.IllegalBinaryFormatException;
import org.esa.nest.dataio.ceos.CEOSImageFile;
import org.esa.nest.dataio.ceos.CEOSProductDirectory;
import org.esa.nest.dataio.ceos.CeosHelper;
import org.esa.snap.datamodel.AbstractMetadata;
import org.esa.snap.datamodel.OrbitStateVector;
import org.esa.snap.datamodel.Orbits;
import org.esa.snap.datamodel.Unit;
import org.esa.snap.eo.Constants;
import org.esa.snap.eo.GeoUtils;
import org.esa.snap.gpf.OperatorUtils;
import org.esa.snap.gpf.ReaderUtils;
import org.esa.snap.util.Maths;

import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This class represents a product directory.
 */
class BasicCeosProductDirectory extends CEOSProductDirectory {

    private BasicCeosImageFile[] imageFiles = null;
    private BasicCeosLeaderFile leaderFile = null;
    private BasicCeosTrailerFile trailerFile = null;

    private final transient Map<String, BasicCeosImageFile> bandImageFileMap = new HashMap<>(1);

    public BasicCeosProductDirectory(final File dir) {
        Guardian.assertNotNull("dir", dir);

        constants = new BasicCeosConstants();
        baseDir = dir;
    }

    @Override
    protected void readProductDirectory() throws IOException, IllegalBinaryFormatException {
        readVolumeDirectoryFile();

        leaderFile = new BasicCeosLeaderFile(
                createInputStream(CeosHelper.getCEOSFile(baseDir, constants.getLeaderFilePrefix())));
        final File trlFile = CeosHelper.getCEOSFile(baseDir, constants.getTrailerFilePrefix());
        if (trlFile != null) {
            trailerFile = new BasicCeosTrailerFile(createInputStream(trlFile));
        }

        BinaryRecord histogramRec = leaderFile.getHistogramRecord();
        if (histogramRec == null)
            histogramRec = trailerFile.getHistogramRecord();

        final String[] imageFileNames = CEOSImageFile.getImageFileNames(baseDir, constants.getImageFilePrefix());
        final List<BasicCeosImageFile> imgArray = new ArrayList<>(imageFileNames.length);
        for (String fileName : imageFileNames) {
            try {
                final BasicCeosImageFile imgFile = new BasicCeosImageFile(
                        createInputStream(new File(baseDir, fileName)), histogramRec);
                imgArray.add(imgFile);
            } catch (Exception e) {
                e.printStackTrace();
                // continue
            }
        }
        imageFiles = imgArray.toArray(new BasicCeosImageFile[imgArray.size()]);

        sceneWidth = imageFiles[0].getRasterWidth();
        sceneHeight = imageFiles[0].getRasterHeight();
        assertSameWidthAndHeightForAllImages(imageFiles, sceneWidth, sceneHeight);
    }

    @Override
    public Product createProduct() throws IOException {
        assert (productType != null);
        productType = extractProductType(productType);

        final Product product = new Product(getProductName(), productType, sceneWidth, sceneHeight);

        if (imageFiles.length > 1) {
            int index = 1;
            for (final BasicCeosImageFile imageFile : imageFiles) {

                if (isProductSLC) {
                    final Band bandI = createBand(product, "i_" + index, Unit.REAL, imageFile);
                    final Band bandQ = createBand(product, "q_" + index, Unit.IMAGINARY, imageFile);
                    ReaderUtils.createVirtualIntensityBand(product, bandI, bandQ, "_" + index);
                } else {
                    final Band band = createBand(product, "Amplitude_" + index, Unit.AMPLITUDE, imageFile);
                    SARReader.createVirtualIntensityBand(product, band, "_" + index);
                }
                ++index;
            }
        } else {
            final BasicCeosImageFile imageFile = imageFiles[0];
            if (isProductSLC) {
                final Band bandI = createBand(product, "i", Unit.REAL, imageFile);
                final Band bandQ = createBand(product, "q", Unit.IMAGINARY, imageFile);
                ReaderUtils.createVirtualIntensityBand(product, bandI, bandQ, "");
            } else {
                final Band band = createBand(product, "Amplitude", Unit.AMPLITUDE, imageFile);
                SARReader.createVirtualIntensityBand(product, band, "");
            }
        }

        BinaryRecord facilityRec = leaderFile.getFacilityRecord();
        if (facilityRec == null)
            facilityRec = trailerFile.getFacilityRecord();
        BinaryRecord sceneRec = leaderFile.getSceneRecord();
        if (sceneRec == null)
            sceneRec = trailerFile.getSceneRecord();
        BinaryRecord detProcRec = leaderFile.getDetailedProcessingRecord();
        if (detProcRec == null)
            detProcRec = trailerFile.getDetailedProcessingRecord();
        BinaryRecord mapProjRec = leaderFile.getMapProjRecord();
        if (mapProjRec == null)
            mapProjRec = trailerFile.getMapProjRecord();

        product.setStartTime(getUTCScanStartTime(sceneRec, detProcRec));
        product.setEndTime(getUTCScanStopTime(sceneRec, detProcRec));
        product.setDescription(getProductDescription());

        addMetaData(product);

        // set slant_range_to_first_pixel in metadata
        final MetadataElement absRoot = AbstractMetadata.getAbstractedMetadata(product);
        final TiePointGrid slantRangeTimeTPG = product.getTiePointGrid(OperatorUtils.TPG_SLANT_RANGE_TIME);
        if (slantRangeTimeTPG != null) {
            final int numOutputLines = absRoot.getAttributeInt(AbstractMetadata.num_output_lines);
            final double slantRangeTime = slantRangeTimeTPG.getPixelDouble(numOutputLines / 2, 0)
                    / Constants.oneBillion; //s
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.slant_range_to_first_pixel,
                    slantRangeTime * Constants.halfLightSpeed);
        }

        float[] latCorners = leaderFile.getLatCorners(leaderFile.getMapProjRecord());
        float[] lonCorners = leaderFile.getLonCorners(leaderFile.getMapProjRecord());
        if (latCorners == null || lonCorners == null) {
            latCorners = imageFiles[0].getLatCorners();
            lonCorners = imageFiles[0].getLonCorners();
        }
        if (latCorners != null && lonCorners != null) {
            ReaderUtils.addGeoCoding(product, latCorners, lonCorners);
        }

        if (product.getGeoCoding() == null) {
            addGeoCodingFromSceneLabel(product);
        }

        if (product.getGeoCoding() == null) {
            addTPGGeoCoding(product, sceneRec);
        }

        if (mapProjRec == null) {
            setLatLonMetadata(product, absRoot);
        }

        if (VisatApp.getApp() != null) {
            VisatApp.getApp()
                    .showWarningDialog("This product is for an unknown mission in CEOS format.\n"
                            + "Some functionality may not be supported.\n"
                            + "Please contact nest_pr@array.ca for further support.");
        }

        return product;
    }

    private static String extractProductType(final String productType) {

        if (productType.contains("SLC"))
            return "SLC";
        else if (productType.contains("SGF"))
            return "SGF";
        else if (productType.contains("SGX"))
            return "SGX";
        else if (productType.contains("SSG"))
            return "SSG";
        else if (productType.contains("SCN"))
            return "SCN";
        else if (productType.contains("SCW"))
            return "SCW";
        else if (productType.trim().isEmpty())
            return "unknown";
        return productType;
    }

    public boolean isCeos() throws IOException {
        return true;
    }

    @Override
    public CEOSImageFile getImageFile(final Band band) {
        return bandImageFileMap.get(band.getName());
    }

    @Override
    public void close() throws IOException {
        for (int i = 0; i < imageFiles.length; i++) {
            imageFiles[i].close();
            imageFiles[i] = null;
        }
        imageFiles = null;
    }

    private Band createBand(final Product product, final String name, final String unit,
            final BasicCeosImageFile imageFile) {

        final Band band = createBand(product, name, unit, imageFile.getBitsPerSample());
        bandImageFileMap.put(name, imageFile);

        return band;
    }

    private void addMetaData(final Product product) throws IOException {

        final MetadataElement root = AbstractMetadata.addOriginalProductMetadata(product.getMetadataRoot());

        final MetadataElement leadMetadata = new MetadataElement("Leader");
        leaderFile.addMetadata(leadMetadata);
        root.addElement(leadMetadata);

        final MetadataElement trailMetadata = new MetadataElement("Trailer");
        trailerFile.addMetadata(trailMetadata);
        root.addElement(trailMetadata);

        final MetadataElement volMetadata = new MetadataElement("Volume");
        volumeDirectoryFile.assignMetadataTo(volMetadata);
        root.addElement(volMetadata);

        int c = 1;
        for (final BasicCeosImageFile imageFile : imageFiles) {
            imageFile.assignMetadataTo(root, c++);
        }

        addSummaryMetadata(new File(baseDir, BasicCeosConstants.SUMMARY_FILE_NAME), "Summary Information", root);
        addSummaryMetadata(new File(baseDir, BasicCeosConstants.SCENE_LABEL_FILE_NAME), "Scene Label", root);
        addSummaryMetadata(new File(baseDir.getParentFile(), BasicCeosConstants.SCENE_LABEL_FILE_NAME),
                "Scene Label", root);

        // try txt summary file
        // removed because it is not in the property name value format
        //final File volFile = CeosHelper.getCEOSFile(baseDir, constants.getVolumeFilePrefix());
        //final File txtFile = FileUtils.exchangeExtension(volFile, ".txt");
        //addSummaryMetadata(txtFile, "Scene Description", root);

        addAbstractedMetadataHeader(product, product.getMetadataRoot());
    }

    private void addAbstractedMetadataHeader(Product product, MetadataElement root) {

        final MetadataElement absRoot = AbstractMetadata.addAbstractedMetadataHeader(root);

        BinaryRecord mapProjRec = leaderFile.getMapProjRecord();
        if (mapProjRec == null)
            mapProjRec = trailerFile.getMapProjRecord();
        BinaryRecord sceneRec = leaderFile.getSceneRecord();
        if (sceneRec == null)
            sceneRec = trailerFile.getSceneRecord();
        final BinaryRecord radiometricRec = leaderFile.getRadiometricRecord();
        BinaryRecord facilityRec = leaderFile.getFacilityRecord();
        if (facilityRec == null)
            facilityRec = trailerFile.getFacilityRecord();
        BinaryRecord detProcRec = leaderFile.getDetailedProcessingRecord();
        if (detProcRec == null)
            detProcRec = trailerFile.getDetailedProcessingRecord();

        //mph
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.PRODUCT, getProductName());
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.PRODUCT_TYPE, getProductType());
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.SPH_DESCRIPTOR,
                sceneRec.getAttributeString("Product type descriptor"));
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.MISSION, "RS1");

        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.PROC_TIME,
                getProcTime(volumeDirectoryFile.getVolumeDescriptorRecord()));

        final ProductData.UTC startTime = getUTCScanStartTime(sceneRec, detProcRec);
        final ProductData.UTC endTime = getUTCScanStopTime(sceneRec, detProcRec);
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_line_time, startTime);
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_line_time, endTime);

        if (mapProjRec != null) {
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_near_lat,
                    mapProjRec.getAttributeDouble("1st line 1st pixel geodetic latitude"));
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_near_long,
                    mapProjRec.getAttributeDouble("1st line 1st pixel geodetic longitude"));

            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_far_lat,
                    mapProjRec.getAttributeDouble("1st line last valid pixel geodetic latitude"));
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_far_long,
                    mapProjRec.getAttributeDouble("1st line last valid pixel geodetic longitude"));

            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_near_lat,
                    mapProjRec.getAttributeDouble("Last line 1st pixel geodetic latitude"));
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_near_long,
                    mapProjRec.getAttributeDouble("Last line 1st pixel geodetic longitude"));
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_far_lat,
                    mapProjRec.getAttributeDouble("Last line last valid pixel geodetic latitude"));
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_far_long,
                    mapProjRec.getAttributeDouble("Last line last valid pixel geodetic longitude"));

            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.PASS, getPass(mapProjRec, sceneRec));
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.range_spacing,
                    mapProjRec.getAttributeDouble("Nominal inter-pixel distance in output scene"));
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.azimuth_spacing,
                    mapProjRec.getAttributeDouble("Nominal inter-line distance in output scene"));

            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.srgr_flag, isGroundRange(mapProjRec));
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.map_projection, getMapProjection(mapProjRec));
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.geo_ref_system,
                    mapProjRec.getAttributeString("Name of reference ellipsoid"));

        } else if (sceneRec != null) {
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.range_spacing,
                    sceneRec.getAttributeDouble("Pixel spacing"));
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.azimuth_spacing,
                    sceneRec.getAttributeDouble("Line spacing"));
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.PASS, getPass(mapProjRec, sceneRec));
        }

        //sph
        if (sceneRec != null) {
            final String absOrbit = sceneRec.getAttributeString("Orbit number").trim();
            if (absOrbit != null && !absOrbit.isEmpty()) {
                AbstractMetadata.setAttribute(absRoot, AbstractMetadata.ABS_ORBIT, Integer.parseInt(absOrbit));
            }
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.mds1_tx_rx_polar,
                    SARReader.findPolarizationInBandName(
                            sceneRec.getAttributeString("Sensor ID and mode of operation for this channel")));

            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.algorithm,
                    sceneRec.getAttributeString("Processing algorithm identifier"));
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.azimuth_looks,
                    sceneRec.getAttributeDouble("Nominal number of looks processed in azimuth"));
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.range_looks,
                    sceneRec.getAttributeDouble("Nominal number of looks processed in range"));
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.pulse_repetition_frequency,
                    sceneRec.getAttributeDouble("Pulse Repetition Frequency"));
            double radarFreq = sceneRec.getAttributeDouble("Radar frequency");
            if (Double.compare(radarFreq, 0.0) == 0) {
                final double radarWaveLength = sceneRec.getAttributeDouble("Radar wavelength"); // in m
                if (Double.compare(radarWaveLength, 0.0) != 0) {
                    radarFreq = Constants.lightSpeed / radarWaveLength / Constants.oneMillion; // in MHz
                }
            }
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.radar_frequency, radarFreq);

            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.range_sampling_rate,
                    sceneRec.getAttributeDouble("Range sampling rate"));

            // add Range and Azimuth bandwidth
            final double rangeBW = sceneRec.getAttributeDouble("Total processor bandwidth in range"); // Hz
            final double azimuthBW = sceneRec.getAttributeDouble("Total processor bandwidth in azimuth"); // Hz

            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.range_bandwidth,
                    rangeBW / Constants.oneMillion);
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.azimuth_bandwidth, azimuthBW);
        }

        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.SAMPLE_TYPE, getSampleType());
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.line_time_interval,
                ReaderUtils.getLineTimeInterval(startTime, endTime, sceneHeight));
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.num_output_lines, product.getSceneRasterHeight());
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.num_samples_per_line,
                product.getSceneRasterWidth());
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.TOT_SIZE, ReaderUtils.getTotalSize(product));

        if (facilityRec != null) {
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.STATE_VECTOR_TIME, AbstractMetadata.parseUTC(
                    facilityRec.getAttributeString("Time of input state vector used to processed the image")));

            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.ant_elev_corr_flag,
                    facilityRec.getAttributeInt("Antenna pattern correction flag"));
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.range_spread_comp_flag,
                    facilityRec.getAttributeInt("Range spreading loss compensation flag"));
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.replica_power_corr_flag, 0);
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.abs_calibration_flag, 0);
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.coregistered_stack, 0);
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.calibration_factor,
                    facilityRec.getAttributeDouble("Absolute calibration constant K"));
        }

        if (radiometricRec != null) {
            AbstractMetadata.setAttribute(absRoot, AbstractMetadata.calibration_factor,
                    radiometricRec.getAttributeDouble("Calibration constant"));
        }

        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.replica_power_corr_flag, 0);
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.abs_calibration_flag, 0);

        addOrbitStateVectors(absRoot, leaderFile.getPlatformPositionRecord());
        if (facilityRec != null)
            addSRGRCoefficients(absRoot, facilityRec);
        else
            addSRGRCoefficients(absRoot, detProcRec);
    }

    private String getMapProjection(final BinaryRecord mapProjRec) {
        if (productType.contains("IMG") || productType.contains("GEC") || productType.contains("SSG")) {
            return mapProjRec.getAttributeString("Map projection descriptor");
        }
        return " ";
    }

    private String getProductName() {
        return volumeDirectoryFile.getProductName();
    }

    private String getProductDescription() {
        BinaryRecord sceneRecord = leaderFile.getSceneRecord();
        if (sceneRecord == null)
            sceneRecord = trailerFile.getSceneRecord();

        String level = "";
        if (sceneRecord != null) {
            level = sceneRecord.getAttributeString("Scene reference number").trim();
        }
        return BasicCeosConstants.PRODUCT_DESCRIPTION_PREFIX + level;
    }

    private static void addGeoCodingFromSceneLabel(Product product) {

        final MetadataElement sceneLabelElem = AbstractMetadata.getOriginalProductMetadata(product)
                .getElement("Scene Label");
        if (sceneLabelElem != null) {

            try {
                final String ulLatLon = sceneLabelElem.getAttributeString("UL_CORNER_LAT_LON");
                final String urLatLon = sceneLabelElem.getAttributeString("UR_CORNER_LAT_LON");
                final String llLatLon = sceneLabelElem.getAttributeString("LL_CORNER_LAT_LON");
                final String lrLatLon = sceneLabelElem.getAttributeString("LR_CORNER_LAT_LON");

                final float latUL = Float.parseFloat(ulLatLon.substring(0, ulLatLon.indexOf(',')));
                final float latUR = Float.parseFloat(urLatLon.substring(0, urLatLon.indexOf(',')));
                final float latLL = Float.parseFloat(llLatLon.substring(0, llLatLon.indexOf(',')));
                final float latLR = Float.parseFloat(lrLatLon.substring(0, lrLatLon.indexOf(',')));
                final float[] latCorners = new float[] { latUL, latUR, latLL, latLR };

                final float lonUL = Float
                        .parseFloat(ulLatLon.substring(ulLatLon.indexOf(',') + 1, ulLatLon.length() - 1));
                final float lonUR = Float
                        .parseFloat(urLatLon.substring(urLatLon.indexOf(',') + 1, urLatLon.length() - 1));
                final float lonLL = Float
                        .parseFloat(llLatLon.substring(llLatLon.indexOf(',') + 1, llLatLon.length() - 1));
                final float lonLR = Float
                        .parseFloat(lrLatLon.substring(lrLatLon.indexOf(',') + 1, lrLatLon.length() - 1));
                final float[] lonCorners = new float[] { lonUL, lonUR, lonLL, lonLR };

                ReaderUtils.addGeoCoding(product, latCorners, lonCorners);
            } catch (Exception e) {
                Debug.trace(e.toString());
            }
        }
    }

    protected static void addOrbitStateVectors(final MetadataElement absRoot, final BinaryRecord platformPosRec) {
        if (platformPosRec == null)
            return;

        try {
            final MetadataElement orbitVectorListElem = absRoot.getElement(AbstractMetadata.orbit_state_vectors);
            final int numPoints = platformPosRec.getAttributeInt("Number of data points");
            final double theta = platformPosRec.getAttributeDouble("Greenwich mean hour angle");
            /*
            final double firstLineUTC = absRoot.getAttributeUTC(AbstractMetadata.first_line_time).getMJD();
            final double lastLineUTC = absRoot.getAttributeUTC(AbstractMetadata.last_line_time).getMJD();
            int startIdx = 0;
            int endIdx = 0;
            for (int i = 1; i <= numPoints; i++) {
            double time = getOrbitTime(platformPosRec, i).getMJD();
            if (time < firstLineUTC) {
                startIdx = i;
            }
                
            if (time < lastLineUTC) {
                endIdx = i;
            }
            }
            startIdx = Math.max(startIdx - 1, 1);
            endIdx = Math.min(endIdx+1, numPoints);
            */
            for (int i = 1; i <= numPoints; ++i) {
                addVector(AbstractMetadata.orbit_vector, orbitVectorListElem, platformPosRec, theta, i);
            }

            if (absRoot.getAttributeUTC(AbstractMetadata.STATE_VECTOR_TIME, AbstractMetadata.NO_METADATA_UTC)
                    .equalElems(AbstractMetadata.NO_METADATA_UTC)) {

                AbstractMetadata.setAttribute(absRoot, AbstractMetadata.STATE_VECTOR_TIME,
                        getOrbitTime(platformPosRec, 1));
            }
        } catch (Exception e) {
            // continue without state vectors
        }
    }

    private static void addVector(String name, MetadataElement orbitVectorListElem, BinaryRecord platformPosRec,
            double theta, int num) {

        final MetadataElement orbitVectorElem = new MetadataElement(name + num);

        final double xPosECI = platformPosRec.getAttributeDouble("Position vector X " + num);
        final double yPosECI = platformPosRec.getAttributeDouble("Position vector Y " + num);
        final double zPosECI = platformPosRec.getAttributeDouble("Position vector Z " + num);

        final double xVelECI = platformPosRec.getAttributeDouble("Velocity vector X' " + num) / 1000.0; // mm to m
        final double yVelECI = platformPosRec.getAttributeDouble("Velocity vector Y' " + num) / 1000.0;
        final double zVelECI = platformPosRec.getAttributeDouble("Velocity vector Z' " + num) / 1000.0;

        final double thetaInRd = theta * Constants.DTOR;
        final double cosTheta = FastMath.cos(thetaInRd);
        final double sinTheta = FastMath.sin(thetaInRd);

        final double xPosECEF = cosTheta * xPosECI + sinTheta * yPosECI;
        final double yPosECEF = -sinTheta * xPosECI + cosTheta * yPosECI;
        final double zPosECEF = zPosECI;

        double t = getOrbitTime(platformPosRec, num).getMJD() / 36525.0; // Julian centuries
        double a1 = 876600.0 * 3600.0 + 8640184.812866;
        double a2 = 0.093104;
        double a3 = -6.2e-6;
        double thp = ((a1 + 2.0 * a2 * t + 3.0 * a3 * t * t) / 240.0 * Constants.PI / 180.0)
                / (36525.0 * Constants.secondsInDay);

        final double xVelECEF = -sinTheta * thp * xPosECI + cosTheta * thp * yPosECI + cosTheta * xVelECI
                + sinTheta * yVelECI;
        final double yVelECEF = -cosTheta * thp * xPosECI - sinTheta * thp * yPosECI - sinTheta * xVelECI
                + cosTheta * yVelECI;
        final double zVelECEF = zVelECI;

        orbitVectorElem.setAttributeUTC(AbstractMetadata.orbit_vector_time, getOrbitTime(platformPosRec, num));
        orbitVectorElem.setAttributeDouble(AbstractMetadata.orbit_vector_x_pos, xPosECEF);
        orbitVectorElem.setAttributeDouble(AbstractMetadata.orbit_vector_y_pos, yPosECEF);
        orbitVectorElem.setAttributeDouble(AbstractMetadata.orbit_vector_z_pos, zPosECEF);
        orbitVectorElem.setAttributeDouble(AbstractMetadata.orbit_vector_x_vel, xVelECEF);
        orbitVectorElem.setAttributeDouble(AbstractMetadata.orbit_vector_y_vel, yVelECEF);
        orbitVectorElem.setAttributeDouble(AbstractMetadata.orbit_vector_z_vel, zVelECEF);

        orbitVectorListElem.addElement(orbitVectorElem);
    }

    protected static void addSRGRCoefficients(final MetadataElement absRoot, final BinaryRecord detailedProcRec) {
        if (detailedProcRec == null)
            return;

        final MetadataElement srgrCoefficientsElem = absRoot.getElement(AbstractMetadata.srgr_coefficients);
        final Integer numSRGRCoefSets = detailedProcRec.getAttributeInt("Number of SRGR coefficient sets");
        if (numSRGRCoefSets == null)
            return;

        final DateFormat dateFormat = ProductData.UTC.createDateFormat("yyyy-DDD-HH:mm:ss");

        for (int i = 1; i <= numSRGRCoefSets; ++i) {

            final MetadataElement srgrListElem = new MetadataElement(AbstractMetadata.srgr_coef_list + "." + i);
            srgrCoefficientsElem.addElement(srgrListElem);

            final String updateTimeStr = detailedProcRec.getAttributeString("SRGR update date/time " + i);
            final ProductData.UTC utcTime = AbstractMetadata.parseUTC(updateTimeStr, dateFormat);
            srgrListElem.setAttributeUTC(AbstractMetadata.srgr_coef_time, utcTime);
            AbstractMetadata.addAbstractedAttribute(srgrListElem, AbstractMetadata.ground_range_origin,
                    ProductData.TYPE_FLOAT64, "m", "Ground Range Origin");
            AbstractMetadata.setAttribute(srgrListElem, AbstractMetadata.ground_range_origin, 0.0);

            addSRGRCoef(srgrListElem, detailedProcRec, "SRGR coefficients1 " + i, 1);
            addSRGRCoef(srgrListElem, detailedProcRec, "SRGR coefficients2 " + i, 2);
            addSRGRCoef(srgrListElem, detailedProcRec, "SRGR coefficients3 " + i, 3);
            addSRGRCoef(srgrListElem, detailedProcRec, "SRGR coefficients4 " + i, 4);
            addSRGRCoef(srgrListElem, detailedProcRec, "SRGR coefficients5 " + i, 5);
            addSRGRCoef(srgrListElem, detailedProcRec, "SRGR coefficients6 " + i, 6);
        }
    }

    private void addRSATTiePointGrids(final Product product, final BinaryRecord sceneRec,
            final BinaryRecord detProcRec) {

        final int gridWidth = 11;
        final int gridHeight = 11;
        final int sceneWidth = product.getSceneRasterWidth();
        final int sceneHeight = product.getSceneRasterHeight();
        final int subSamplingX = sceneWidth / (gridWidth - 1);
        final int subSamplingY = sceneHeight / (gridHeight - 1);
        final float[] rangeDist = new float[gridWidth * gridHeight];
        final float[] rangeTime = new float[gridWidth * gridHeight];

        int k = 0;
        for (int j = 0; j < gridHeight; j++) {
            final int y = Math.min(j * subSamplingY, sceneHeight - 1);
            final double slantRangeToFirstPixel = imageFiles[0].getSlantRangeToFirstPixel(y); // meters
            final double slantRangeToMidPixel = imageFiles[0].getSlantRangeToMidPixel(y);
            final double slantRangeToLastPixel = imageFiles[0].getSlantRangeToLastPixel(y);
            final double[] polyCoef = computePolynomialCoefficients(slantRangeToFirstPixel, slantRangeToMidPixel,
                    slantRangeToLastPixel, sceneWidth);

            for (int i = 0; i < gridWidth; i++) {
                final int x = i * subSamplingX;
                rangeDist[k++] = (float) (polyCoef[0] + polyCoef[1] * x + polyCoef[2] * x * x);
            }
        }

        // get slant range time in nanoseconds from range distance in meters
        for (k = 0; k < rangeDist.length; k++) {
            rangeTime[k] = (float) ((rangeDist[k] / Constants.halfLightSpeed) * Constants.oneBillion);// in ns
        }

        final TiePointGrid slantRangeGrid = new TiePointGrid(OperatorUtils.TPG_SLANT_RANGE_TIME, gridWidth,
                gridHeight, 0, 0, subSamplingX, subSamplingY, rangeTime);

        slantRangeGrid.setUnit(Unit.NANOSECONDS);
        product.addTiePointGrid(slantRangeGrid);

        if (detProcRec == null)
            return;

        final double r = calculateEarthRadius(sceneRec); // earth radius
        final double eph_orb_data = detProcRec.getAttributeDouble("Ephemeris orbit data1");
        final double h = eph_orb_data - r; // orbital altitude

        // incidence angle
        final float[] angles = new float[gridWidth * gridHeight];

        k = 0;
        for (int j = 0; j < gridHeight; j++) {
            for (int i = 0; i < gridWidth; i++) {
                final double RS = rangeDist[k];
                final double a = ((h * h) - (RS * RS) + (2.0 * r * h)) / (2.0 * RS * r);
                angles[k] = (float) (FastMath.acos(a) * Constants.RTOD);
                k++;
            }
        }

        final TiePointGrid incidentAngleGrid = new TiePointGrid(OperatorUtils.TPG_INCIDENT_ANGLE, gridWidth,
                gridHeight, 0, 0, subSamplingX, subSamplingY, angles);

        incidentAngleGrid.setUnit(Unit.DEGREES);
        product.addTiePointGrid(incidentAngleGrid);
    }

    private static double[] computePolynomialCoefficients(double slantRangeToFirstPixel,
            double slantRangeToMidPixel, double slantRangeToLastPixel, int imageWidth) {

        final int firstPixel = 0;
        final int midPixel = imageWidth / 2;
        final int lastPixel = imageWidth - 1;
        final double[] idxArray = { firstPixel, midPixel, lastPixel };
        final double[] rangeArray = { slantRangeToFirstPixel, slantRangeToMidPixel, slantRangeToLastPixel };
        final Matrix A = Maths.createVandermondeMatrix(idxArray, 2);
        final Matrix b = new Matrix(rangeArray, 3);
        final Matrix x = A.solve(b);
        return x.getColumnPackedCopy();
    }

    private static double calculateEarthRadius(BinaryRecord sceneRec) {

        final double platLat = sceneRec.getAttributeDouble("Sensor platform geodetic latitude at nadir");
        final double a = FastMath.tan(platLat * Constants.DTOR);
        final double a2 = a * a;
        final double ellipmin = Constants.semiMinorAxis;
        final double ellipmin2 = ellipmin * ellipmin;
        final double ellipmaj = Constants.semiMajorAxis;
        final double ellipmaj2 = ellipmaj * ellipmaj;

        return Constants.semiMinorAxis * (Math.sqrt(1 + a2) / Math.sqrt((ellipmin2 / ellipmaj2) + a2));
    }

    /**
     * Update target product GEOCoding. A new tie point grid is generated.
     *
     * @param product  The product.
     * @param sceneRec The scene record.
     * @throws java.io.IOException The exceptions.
     */
    private static void addTPGGeoCoding(final Product product, final BinaryRecord sceneRec) throws IOException {

        final int gridWidth = 11;
        final int gridHeight = 11;
        final float[] targetLatTiePoints = new float[gridWidth * gridHeight];
        final float[] targetLonTiePoints = new float[gridWidth * gridHeight];
        final int sourceImageWidth = product.getSceneRasterWidth();
        final int sourceImageHeight = product.getSceneRasterHeight();

        final double subSamplingX = sourceImageWidth / (double) (gridWidth - 1);
        final double subSamplingY = sourceImageHeight / (double) (gridHeight - 1);

        final TiePointGrid slantRangeTime = product.getTiePointGrid(OperatorUtils.TPG_SLANT_RANGE_TIME);
        final MetadataElement absRoot = AbstractMetadata.getAbstractedMetadata(product);
        final double firstLineUTC = absRoot.getAttributeUTC(AbstractMetadata.first_line_time).getMJD();
        final double lastLineUTC = absRoot.getAttributeUTC(AbstractMetadata.last_line_time).getMJD();
        final double lineTimeInterval = absRoot.getAttributeDouble(AbstractMetadata.line_time_interval)
                / Constants.secondsInDay; // s to day

        final double latMid = sceneRec.getAttributeDouble("scene centre geodetic latitude");
        final double lonMid = sceneRec.getAttributeDouble("scene centre geodetic longitude");

        OrbitStateVector[] orbitStateVectors;
        try {
            orbitStateVectors = AbstractMetadata.getOrbitStateVectors(absRoot);
        } catch (Exception e) {
            throw new IOException(e.getMessage());
        }

        if (!checkStateVectorValidity(orbitStateVectors))
            return;

        final int numVectors = orbitStateVectors.length;
        int startIdx = 0;
        int endIdx = 0;
        final double t1 = Math.min(firstLineUTC, lastLineUTC);
        final double t2 = Math.max(firstLineUTC, lastLineUTC);
        for (int i = 0; i < numVectors; i++) {
            double time = orbitStateVectors[i].time_mjd;
            if (time < t1) {
                startIdx = i;
            }

            if (time < t2) {
                endIdx = i;
            }
        }

        while (endIdx - startIdx + 1 < Math.min(5, numVectors)) {
            startIdx = Math.max(startIdx - 1, 0);
            endIdx = Math.min(endIdx + 1, numVectors - 1);
        }
        final int numVectorsUsed = endIdx - startIdx + 1;

        final double[] timeArray = new double[numVectorsUsed];
        final double[] xPosArray = new double[numVectorsUsed];
        final double[] yPosArray = new double[numVectorsUsed];
        final double[] zPosArray = new double[numVectorsUsed];
        final double[] xVelArray = new double[numVectorsUsed];
        final double[] yVelArray = new double[numVectorsUsed];
        final double[] zVelArray = new double[numVectorsUsed];

        for (int i = startIdx; i <= endIdx; i++) {
            timeArray[i - startIdx] = orbitStateVectors[i].time_mjd;
            xPosArray[i - startIdx] = orbitStateVectors[i].x_pos; // m
            yPosArray[i - startIdx] = orbitStateVectors[i].y_pos; // m
            zPosArray[i - startIdx] = orbitStateVectors[i].z_pos; // m
            xVelArray[i - startIdx] = orbitStateVectors[i].x_vel; // m/s
            yVelArray[i - startIdx] = orbitStateVectors[i].y_vel; // m/s
            zVelArray[i - startIdx] = orbitStateVectors[i].z_vel; // m/s
        }

        // Create new tie point grid
        int k = 0;
        for (int r = 0; r < gridHeight; r++) {
            // get the zero Doppler time for the rth line
            int y;
            if (r == gridHeight - 1) { // last row
                y = sourceImageHeight - 1;
            } else { // other rows
                y = (int) (r * subSamplingY);
            }
            final double curLineUTC = firstLineUTC + y * lineTimeInterval;
            //System.out.println((new ProductData.UTC(curLineUTC)).toString());

            // compute the satellite position and velocity for the zero Doppler time using cubic interpolation
            final Orbits.OrbitVector data = getOrbitData(curLineUTC, timeArray, xPosArray, yPosArray, zPosArray,
                    xVelArray, yVelArray, zVelArray);

            for (int c = 0; c < gridWidth; c++) {
                int x;
                if (c == gridWidth - 1) { // last column
                    x = sourceImageWidth - 1;
                } else { // other columns
                    x = (int) (c * subSamplingX);
                }

                final double slrgTime = slantRangeTime.getPixelDouble(x, y) / Constants.oneBillion; // ns to s;
                final GeoPos geoPos = computeLatLon(latMid, lonMid, slrgTime, data);
                targetLatTiePoints[k] = (float) geoPos.lat;
                targetLonTiePoints[k] = (float) geoPos.lon;
                ++k;
            }
        }

        final TiePointGrid latGrid = new TiePointGrid(OperatorUtils.TPG_LATITUDE, gridWidth, gridHeight, 0.0f, 0.0f,
                subSamplingX, subSamplingY, targetLatTiePoints);

        final TiePointGrid lonGrid = new TiePointGrid(OperatorUtils.TPG_LONGITUDE, gridWidth, gridHeight, 0.0f,
                0.0f, subSamplingX, subSamplingY, targetLonTiePoints, TiePointGrid.DISCONT_AT_180);

        final TiePointGeoCoding tpGeoCoding = new TiePointGeoCoding(latGrid, lonGrid, Datum.WGS_84);

        product.addTiePointGrid(latGrid);
        product.addTiePointGrid(lonGrid);
        product.setGeoCoding(tpGeoCoding);
    }

    private static boolean checkStateVectorValidity(OrbitStateVector[] orbitStateVectors) {

        if (orbitStateVectors == null) {
            return false;
        }
        if (orbitStateVectors.length <= 1) {
            return false;
        }

        for (int i = 1; i < orbitStateVectors.length; i++) {
            if (orbitStateVectors[i].time_mjd == orbitStateVectors[0].time_mjd) {
                return false;
            }
        }

        return true;
    }

    private static void setLatLonMetadata(final Product product, final MetadataElement absRoot) {
        final GeoCoding geoCoding = product.getGeoCoding();
        if (geoCoding == null)
            return;

        final GeoPos geoPosFirstNear = product.getGeoCoding().getGeoPos(new PixelPos(0, 0), null);
        final GeoPos geoPosFirstFar = product.getGeoCoding()
                .getGeoPos(new PixelPos(product.getSceneRasterWidth() - 1, 0), null);
        final GeoPos geoPosLastNear = product.getGeoCoding()
                .getGeoPos(new PixelPos(0, product.getSceneRasterHeight() - 1), null);
        final GeoPos geoPosLastFar = product.getGeoCoding().getGeoPos(
                new PixelPos(product.getSceneRasterWidth() - 1, product.getSceneRasterHeight() - 1), null);

        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_near_lat, geoPosFirstNear.getLat());
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_near_long, geoPosFirstNear.getLon());
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_far_lat, geoPosFirstFar.getLat());
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_far_long, geoPosFirstFar.getLon());
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_near_lat, geoPosLastNear.getLat());
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_near_long, geoPosLastNear.getLon());
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_far_lat, geoPosLastFar.getLat());
        AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_far_long, geoPosLastFar.getLon());
    }

    /**
     * Compute accurate target geo position.
     *
     * @param latMid   The scene latitude.
     * @param lonMid   The scene longitude.
     * @param slrgTime The slant range time of the given pixel.
     * @param data     The orbit data.
     * @return The geo position of the target.
     */
    private static GeoPos computeLatLon(final double latMid, final double lonMid, double slrgTime,
            Orbits.OrbitVector data) {

        final double[] xyz = new double[3];
        final GeoPos geoPos = new GeoPos(latMid, lonMid);

        // compute initial (x,y,z) coordinate from lat/lon
        GeoUtils.geo2xyz(geoPos, xyz);

        // compute accurate (x,y,z) coordinate using Newton's method
        GeoUtils.computeAccurateXYZ(data, xyz, slrgTime);

        // compute (lat, lon, alt) from accurate (x,y,z) coordinate
        GeoUtils.xyz2geo(xyz, geoPos);

        return geoPos;
    }

    /**
     * Get orbit information for given time.
     *
     * @param utc       The UTC in days.
     * @param timeArray Array holding zeros Doppler times for all state vectors.
     * @param xPosArray Array holding x coordinates for sensor positions in all state vectors.
     * @param yPosArray Array holding y coordinates for sensor positions in all state vectors.
     * @param zPosArray Array holding z coordinates for sensor positions in all state vectors.
     * @param xVelArray Array holding x velocities for sensor positions in all state vectors.
     * @param yVelArray Array holding y velocities for sensor positions in all state vectors.
     * @param zVelArray Array holding z velocities for sensor positions in all state vectors.
     * @return The orbit information.
     */
    private static Orbits.OrbitVector getOrbitData(final double utc, final double[] timeArray,
            final double[] xPosArray, final double[] yPosArray, final double[] zPosArray, final double[] xVelArray,
            final double[] yVelArray, final double[] zVelArray) {

        // Lagrange polynomial interpolation
        return new Orbits.OrbitVector(utc, Maths.lagrangeInterpolatingPolynomial(timeArray, xPosArray, utc),
                Maths.lagrangeInterpolatingPolynomial(timeArray, yPosArray, utc),
                Maths.lagrangeInterpolatingPolynomial(timeArray, zPosArray, utc),
                Maths.lagrangeInterpolatingPolynomial(timeArray, xVelArray, utc),
                Maths.lagrangeInterpolatingPolynomial(timeArray, yVelArray, utc),
                Maths.lagrangeInterpolatingPolynomial(timeArray, zVelArray, utc));
    }
}