public class C2rccCommons {

    public static TimeCoding getTimeCoding(Product product) {
        if (product.getSceneTimeCoding() != null) {
            return product.getSceneTimeCoding();
        }
        final ProductData.UTC startTime = product.getStartTime();
        final ProductData.UTC endTime = product.getEndTime();
        if (endTime == null || startTime == null) {
            throw new OperatorException("Could not retrieve time information from source product");
        }
        return getTimeCoding(startTime, endTime);
    }

    public static TimeCoding getTimeCoding(ProductData.UTC startTime, ProductData.UTC endTime) {
        if (startTime == null || endTime == null) {
            throw new IllegalArgumentException("Arguments startTime and endTime must be given.");
        } else {
            double startTimeMJD = startTime.getMJD();
            double constantTime = (endTime.getMJD() - startTimeMJD) / 2.0 + startTimeMJD;
            return new ConstantTimeCoding(constantTime);
        }
    }

    public static void ensureTimeInformation(Product product, ProductData.UTC startTime, ProductData.UTC endTime, TimeCoding timeCoding) {
        if(product.getStartTime() == null) {
            product.setStartTime(startTime);
        }
        if(product.getEndTime() == null) {
            product.setEndTime(endTime);
        }
        if (product.getSceneTimeCoding() == null) {
            product.setSceneTimeCoding(timeCoding);
        }
    }

    public static boolean areSamplesValid(Sample[] sourceSamples, int x, int y) {
        boolean samplesValid = true;
        for (Sample sourceSample : sourceSamples) {
            // can be null because samples for ozone and atm_pressure might be missing
            RasterDataNode node = sourceSample.getNode();
            if (node != null) {
                if (!node.isPixelValid(x, y)) {
                    samplesValid = false;
                    break;
                }
            }
        }
        return samplesValid;
    }

    public static Band addBand(Product targetProduct, String name, String unit, String description) {
        Band targetBand = targetProduct.addBand(name, ProductData.TYPE_FLOAT32);
        targetBand.setUnit(unit);
        targetBand.setDescription(description);
        targetBand.setGeophysicalNoDataValue(Double.NaN);
        targetBand.setNoDataValueUsed(true);
        return targetBand;
    }

