@Override
  public void computeTile(Band targetBand, Tile targetTile, ProgressMonitor pm)
      throws OperatorException {

    if (targetBand.isFlagBand()) {
      // no computations
      return;
    }

    final Rectangle rectangle = targetTile.getRectangle();
    final int bigWidth = (int) (scalingFactor * rectangle.getWidth());
    final int bigHeight = (int) (scalingFactor * rectangle.getHeight());
    final int bigX = (int) (scalingFactor * rectangle.getX());
    final int bigY = (int) (scalingFactor * rectangle.getY());
    final Rectangle big = new Rectangle(bigX, bigY, bigWidth, bigHeight);

    pm.beginTask("Processing frame...", rectangle.height);

    try {
      // todo: clean up the tiles which are not finally needed  (depends on how many channels are
      // used)
      final Tile szMerisTile = getSourceTile(synergyProduct.getTiePointGrid("sun_zenith"), big);
      final Tile vzMerisTile = getSourceTile(synergyProduct.getTiePointGrid("view_zenith"), big);
      final Tile saMerisTile = getSourceTile(synergyProduct.getTiePointGrid("sun_azimuth"), big);
      final Tile pressureTile = getSourceTile(synergyProduct.getTiePointGrid("atm_press"), big);

      final Tile seAatsrNadirTile =
          getSourceTile(
              synergyProduct.getBand(
                  "sun_elev_nadir" + "_" + SynergyConstants.INPUT_BANDS_SUFFIX_AATSR + ""),
              big);
      final Tile veAatsrNadirTile =
          getSourceTile(
              synergyProduct.getBand(
                  "view_elev_nadir" + "_" + SynergyConstants.INPUT_BANDS_SUFFIX_AATSR + ""),
              big);
      final Tile saAatsrNadirTile =
          getSourceTile(
              synergyProduct.getBand(
                  "sun_azimuth_nadir" + "_" + SynergyConstants.INPUT_BANDS_SUFFIX_AATSR + ""),
              big);
      final Tile seAatsrFwardTile =
          getSourceTile(
              synergyProduct.getBand(
                  "sun_elev_fward" + "_" + SynergyConstants.INPUT_BANDS_SUFFIX_AATSR + ""),
              big);
      final Tile veAatsrFwardTile =
          getSourceTile(
              synergyProduct.getBand(
                  "view_elev_fward" + "_" + SynergyConstants.INPUT_BANDS_SUFFIX_AATSR + ""),
              big);
      final Tile saAatsrFwardTile =
          getSourceTile(
              synergyProduct.getBand(
                  "sun_azimuth_fward" + "_" + SynergyConstants.INPUT_BANDS_SUFFIX_AATSR + ""),
              big);
      final Tile vaAatsrFwardTile =
          getSourceTile(
              synergyProduct.getBand(
                  "view_azimuth_fward" + "_" + SynergyConstants.INPUT_BANDS_SUFFIX_AATSR + ""),
              big);
      final Tile merisRad13Tile =
          getSourceTile(
              synergyProduct.getBand(
                  "radiance_13" + "_" + SynergyConstants.INPUT_BANDS_SUFFIX_MERIS + ""),
              big);
      final Tile merisRad14Tile =
          getSourceTile(
              synergyProduct.getBand(
                  "radiance_14" + "_" + SynergyConstants.INPUT_BANDS_SUFFIX_MERIS + ""),
              big);

      final Band reflecNadir16Band =
          synergyProduct.getBand(
              "reflec_nadir_1600" + "_" + SynergyConstants.INPUT_BANDS_SUFFIX_AATSR + "");
      final Tile aatsrReflNadir1600Tile = getSourceTile(reflecNadir16Band, big);
      final Band reflecNadir87Band =
          synergyProduct.getBand(
              "reflec_nadir_0870" + "_" + SynergyConstants.INPUT_BANDS_SUFFIX_AATSR + "");
      final Tile aatsrReflNadir0870Tile = getSourceTile(reflecNadir87Band, big);
      final Band reflecFward16Band =
          synergyProduct.getBand(
              "reflec_fward_1600" + "_" + SynergyConstants.INPUT_BANDS_SUFFIX_AATSR + "");
      final Tile aatsrReflFward1600Tile = getSourceTile(reflecFward16Band, big);
      final Band reflecFward87Band =
          synergyProduct.getBand(
              "reflec_fward_0870" + "_" + SynergyConstants.INPUT_BANDS_SUFFIX_AATSR + "");
      final Tile aatsrReflFward0870Tile = getSourceTile(reflecFward87Band, big);

      final Tile wsTile =
          getSourceTile(glintProduct.getBand(GlintAveOp.RESULT_WINDSPEED_NAME), rectangle);

      // Flags tiles

      final Tile isInvalid = getSourceTile(invalidBand, rectangle);

      for (int iY = rectangle.y; iY < rectangle.y + rectangle.height; iY++) {
        for (int iX = rectangle.x; iX < rectangle.x + rectangle.width; iX++) {

          final int iTarX = (int) (scalingFactor * iX + aveBlock);
          final int iTarY = (int) (scalingFactor * iY + aveBlock);
          checkForCancellation();

          final float aatsrViewElevationNadir = getAvePixel(veAatsrNadirTile, iTarX, iTarY);
          final float aatsrSunElevationNadir = getAvePixel(seAatsrNadirTile, iTarX, iTarY);
          final float aatsrViewElevationFward = getAvePixel(veAatsrFwardTile, iTarX, iTarY);
          final float aatsrSunElevationFward = getAvePixel(seAatsrFwardTile, iTarX, iTarY);

          // just use one windspeed (the 'closer to ECMWF' one from Glint retrieval)
          final float ws = wsTile.getSampleFloat(iX, iY);

          if (isInvalid.getSampleBoolean(iX, iY)
              || ws == SynergyConstants.OUTPUT_WS_BAND_NODATAVALUE) {
            targetTile.setSample(iX, iY, noDataVal);
          } else if (targetBand.getName().equals(SynergyConstants.OUTPUT_AOT_BAND_NAME)
              && (aot550Result[iX][iY] > 0.0
                  || aot550Result[iX][iY] == SynergyConstants.OUTPUT_AOT_BAND_NODATAVALUE)) {
            targetTile.setSample(iX, iY, aot550Result[iX][iY]);
          } else if (targetBand.getName().equals(SynergyConstants.OUTPUT_ANG_BAND_NAME)
              && (angResult[iX][iY] > 0.0
                  || angResult[iX][iY] == SynergyConstants.OUTPUT_ANG_BAND_NODATAVALUE)) {
            targetTile.setSample(iX, iY, angResult[iX][iY]);
          } else if (targetBand.getName().equals(SynergyConstants.OUTPUT_AOTERR_BAND_NAME)
              && (aot550ErrorResult[iX][iY] > 0.0
                  || aot550ErrorResult[iX][iY]
                      == SynergyConstants.OUTPUT_AOTERR_BAND_NODATAVALUE)) {
            targetTile.setSample(iX, iY, aot550ErrorResult[iX][iY]);
          } else if (targetBand.getName().equals(SynergyConstants.OUTPUT_ANGERR_BAND_NAME)
              && (angErrorResult[iX][iY] > 0.0
                  || angErrorResult[iX][iY] == SynergyConstants.OUTPUT_ANGERR_BAND_NODATAVALUE)) {
            targetTile.setSample(iX, iY, aot550ErrorResult[iX][iY]);
          } else if (targetBand.getName().equals(SynergyConstants.OUTPUT_GLINT_BAND_NAME)
              && (glintResult[iX][iY] > 0.0
                  || glintResult[iX][iY] == SynergyConstants.OUTPUT_GLINT_BAND_NODATAVALUE)) {
            targetTile.setSample(iX, iY, glintResult[iX][iY]);
          } else if (targetBand.getName().equals(SynergyConstants.OUTPUT_WS_BAND_NAME)
              && (wsResult[iX][iY] > 0.0
                  || wsResult[iX][iY] == SynergyConstants.OUTPUT_WS_BAND_NODATAVALUE)) {
            targetTile.setSample(iX, iY, wsResult[iX][iY]);
          } else {
            final float merisViewAzimuth = getAvePixel(vaMerisTileComplete, iTarX, iTarY);
            final float merisSunAzimuth = getAvePixel(saMerisTile, iTarX, iTarY);
            final float merisAzimuthDifference =
                GlintPreparation.removeAzimuthDifferenceAmbiguity(
                    merisViewAzimuth, merisSunAzimuth);
            final float merisViewZenith = getAvePixel(vzMerisTile, iTarX, iTarY);
            final float merisSunZenith = getAvePixel(szMerisTile, iTarX, iTarY);
            final float merisRad13 =
                getAvePixel(merisRad13Tile, iTarX, iTarY) / SynergyConstants.MERIS_13_SOLAR_FLUX;
            final float merisRad14 =
                getAvePixel(merisRad14Tile, iTarX, iTarY) / SynergyConstants.MERIS_14_SOLAR_FLUX;
            final double aatsrSeNadir = getAvePixel(seAatsrNadirTile, iTarX, iTarY);
            final double aatsrSeFward = getAvePixel(seAatsrFwardTile, iTarX, iTarY);

            // for RP test data (unit '%'), we need to divide AATSR reflectances by 100.
            // however, the correct AATSR units should be 'dl', as for the Synergy products created
            // in the Synergy module
            float aatsrUnitCorrFactor = 1.0f;
            if (reflecNadir87Band.getUnit().equals("%")) {
              // check for one band should be enough
              aatsrUnitCorrFactor = 100.0f;
            }
            final float aatsrReflNadir87 =
                (float)
                    (getAvePixel(aatsrReflNadir0870Tile, iTarX, iTarY)
                        / (Math.PI
                            * Math.cos(MathUtils.DTOR * (90.0 - aatsrSeNadir))
                            * aatsrUnitCorrFactor));
            final float aatsrReflNadir16 =
                (float)
                    (getAvePixel(aatsrReflNadir1600Tile, iTarX, iTarY)
                        / (Math.PI
                            * Math.cos(MathUtils.DTOR * (90.0 - aatsrSeNadir))
                            * aatsrUnitCorrFactor));
            final float aatsrReflFward87 =
                (float)
                    (getAvePixel(aatsrReflFward0870Tile, iTarX, iTarY)
                        / (Math.PI
                            * Math.cos(MathUtils.DTOR * (90.0 - aatsrSeFward))
                            * aatsrUnitCorrFactor));
            final float aatsrReflFward16 =
                (float)
                    (getAvePixel(aatsrReflFward1600Tile, iTarX, iTarY)
                        / (Math.PI
                            * Math.cos(MathUtils.DTOR * (90.0 - aatsrSeFward))
                            * aatsrUnitCorrFactor));

            final float aatsrViewAzimuthNadir = getAvePixel(vaAatsrNadirTileComplete, iTarX, iTarY);
            final float aatsrSunAzimuthNadir = getAvePixel(saAatsrNadirTile, iTarX, iTarY);
            final float aatsrViewAzimuthFward = vaAatsrFwardTile.getSampleFloat(iTarX, iTarY);
            final float aatsrSunAzimuthFward = saAatsrFwardTile.getSampleFloat(iTarX, iTarY);

            final float aatsrAzimuthDifferenceNadir =
                GlintPreparation.removeAzimuthDifferenceAmbiguity(
                    aatsrViewAzimuthNadir, aatsrSunAzimuthNadir);
            final float aatsrAzimuthDifferenceFward = aatsrViewAzimuthFward - aatsrSunAzimuthFward;
            // negative pressures were stored in LUT to ensure ascending sequence
            final float surfacePressure = -1.0f * getAvePixel(pressureTile, iTarX, iTarY);

            // breadboard begin STEP 1
            final float[] glintArray =
                doSynAOStep1(
                    aatsrViewElevationNadir,
                    aatsrViewElevationFward,
                    aatsrSunElevationNadir,
                    aatsrSunElevationFward,
                    aatsrAzimuthDifferenceNadir,
                    aatsrAzimuthDifferenceFward,
                    merisViewZenith,
                    merisSunZenith,
                    merisAzimuthDifference,
                    surfacePressure,
                    ws);
            glintResult[iX][iY] = glintArray[0];
            wsResult[iX][iY] = ws;
            // breadboard end STEP 1

            // breadboard begin STEP 2
            doSynAOStep2();
            // breadboard end STEP 2

            // breadboard begin STEP 3
            doSynAOStep3(
                iY,
                iX,
                merisRad13,
                merisRad14,
                aatsrReflNadir16,
                aatsrReflNadir87,
                aatsrReflFward16,
                aatsrReflFward87);

            // breadboard end STEP 3

            if (targetBand.getName().equals(SynergyConstants.OUTPUT_AOT_BAND_NAME)) {
              targetTile.setSample(iX, iY, aot550Result[iX][iY]);
            }
            if (targetBand.getName().equals(SynergyConstants.OUTPUT_ANG_BAND_NAME)) {
              targetTile.setSample(iX, iY, angResult[iX][iY]);
            }
            if (targetBand.getName().equals(SynergyConstants.OUTPUT_AOTERR_BAND_NAME)) {
              targetTile.setSample(iX, iY, aot550ErrorResult[iX][iY]);
            }
            if (targetBand.getName().equals(SynergyConstants.OUTPUT_ANGERR_BAND_NAME)) {
              targetTile.setSample(iX, iY, angErrorResult[iX][iY]);
            }
            if (targetBand.getName().equals(SynergyConstants.OUTPUT_GLINT_BAND_NAME)) {
              targetTile.setSample(iX, iY, glintResult[iX][iY]);
            }
            if (targetBand.getName().equals(SynergyConstants.OUTPUT_WS_BAND_NAME)) {
              targetTile.setSample(iX, iY, wsResult[iX][iY]);
            }
          }
          pm.worked(1);
        }
      }
    } catch (Exception e) {
      throw new OperatorException(
          "Failed to process ocean aerosol algorithm:\n" + e.getMessage(), e);
    } finally {
      pm.done();
    }
  }
  public void initialize() throws OperatorException {
    //        System.out.println("starting...");

    if (new File(SynergyConstants.SYNERGY_AUXDATA_HOME_DEFAULT).exists()) {
      auxdataPath =
          SynergyConstants.SYNERGY_AUXDATA_HOME_DEFAULT
              + File.separator
              + "aerosolLUTs"
              + File.separator
              + "ocean";
    } else {
      // try this one (in case of calvalus processing)
      auxdataPath = SynergyConstants.SYNERGY_AUXDATA_CALVALUS_DEFAULT;
    }

    noDataVal = (float) SynergyConstants.OUTPUT_AOT_BAND_NODATAVALUE;

    // get the glint product...
    Map<String, Product> glintInput = new HashMap<String, Product>(3);
    glintInput.put("l1bSynergy", synergyProduct);
    Map<String, Object> glintAveParams = new HashMap<String, Object>(2);
    glintAveParams.put("aveBlock", aveBlock);
    glintProduct =
        GPF.createProduct(
            OperatorSpi.getOperatorAlias(GlintAveOp.class), glintAveParams, glintInput);

    scalingFactor = aveBlock;
    aveBlock /= 2;
    minNAve = (int) (scalingFactor * scalingFactor - 1);
    noDataVal = (float) SynergyConstants.OUTPUT_AOT_BAND_NODATAVALUE;

    createTargetProduct();
    //        targetProduct = glintProduct;       // test

    // correction of azimuth discontinuity:
    // set up tiles for MERIS and AATSR which cover the whole scene...
    final int sceneWidth = synergyProduct.getSceneRasterWidth();
    final int sceneHeight = synergyProduct.getSceneRasterHeight();
    final Rectangle rect = new Rectangle(0, 0, sceneWidth, sceneHeight);
    vaMerisTileComplete = getSourceTile(synergyProduct.getTiePointGrid("view_azimuth"), rect);
    vaAatsrNadirTileComplete =
        getSourceTile(
            synergyProduct.getBand(
                "view_azimuth_nadir" + "_" + SynergyConstants.INPUT_BANDS_SUFFIX_AATSR + ""),
            rect);

    aot550Result = new float[sceneWidth][sceneHeight];
    angResult = new float[sceneWidth][sceneHeight];
    aot550ErrorResult = new float[sceneWidth][sceneHeight];
    angErrorResult = new float[sceneWidth][sceneHeight];
    glintResult = new float[sceneWidth][sceneHeight];
    wsResult = new float[sceneWidth][sceneHeight];

    // read aerosol class table
    try {
      aerosolClassTable = AerosolAuxData.getInstance().createAerosolClassTable();
    } catch (IOException e) {
      throw new OperatorException("Failed to read aerosol class table:\n" + e.getMessage(), e);
    }

    // read aerosol models
    try {
      aerosolModelTable = AerosolAuxData.getInstance().createAerosolModelTable(auxdataPath);
    } catch (IOException e) {
      throw new OperatorException("Failed to read aerosol class table:\n" + e.getMessage(), e);
    }

    //        wvl=[ 865,  885,1610,   885,1610,   885]
    wvl = new float[] {865.0f, 885.0f, 1610.0f, 885.0f, 1610.0f, 885.0f};
    wvlWeight = new float[] {1.0f, 1.0f, 3.0f, 1.0f, 3.0f, 3.0f};
    wvlIndex = new int[] {0, 3, 5};
    // at this point, just use 1 MERIS and 1 AATSR channel...
    // todo: clarify with RP which we should finally use

    // find model indices belonging to aerosol classes...
    final List<Integer> modelIndices = aerosolModelTable.getMaritimeAndDesertIndices();
    nMod = modelIndices.size();
    nWvl = wvlIndex.length;

    try {
      aerosolLookupTables =
          AerosolAuxData.getInstance()
              .createAerosolOceanLookupTables(
                  auxdataPath, modelIndices,
                  wvl, wvlIndex);
    } catch (IOException e) {
      throw new OperatorException("Failed to create aerosol lookup tables:\n" + e.getMessage(), e);
      //            String msg = SynergyConstants.AUXDATA_ERROR_MESSAGE;
      //            SynergyUtils.logErrorMessage(msg);
    }
    nTauLut = aerosolLookupTables[0][0].getDimensions()[4].getSequence().length;

    interpol5DResultLow = new double[nMod][nWvl][nTauLut];
    interpol5DResultHigh = new double[nMod][nWvl][nTau];
    interpolAngResult = new float[nWvl][nTau][nAng];
    costFunction = new float[nWvl][nTau][nAng];

    vectorTauLut = new double[nTauLut];
    for (int i = 0; i < nTauLut; i++) {
      vectorTauLut[i] = i * 2.0 / (nTauLut - 1);
    }
    vectorTauLutHigh = new double[nTau];
    for (int i = 0; i < nTau; i++) {
      vectorTauLutHigh[i] = i * 2.0 / (nTau - 1);
    }

    final float[] angArray = aerosolModelTable.getAngArray(modelIndices, 0);
    angstroemParameters = AerosolHelpers.getInstance().getAngstroemParameters(angArray, nAng);

    // read corresponding small LUTs and make a big LUT...

    // correct azimuths in these tiles for later usage...
    GlintPreparation.correctViewAzimuthLinear(vaMerisTileComplete, rect);
    GlintPreparation.correctViewAzimuthLinear(vaAatsrNadirTileComplete, rect);
  }