|
| 1 | +package org.esa.snap.idepix.s2msi; |
| 2 | + |
| 3 | +import org.esa.snap.core.dataio.ProductSubsetDef; |
| 4 | +import org.esa.snap.core.datamodel.Band; |
| 5 | +import org.esa.snap.core.datamodel.CrsGeoCoding; |
| 6 | +import org.esa.snap.core.datamodel.GeneralFilterBand; |
| 7 | +import org.esa.snap.core.datamodel.GeoCoding; |
| 8 | +import org.esa.snap.core.datamodel.Kernel; |
| 9 | +import org.esa.snap.core.datamodel.Mask; |
| 10 | +import org.esa.snap.core.datamodel.Product; |
| 11 | +import org.esa.snap.core.datamodel.ProductData; |
| 12 | +import org.esa.snap.core.datamodel.VirtualBand; |
| 13 | +import org.esa.snap.core.gpf.OperatorException; |
| 14 | +import org.esa.snap.core.gpf.Tile; |
| 15 | +import org.opengis.referencing.operation.MathTransform; |
| 16 | + |
| 17 | +import java.awt.Color; |
| 18 | +import java.awt.geom.AffineTransform; |
| 19 | +import java.io.IOException; |
| 20 | + |
| 21 | +import static org.esa.snap.idepix.s2msi.util.S2IdepixConstants.*; |
| 22 | + |
| 23 | +public class CoastalCloudDistinction { |
| 24 | + |
| 25 | + private static final float COAST_BUFFER_SIZE = 1000f; |
| 26 | + |
| 27 | + private final Band dilatedLandFlag; |
| 28 | + private final Band dilatedWaterFlag; |
| 29 | + private final Band idx1band; |
| 30 | + private final Band idx2band; |
| 31 | + private final int resolution; |
| 32 | + |
| 33 | + public CoastalCloudDistinction(Product s2ClassifProduct) { |
| 34 | + final Product ccdHelperProduct = createCcdHelperProduct(s2ClassifProduct); |
| 35 | + resolution = determineSourceResolution(s2ClassifProduct); |
| 36 | + dilatedLandFlag = createDilatedLandFlag(ccdHelperProduct); |
| 37 | + dilatedWaterFlag = createDilatedWaterFlag(ccdHelperProduct); |
| 38 | + idx1band = getRatio2_11(ccdHelperProduct); |
| 39 | + idx2band = getRatio8_11(ccdHelperProduct); |
| 40 | + } |
| 41 | + |
| 42 | + private Product createCcdHelperProduct(Product s2ClassifProduct) { |
| 43 | + try { |
| 44 | + return s2ClassifProduct.createSubset(new ProductSubsetDef(), "ccd_helper", ""); |
| 45 | + } catch (IOException e) { |
| 46 | + throw new OperatorException("Cannot create helper product for coastal cloud distinction." ,e); |
| 47 | + } |
| 48 | + } |
| 49 | + |
| 50 | + private Band createDilatedLandFlag(Product ucdHelperProduct) { |
| 51 | + return createDilatedIdepixFlag(ucdHelperProduct, "tmp_land_mask", IDEPIX_LAND_DESCR_TEXT, IDEPIX_LAND_NAME); |
| 52 | + } |
| 53 | + |
| 54 | + private Band createDilatedWaterFlag(Product ucdHelperProduct) { |
| 55 | + return createDilatedIdepixFlag(ucdHelperProduct, "tmp_water_mask", IDEPIX_WATER_DESCR_TEXT, IDEPIX_WATER_NAME); |
| 56 | + } |
| 57 | + |
| 58 | + private Band createDilatedIdepixFlag(Product ucdHelperProduct, |
| 59 | + String maskName, |
| 60 | + String descriptionName, |
| 61 | + String flagName) { |
| 62 | + final Mask flagMask = Mask.BandMathsType.create(maskName, descriptionName, |
| 63 | + ucdHelperProduct.getSceneRasterWidth(), ucdHelperProduct.getSceneRasterHeight(), |
| 64 | + String.format("%1$s.%2$s", IDEPIX_CLASSIF_FLAGS, flagName), |
| 65 | + new Color(178, 0, 178), 0.5f); |
| 66 | + flagMask.setOwner(ucdHelperProduct); |
| 67 | + |
| 68 | + return computeDilate(flagMask); |
| 69 | + } |
| 70 | + |
| 71 | + private Band computeDilate(Band band) { |
| 72 | + final int kernelSize = (int) Math.floor((2 * COAST_BUFFER_SIZE) / resolution); |
| 73 | + final Kernel kernel = new Kernel(kernelSize, kernelSize, new double[kernelSize * kernelSize]); |
| 74 | + final GeneralFilterBand filterBand = new GeneralFilterBand( |
| 75 | + "__dilate33_" + band.getName(), band, GeneralFilterBand.OpType.DILATION, kernel, 1 |
| 76 | + ); |
| 77 | + band.getProduct().addBand(filterBand); |
| 78 | + return filterBand; |
| 79 | + } |
| 80 | + |
| 81 | + public void correctCloudFlag(int x, int y, Tile classifFlagTile, Tile targetFlagTile) { |
| 82 | + final boolean landFlag = classifFlagTile.getSampleBit(x, y, IDEPIX_LAND); |
| 83 | + final boolean waterFlag = classifFlagTile.getSampleBit(x, y, IDEPIX_WATER); |
| 84 | + final boolean cloudAmbiguousFlag = classifFlagTile.getSampleBit(x, y, IDEPIX_CLOUD_AMBIGUOUS); |
| 85 | + final boolean cloudFlag = classifFlagTile.getSampleBit(x, y, IDEPIX_CLOUD); |
| 86 | + final boolean cloudSureFlag = classifFlagTile.getSampleBit(x, y, IDEPIX_CLOUD_SURE); |
| 87 | + targetFlagTile.setSample(x, y, classifFlagTile.getSampleInt(x, y)); |
| 88 | + final boolean isCoastal = (dilatedLandFlag.getSampleInt(x, y) == 255 && !landFlag) || |
| 89 | + (dilatedWaterFlag.getSampleInt(x, y) == 255 && !waterFlag); |
| 90 | + if (isCoastal) { |
| 91 | + final float idx1 = idx1band.getSampleFloat(x, y); |
| 92 | + final float idx2 = idx2band.getSampleFloat(x, y); |
| 93 | + final boolean notCoast = idx1 > 0.7 || (idx1 < 1 && idx1 > 0.6 && idx2 > 0.9); |
| 94 | + final boolean cloudSureCoastalFlag = cloudSureFlag && notCoast; |
| 95 | + final boolean cloudAmbiguousCoastalFlag = cloudAmbiguousFlag && notCoast; |
| 96 | + targetFlagTile.setSample(x, y, IDEPIX_CLOUD_AMBIGUOUS, cloudAmbiguousCoastalFlag); |
| 97 | + targetFlagTile.setSample(x, y, IDEPIX_CLOUD_SURE, cloudSureCoastalFlag); |
| 98 | + targetFlagTile.setSample(x, y, IDEPIX_CLOUD, cloudAmbiguousCoastalFlag || cloudSureCoastalFlag); |
| 99 | + } else { |
| 100 | + targetFlagTile.setSample(x, y, IDEPIX_CLOUD_AMBIGUOUS, cloudAmbiguousFlag); |
| 101 | + targetFlagTile.setSample(x, y, IDEPIX_CLOUD_SURE, cloudSureFlag); |
| 102 | + targetFlagTile.setSample(x, y, IDEPIX_CLOUD, cloudFlag); |
| 103 | + } |
| 104 | + } |
| 105 | + |
| 106 | + private Band getRatio2_11(Product product) { |
| 107 | + return createRatioBand(product.getBand("B2"), product.getBand("B11"), product); |
| 108 | + } |
| 109 | + |
| 110 | + private Band getRatio8_11(Product product) { |
| 111 | + return createRatioBand(product.getBand("B8"), product.getBand("B11"), product); |
| 112 | + } |
| 113 | + |
| 114 | + private int determineSourceResolution(Product product) throws OperatorException { |
| 115 | + final GeoCoding sceneGeoCoding = product.getSceneGeoCoding(); |
| 116 | + if (sceneGeoCoding instanceof CrsGeoCoding) { |
| 117 | + final MathTransform imageToMapTransform = sceneGeoCoding.getImageToMapTransform(); |
| 118 | + if (imageToMapTransform instanceof AffineTransform) { |
| 119 | + return (int) ((AffineTransform) imageToMapTransform).getScaleX(); |
| 120 | + } |
| 121 | + } |
| 122 | + throw new OperatorException("Invalid product"); |
| 123 | + } |
| 124 | + |
| 125 | + private static Band createRatioBand(Band numerator, Band denominator, Product owner) { |
| 126 | + final String name = String.format("__ratio_%s_%s", numerator.getName(), denominator.getName()); |
| 127 | + final String expression = String.format("%s / %s", numerator.getName(), denominator.getName()); |
| 128 | + return createVirtualBand(name, expression, owner); |
| 129 | + } |
| 130 | + |
| 131 | + private static VirtualBand createVirtualBand(String name, String expression, Product owner) { |
| 132 | + final VirtualBand virtBand = new VirtualBand(name, |
| 133 | + ProductData.TYPE_FLOAT32, |
| 134 | + owner.getSceneRasterWidth(), |
| 135 | + owner.getSceneRasterHeight(), |
| 136 | + expression); |
| 137 | + owner.addBand(virtBand); |
| 138 | + return virtBand; |
| 139 | + } |
| 140 | + |
| 141 | +} |
0 commit comments