    public static Band addVirtualBand(Product targetProduct, String name, String expression, String unit, String description) {
        Band band = targetProduct.addBand(name, expression);
        band.setUnit(unit);
        band.setDescription(description);
        band.getSourceImage(); // trigger source image creation
        band.setGeophysicalNoDataValue(Double.NaN);
        band.setNoDataValueUsed(true);
        return band;
    }
} public interface C2rccConfigurable {

    void setAtmosphericAuxDataPath(String atmosphericAuxDataPath);

    void setTomsomiStartProduct(Product tomsomiStartProduct);

    void setTomsomiEndProduct(Product tomsomiEndProduct);

    void setNcepStartProduct(Product ncepStartProduct);

    void setNcepEndProduct(Product ncepEndProduct);

    void setTemperature(double temperature);

    void setSalinity(double salinity);

    void setOzone(double ozone);

    void setPress(double press);

    void setValidPixelExpression(String validPixelExpression);

    void setOutputRtosa(boolean outputRtosa);

    void setOutputAsRrs(boolean asRadianceRefl);
} public interface AtmosphericAuxdata {

    /**
     * Gets the daily interpolated ozone content value at the specified geo-location in Dobson units.
     *
     * @param mjd the date/time value as mjd to retrieve the data for
     * @param x   the pixel x-index, preferred over lat if applicable
     * @param y   the pixel x-index, preferred over lon if applicable
     * @param lat the latitude value
     * @param lon the longitude value   @return the ozone value or NaN if value could not be retrieved
     * @throws Exception in case of disk access failures
     */
    double getOzone(double mjd, int x, int y, double lat, double lon) throws Exception;

    /**
     * Gets the quarter daily interpolated surface pressure value at the specified geo-location in mBar.
     *
     * @param mjd the date/time value as mjd to retrieve the data for
     * @param x   the pixel x-index, preferred over lat if applicable
     * @param y   the pixel y-index, preferred over lon if applicable
     * @param lat the latitude value
     * @param lon the longitude value
     * @return the pressure value or NaN if value could not be retrieved
     * @throws Exception in case of disk access failures
     */
    double getSurfacePressure(double mjd, int x, int y, double lat, double lon) throws Exception;

    /**
     * Releases all resources.
     */
    void dispose();
} public class AtmosphericAuxdataBuilder {

    private RasterDataNode ozoneRaster;
    private RasterDataNode surfPressureRaster;
    private String atmosphericAuxDataPath;
    private Product tomsomiStartProduct;
    private Product tomsomiEndProduct;
    private Product ncepStartProduct;
    private Product ncepEndProduct;

    private double ozone = 330;
    private double surfacePressure = 1000;

    public void useAtmosphericAuxDataPath(String atmosphericAuxDataPath) {
        this.atmosphericAuxDataPath = atmosphericAuxDataPath;
    }

    public void useNcepProducts(Product ncepStartProduct, Product ncepEndProduct) {
        this.ncepStartProduct = ncepStartProduct;
        this.ncepEndProduct = ncepEndProduct;
    }

    public void setOzone(double ozone) {
        this.ozone = ozone;
    }

    public void setSurfacePressure(double press) {
        this.surfacePressure = press;
    }

    public void useTomsomiProducts(Product tomsomiStartProduct, Product tomsomiEndProduct) {
        this.tomsomiStartProduct = tomsomiStartProduct;
        this.tomsomiEndProduct = tomsomiEndProduct;

    }

    public void useAtmosphericRaster(RasterDataNode ozoneRaster, RasterDataNode surfPressureRaster) {
        this.ozoneRaster = ozoneRaster;
        this.surfPressureRaster = surfPressureRaster;
    }

    public AtmosphericAuxdata create() throws Exception {
        AtmosphericAuxdata auxdata;
        if (ozoneRaster != null && surfPressureRaster != null) {
            auxdata = new RasterAtmosphericAuxdata(ozoneRaster, surfPressureRaster);
        } else {
            if (StringUtils.isNullOrEmpty(atmosphericAuxDataPath)) {
                if (tomsomiStartProduct == null || tomsomiEndProduct == null || ncepStartProduct == null || ncepEndProduct == null) {
                    SystemUtils.LOG.info("Atmospheric auxdata product can't be used. At least one is not specified. " +
                                                 "Using constant values for ozone (" + ozone + ") and surface pressure (" + surfacePressure + ").");
                    auxdata = new ConstantAtmosphericAuxdata(ozone, surfacePressure);
                } else {
                    auxdata = new AtmosphericAuxdataStatic(tomsomiStartProduct, tomsomiEndProduct, "ozone", ozone,
                                                           ncepStartProduct, ncepEndProduct, "press", surfacePressure);
                }
            } else {
                final AncDownloader ancDownloader = new AncDownloader(AncillaryCommons.ANC_DATA_URI);
                final AncRepository ancRepository = new AncRepository(new File(atmosphericAuxDataPath), ancDownloader);
                AncDataFormat ozoneFormat = AncillaryCommons.createOzoneFormat(ozone);
                AncDataFormat pressureFormat = AncillaryCommons.createPressureFormat(surfacePressure);
                auxdata = new AtmosphericAuxdataDynamic(ancRepository, ozoneFormat, pressureFormat);
            }
        }
        return auxdata;
    }
} public class NNUtils {
    public static String[] getNNFilePaths(Path nnRootPath, String[] alternativeNetDirNames) throws IOException {
        final ArrayList<String> pathsList = new ArrayList<>();
        final String prefix = "The path '" + nnRootPath.toString() + "' ";

        if (!Files.isDirectory(nnRootPath)) {
            throw new OperatorException(prefix + "for alternative neuronal nets is not a valid path");
        }

        final HashSet<String> dirNames = new HashSet<>();
        Files.newDirectoryStream(nnRootPath).forEach(path -> {
            if (Files.isDirectory(path)) {
                dirNames.add(path.getFileName().toString());
            }
        });
        for (String alternativeNetDirName : alternativeNetDirNames) {
            if (!dirNames.contains(alternativeNetDirName)) {
                throw new OperatorException(prefix + "does not contain the expected sub directory '" + alternativeNetDirName + "'");
            }
            final int[] dotNetFilesCount = {0};
            final Path nnDirPath = nnRootPath.resolve(alternativeNetDirName);
            Files.newDirectoryStream(nnDirPath).forEach(path -> {
                if (path.getFileName().toString().toLowerCase().endsWith(".net")
                        && Files.isRegularFile(path)) {
                    dotNetFilesCount[0]++;
                    pathsList.add(path.toString());
                }
            });
            int count = dotNetFilesCount[0];
            if (count != 1) {
                throw new OperatorException("The path '" + nnDirPath + " must contain exact 1 file with file ending '*.net', but contains " + count);
            }
        }

        return pathsList.toArray(new String[pathsList.size()]);
    }
} public class RgbProfiles {
    private static final String[] RGB_BAND_NAMES = {
            "rhow", "rrs", "rhown", "rpath", "rtoa",
            "rtosa_gc", "rtosagc_aann", "tdown", "tup"
    };

    public static void installRgbProfiles(String productType, String redExpr, String greenExpr, String blueExpr) {
        RGBImageProfileManager profileManager = RGBImageProfileManager.getInstance();
        RGBImageProfile[] allProfiles = profileManager.getAllProfiles();
        for (String bandName : RGB_BAND_NAMES) {
            String profileName = productType + " " + bandName;
            Stream<RGBImageProfile> profileStream = Arrays.stream(allProfiles);
            boolean profileExists = profileStream.anyMatch(rgbImageProfile -> rgbImageProfile.getName().equals(profileName));
            if (!profileExists) {
                String[] rgbaExpressions = {
                        String.format(redExpr, bandName),
                        String.format(greenExpr, bandName),
                        String.format(blueExpr, bandName)};
                String[] pattern = {productType, "", ""};
                profileManager.addProfile(new RGBImageProfile(profileName, rgbaExpressions, pattern));
            }
        }
    }

    public static void installMerisRgbProfiles() {
        installRgbProfiles("C2RCC_MERIS",
                           "log(0.05 + 0.35 * %1$s_2 + 0.60 * %1$s_5 + %1$s_6 + 0.13 * %1$s_7)",
                           "log(0.05 + 0.21 * %1$s_3 + 0.50 * %1$s_4 + %1$s_5 + 0.38 * %1$s_6)",
                           "log(0.05 + 0.21 * %1$s_1 + 1.75 * %1$s_2 + 0.47 * %1$s_3 + 0.16 * %1$s_4)");
    }

    public static void installMeris4RgbProfiles() {
        installRgbProfiles("C2RCC_MERIS4",
                           "log(0.05 + 0.35 * %1$s_2 + 0.60 * %1$s_5 + %1$s_6 + 0.13 * %1$s_7)",
                           "log(0.05 + 0.21 * %1$s_3 + 0.50 * %1$s_4 + %1$s_5 + 0.38 * %1$s_6)",
                           "log(0.05 + 0.21 * %1$s_1 + 1.75 * %1$s_2 + 0.47 * %1$s_3 + 0.16 * %1$s_4)");
    }

    public static void installS2MsiRgbProfiles() {
        installRgbProfiles("C2RCC_S2-MSI",
                           "log(0.05 + 0.35 * %1$s_B2 + 0.60 * %1$s_B5 + %1$s_6 + 0.13 * %1$s_B7)",
                           "log(0.05 + 0.21 * %1$s_B3 + 0.50 * %1$s_B4 + %1$s_B5 + 0.38 * %1$s_B6)",
                           "log(0.05 + 0.21 * %1$s_B1 + 1.75 * %1$s_B2 + 0.47 * %1$s_B3 + 0.16 * %1$s_B4)");
    }

    public static void installLandsat8RgbProfiles() {
        installRgbProfiles("C2RCC_LANDSAT-8",
                           "%1$s_4",
                           "%1$s_3",
                           "%1$s_2"
        );
    }

    public static void installModisRgbProfiles() {
        installRgbProfiles("C2RCC_MODIS",
                           "%1$s_667)",
                           "%1$s_547)",
                           "%1$s_443)");
    }

    public static void installSeaWifsRgbProfiles() {
        installRgbProfiles("C2RCC_SEAWIFS",
                           "%1$s_670",
                           "%1$s_555",
                           "%1$s_443");
    }

    public static void installViirsRgbProfiles() {
        installRgbProfiles("C2RCC_VIIRS",
                           "%1$s_671",
                           "%1$s_551",
                           "%1$s_443");
    }

} public static Band addBand(Product targetProduct, String name, String unit, String description) {
    Band targetBand = targetProduct.addBand(name, ProductData.TYPE_FLOAT32);
    targetBand.setUnit(unit);
    targetBand.setDescription(description);
    targetBand.setGeophysicalNoDataValue(Double.NaN);
    targetBand.setNoDataValueUsed(true);
    return targetBand;
} public static Band addVirtualBand(Product targetProduct, String name, String expression, String unit, String description) {
    Band band = targetProduct.addBand(name, expression);
    band.setUnit(unit);
    band.setDescription(description);
    band.getSourceImage(); // trigger source image creation
    band.setGeophysicalNoDataValue(Double.NaN);
    band.setNoDataValueUsed(true);
    return band;
} public static double fetchOzone(final AtmosphericAuxdata atmosphericAuxdata, double timeMJD, int x, int y, double lat, double lon) {
    try {
        return atmosphericAuxdata.getOzone(timeMJD, x, y, lat, lon);
    } catch (Exception e) {
        throw new OperatorException("Unable to fetch ozone value from auxdata.", e);
    }
} public static double fetchSurfacePressure(AtmosphericAuxdata atmosphericAuxdata, double timeMJD, int x, int y, double lat, double lon) {
    try {
        return atmosphericAuxdata.getSurfacePressure(timeMJD, x, y, lat, lon);
    } catch (Exception e) {
        throw new OperatorException("Unable to fetch surface pressure value from auxdata.", e);
    }
} static float[] DEFAULT_WAVELENGTH = new float[]{440, 480, 560, 655, 865}; static final int FLAG_INDEX_ADET_AT_MAX = 6; static final int FLAG_INDEX_ADET_AT_MIN = 11; static final int FLAG_INDEX_AGELB_AT_MAX = 7; static final int FLAG_INDEX_AGELB_AT_MIN = 12; static final int FLAG_INDEX_APIG_AT_MAX = 5; static final int FLAG_INDEX_APIG_AT_MIN = 10; static final int FLAG_INDEX_BPART_AT_MAX = 8; static final int FLAG_INDEX_BPART_AT_MIN = 13; static final int FLAG_INDEX_BWIT_AT_MAX = 9; static final int FLAG_INDEX_BWIT_AT_MIN = 14; static final int FLAG_INDEX_CLOUD = 3; static final int FLAG_INDEX_IOP_OOR = 4; static final int FLAG_INDEX_KD489_AT_MAX = 18; static final int FLAG_INDEX_KD489_OOR = 16; static final int FLAG_INDEX_KDMIN_AT_MAX = 19; static final int FLAG_INDEX_KDMIN_OOR = 17; static final int FLAG_INDEX_RHOW_OOR = 2; static final int FLAG_INDEX_RHOW_OOS = 15; static final int FLAG_INDEX_RTOSA_OOR = 1; static final int FLAG_INDEX_RTOSA_OOS = 0; static final int FLAG_INDEX_VALID_PE = 31; static final int IDX_iop_rw = 3; static final int IDX_iop_unciop = 5; static final int IDX_iop_uncsumiop_unckd = 6; static final int IDX_rtosa_aann = 0; static final int IDX_rtosa_rpath = 9; static final int IDX_rtosa_rw = 1; static final int IDX_rtosa_trans = 8; static final int IDX_rw_iop = 2; static final int IDX_rw_kd = 4; static final int IDX_rw_rwnorm = 7; public static class Result {

    public final double[] r_toa;
    public final double[] r_tosa;
    public final double[] rtosa_aann;
    public final double rtosa_oos;
    public final double[] rpath_nn;
    public final double[] transd_nn;
    public final double[] transu_nn;
    public final double[] rwa;
    public final double rwa_oos;
    public final double[] rwn;
    public final double[] iops_nn;
    public final double kd489_nn;
    public final double kdmin_nn;
    public final double[] unc_iop_abs;
    public final double unc_abs_adg;
    public final double unc_abs_atot;
    public final double unc_abs_btot;
    public final double unc_abs_chl;
    public final double unc_abs_tsm;
    public final double unc_abs_kd489;
    public final double unc_abs_kdmin;
    public final int flags;

    public Result(double[] r_toa, double[] r_tosa, double[] rtosa_aann, double[] rpath_nn, double[] transd_nn, double[] transu_nn, double[] rwa,
                  double[] rwn, double rtosa_oos, double rwa_oos, double[] iops_nn,
                  double kd489_nn, double kdmin_nn, double[] unc_iop_abs,
                  double unc_abs_adg, double unc_abs_atot, double unc_abs_btot, double unc_abs_chl,
                  double unc_abs_tsm, double unc_abs_kd489, double unc_abs_kdmin, int flags) {

        this.r_toa = r_toa;
        this.r_tosa = r_tosa;
        this.rtosa_aann = rtosa_aann;
        this.rtosa_oos = rtosa_oos;
        this.rpath_nn = rpath_nn;
        this.transd_nn = transd_nn;
        this.transu_nn = transu_nn;
        this.rwa = rwa;
        this.rwa_oos = rwa_oos;
        this.rwn = rwn;
        this.iops_nn = iops_nn;
        this.kd489_nn = kd489_nn;
        this.kdmin_nn = kdmin_nn;
        this.unc_iop_abs = unc_iop_abs;
        this.unc_abs_adg = unc_abs_adg;
        this.unc_abs_atot = unc_abs_atot;
        this.unc_abs_btot = unc_abs_btot;
        this.unc_abs_chl = unc_abs_chl;
        this.unc_abs_tsm = unc_abs_tsm;
        this.unc_abs_kdmin = unc_abs_kdmin;
        this.unc_abs_kd489 = unc_abs_kd489;
        this.flags = flags;
    }
}
import com.bc.ceres.core.Assert;
import org.esa.s3tbx.c2rcc.C2rccCommons;
import org.esa.s3tbx.c2rcc.C2rccConfigurable;
import org.esa.s3tbx.c2rcc.ancillary.AtmosphericAuxdata;
import org.esa.s3tbx.c2rcc.ancillary.AtmosphericAuxdataBuilder;
import org.esa.s3tbx.c2rcc.util.NNUtils;
import org.esa.s3tbx.c2rcc.util.RgbProfiles;
import org.esa.snap.core.datamodel.Band;
import org.esa.snap.core.datamodel.FlagCoding;
import org.esa.snap.core.datamodel.GeoPos;
import org.esa.snap.core.datamodel.MetadataAttribute;
import org.esa.snap.core.datamodel.MetadataElement;
import org.esa.snap.core.datamodel.PixelPos;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductData;
import org.esa.snap.core.datamodel.ProductNode;
import org.esa.snap.core.datamodel.ProductNodeEvent;
import org.esa.snap.core.datamodel.ProductNodeListener;
import org.esa.snap.core.datamodel.ProductNodeListenerAdapter;
import org.esa.snap.core.datamodel.RasterDataNode;
import org.esa.snap.core.datamodel.TimeCoding;
import org.esa.snap.core.dataop.dem.ElevationModel;
import org.esa.snap.core.dataop.dem.ElevationModelDescriptor;
import org.esa.snap.core.dataop.dem.ElevationModelRegistry;
import org.esa.snap.core.dataop.resamp.Resampling;
import org.esa.snap.core.gpf.GPF;
import org.esa.snap.core.gpf.OperatorException;
import org.esa.snap.core.gpf.OperatorSpi;
import org.esa.snap.core.gpf.annotations.OperatorMetadata;
import org.esa.snap.core.gpf.annotations.Parameter;
import org.esa.snap.core.gpf.annotations.SourceProduct;
import org.esa.snap.core.gpf.pointop.PixelOperator;
import org.esa.snap.core.gpf.pointop.ProductConfigurer;
import org.esa.snap.core.gpf.pointop.Sample;
import org.esa.snap.core.gpf.pointop.SourceSampleConfigurer;
import org.esa.snap.core.gpf.pointop.TargetSampleConfigurer;
import org.esa.snap.core.gpf.pointop.WritableSample;
import org.esa.snap.core.util.ProductUtils;
import org.esa.snap.core.util.StringUtils;
import org.esa.snap.core.util.converters.BooleanExpressionConverter;
import java.awt.*;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import static org.esa.s3tbx.c2rcc.C2rccCommons.addBand;
import static org.esa.s3tbx.c2rcc.C2rccCommons.addVirtualBand;
import static org.esa.s3tbx.c2rcc.ancillary.AncillaryCommons.fetchOzone;
import static org.esa.s3tbx.c2rcc.ancillary.AncillaryCommons.fetchSurfacePressure;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.DEFAULT_WAVELENGTH;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_ADET_AT_MAX;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_ADET_AT_MIN;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_AGELB_AT_MAX;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_AGELB_AT_MIN;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_APIG_AT_MAX;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_APIG_AT_MIN;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_BPART_AT_MAX;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_BPART_AT_MIN;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_BWIT_AT_MAX;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_BWIT_AT_MIN;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_CLOUD;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_IOP_OOR;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_KD489_AT_MAX;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_KD489_OOR;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_KDMIN_AT_MAX;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_KDMIN_OOR;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_RHOW_OOR;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_RHOW_OOS;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_RTOSA_OOR;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_RTOSA_OOS;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.FLAG_INDEX_VALID_PE;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.IDX_iop_rw;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.IDX_iop_unciop;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.IDX_iop_uncsumiop_unckd;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.IDX_rtosa_aann;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.IDX_rtosa_rpath;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.IDX_rtosa_rw;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.IDX_rtosa_trans;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.IDX_rw_iop;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.IDX_rw_kd;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.IDX_rw_rwnorm;
import static org.esa.s3tbx.c2rcc.landsat8.C2rccLandsat8Algorithm.Result;
        double sunElevation = imageAttributes.getAttribute("SUN_ELEVATION").getData().getElemDouble();
        sunZenith = 90 - sunElevation;

        SubsetInfo subsetInfo = getSubsetInfo(metadataRoot);
        geometryAnglesBuilder = new GeometryAnglesBuilder(subsetInfo.subsampling_x, subsetInfo.offset_x, subsetInfo.center_x,
                                                          sunAzimuth, sunZenith);
        double sunAngleCorrectionFactor = Math.sin(Math.toRadians(sunElevation));

        MetadataElement radiometricRescaling = l1MetadataFile.getElement("RADIOMETRIC_RESCALING");
        reflectance_offset = new double[L8_BAND_COUNT];
        reflectance_scale = new double[L8_BAND_COUNT];
        for (int i = 0; i < L8_BAND_COUNT; i++) {
            // this follows:
            // http://landsat.usgs.gov/Landsat8_Using_Product.php, section 'Conversion to TOA Reflectance'
            // also see org.esa.s3tbx.dataio.landsat.geotiff.Landsat8Metadata#getSunAngleCorrectionFactor
            double scalingOffset = radiometricRescaling.getAttributeDouble(String.format("REFLECTANCE_ADD_BAND_%d", i + 1));
            reflectance_offset[i] = scalingOffset / sunAngleCorrectionFactor;
            double scalingFactor = radiometricRescaling.getAttributeDouble(String.format("REFLECTANCE_MULT_BAND_%d", i + 1));
            reflectance_scale[i] = scalingFactor / sunAngleCorrectionFactor;
        }

        if (sourceProduct.getSceneGeoCoding() == null) {
            throw new OperatorException("The source product must be geo-coded.");
        }

        ElevationModelDescriptor getasse30 = ElevationModelRegistry.getInstance().getDescriptor("GETASSE30");
        if (getasse30 != null) {
            // if elevation model cannot be initialised the fallback height will be used
            elevationModel = getasse30.createDem(Resampling.BILINEAR_INTERPOLATION);
        }

        try {
            if (StringUtils.isNotNullAndNotEmpty(alternativeNNPath)) {
                String[] nnFilePaths = NNUtils.getNNFilePaths(Paths.get(alternativeNNPath), alternativeNetDirNames);
                algorithm = new C2rccLandsat8Algorithm(nnFilePaths, false);
            } else {
                String[] nnFilePaths = c2rccNetSetMap.get(netSet);
                if(nnFilePaths == null) {
                    throw new OperatorException(String.format("Unknown set '%s' of neural nets specified.", netSet));
                }
                algorithm = new C2rccLandsat8Algorithm(nnFilePaths, true);
            }
        } catch (IOException e) {
            throw new OperatorException(e);
        }

        algorithm.setTemperature(temperature);
        algorithm.setSalinity(salinity);
        algorithm.setThresh_absd_log_rtosa(thresholdRtosaOOS);
        algorithm.setThresh_rwlogslope(thresholdAcReflecOos);
        algorithm.setThresh_cloudTransD(thresholdCloudTDown865);

        algorithm.setOutputRtosaGcAann(outputRtosaGcAann);
        algorithm.setOutputRpath(outputRpath);
        algorithm.setOutputTdown(outputTdown);
        algorithm.setOutputTup(outputTup);
        algorithm.setOutputRhow(outputAcReflectance);
        algorithm.setOutputRhown(outputRhown);
        algorithm.setOutputOos(outputOos);
        algorithm.setOutputKd(outputKd);
        algorithm.setOutputUncertainties(outputUncertainties);
        algorithm.setDeriveRwFromPathAndTransmittance(deriveRwFromPathAndTransmittance);

        timeCoding = C2rccCommons.getTimeCoding(sourceProduct);
        initAtmosphericAuxdata();

        if (sourceProduct.isMultiSize()) {
            HashMap<String, Object> parameters = new HashMap<>();
            parameters.put("referenceBand", EXPECTED_BANDNAMES[0]);
            resampledProduct = GPF.createProduct("Resample", parameters, sourceProduct);
        }
        else {
            resampledProduct = sourceProduct;
        }
    }

    private SubsetInfo getSubsetInfo(MetadataElement metadataRoot) {
        SubsetInfo subsetInfo = new SubsetInfo(metadataRoot.getProduct().getSceneRasterWidth() / 2);

        MetadataElement history = metadataRoot.getElement("history");
        if (history != null) {
            MetadataElement subsetElement = history.getElement("SubsetInfo");
            if (subsetElement != null) {
                subsetInfo.subsampling_x = subsetElement.getAttributeInt("SubSampling.x", 1);
                // subsetInfo.subsampling_y = subsetElement.getAttributeInt("SubSampling.y", 1);
                subsetInfo.offset_x = subsetElement.getAttributeInt("SubRegion.x", 0);
                // y_off = subsetElement.getAttributeInt("SubRegion.y", 0);
            }
        }
        return subsetInfo;
    }

    private void initAtmosphericAuxdata() {
        AtmosphericAuxdataBuilder auxdataBuilder = new AtmosphericAuxdataBuilder();
        auxdataBuilder.setOzone(ozone);
        auxdataBuilder.setSurfacePressure(press);
        auxdataBuilder.useAtmosphericAuxDataPath(atmosphericAuxDataPath);
        auxdataBuilder.useTomsomiProducts(tomsomiStartProduct, tomsomiEndProduct);
        auxdataBuilder.useNcepProducts(ncepStartProduct, ncepEndProduct);
        try {
            atmosphericAuxdata = auxdataBuilder.create();
        } catch (Exception e) {
            throw new OperatorException("Could not create provider for atmospheric auxdata", e);
        }
    }

    private void assertSourceBand(String name) {
        if (sourceProduct.getBand(name) == null) {
            throw new OperatorException("Invalid source product, band '" + name + "' required");
        }
    }

    private static double toReflectances(double source, double radiance_offset, double radiance_scale,
                                         double reflectance_offset, double reflectance_scale) {
        double count = (source - radiance_offset) / radiance_scale;
        return count * reflectance_scale + reflectance_offset;
    }

    public static class Spi extends OperatorSpi {
        static{
            RgbProfiles.installLandsat8RgbProfiles();