slices = new ArrayList<>();
+ for (int i = sourceBounds.y; i < sourceBounds.y + sourceBounds.height; i += sliceHeight) {
+ Rectangle curRect = new Rectangle(0, i, sourceBounds.width, sliceHeight);
+ curRect = sourceBounds.intersection(curRect);
+ if (!curRect.isEmpty()) {
+ slices.add(curRect);
+ }
+ }
+ return slices;
+ }
+
+ private RenderedImage createConvolvedImage(RenderedImage sourceImage, KernelJAI jaiKernel) {
+ final RenderedOp convImage = ConvolveDescriptor.create(sourceImage, jaiKernel, new RenderingHints(JAI.KEY_BORDER_EXTENDER, BorderExtender.createInstance(BorderExtender.BORDER_COPY)));
+ // due to convolution value can be higher than 255, so we clamp
+ final RenderedOp clampImage = ClampDescriptor.create(convImage, new double[]{0}, new double[]{255}, null);
+ // normalise to [0,1.0] value range
+ return DivideByConstDescriptor.create(clampImage, new double[]{255}, null);
+ }
+
+ private KernelJAI createJaiKernel(double radius, Dimension spacing) {
+ Dimension kernelHalfDim = new Dimension(MathUtils.ceilInt(radius / spacing.width),
+ MathUtils.ceilInt(radius / spacing.height));
+ int[] xDist = createDistanceArray(kernelHalfDim.width, spacing.width);
+ int[] yDist = createDistanceArray(kernelHalfDim.height, spacing.height);
+ return createKernelData(xDist, yDist, radius);
+ }
+
+ static int[] createDistanceArray(int kernelHalfDim, int spacing) {
+ int[] xDist = new int[kernelHalfDim * 2 + 1];
+ for (int i = 0; i < xDist.length; i++) {
+ xDist[i] = -kernelHalfDim * spacing + i * spacing;
+ }
+ return xDist;
+ }
+
+ private KernelJAI createKernelData(final int[] xDist, final int[] yDist, double radius) {
+ final double[][] kernel = new double[yDist.length][xDist.length];
+ for (int y = 0; y < kernel.length; y++) {
+ double[] rowData = kernel[y];
+ for (int x = 0; x < rowData.length; x++) {
+ rowData[x] = Math.sqrt(Math.pow(yDist[y], 2) + Math.pow(xDist[x], 2)) <= radius ? 1.0 : 0.0;
+ }
+ }
+ final double kernelSum = Arrays.stream(kernel).flatMapToDouble(Arrays::stream).sum();
+ for (double[] rowData : kernel) {
+ for (int x = 0; x < rowData.length; x++) {
+ rowData[x] = rowData[x] / kernelSum;
+ }
+ }
+ final double[] oneDimKernel = Arrays.stream(kernel).flatMapToDouble(Arrays::stream).toArray();
+ final float[] oneDimFloatKernel = new float[oneDimKernel.length];
+ for (int i = 0; i < oneDimKernel.length; i++) {
+ oneDimFloatKernel[i] = (float) oneDimKernel[i];
+ }
+
+ return new KernelJAI(xDist.length, yDist.length, oneDimFloatKernel);
+ }
+
+ private Product createCompatibleProduct(Product sourceProduct, String name) {
+ int sceneWidth = sourceProduct.getSceneRasterWidth();
+ int sceneHeight = sourceProduct.getSceneRasterHeight();
+ return new Product(name, "AATSR_IDEPIX", sceneWidth, sceneHeight);
+ }
+
+ @SuppressWarnings("might be used for debug purpose")
+ private void writeDebugImage(RenderedImage image, String filename) {
+ if (DEBUG) {
+ final File outputDir = new File("target/images");
+ final File output = new File(outputDir, filename);
+ try {
+ Files.createDirectories(output.toPath().getParent());
+ if (!ImageIO.write(image, "PNG", output)) {
+ SystemUtils.LOG.log(Level.WARNING, "No writer found for image '" + filename + "', trying to reformat the image");
+ final RenderedOp extrema = ExtremaDescriptor.create(image, null, 10, 10, Boolean.FALSE, 1, null);
+ final double[] minimum = (double[]) extrema.getProperty("minimum");
+ final double[] maximum = (double[]) extrema.getProperty("maximum");
+ final RenderedOp step1 = SubtractConstDescriptor.create(image, minimum, null);
+ final RenderedOp normImage = DivideByConstDescriptor.create(step1, new double[]{maximum[0] - minimum[0]}, null);
+ final RenderedOp scaledImage = MultiplyConstDescriptor.create(normImage, new double[]{255}, null);
+ final RenderedOp formattedImage = FormatDescriptor.create(scaledImage, DataBuffer.TYPE_BYTE, null);
+ if (!ImageIO.write(formattedImage, "PNG", output)) {
+ SystemUtils.LOG.log(Level.WARNING, "Retry of writing if image '" + filename + "'did not work too. Giving up!");
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /**
+ * The Service Provider Interface (SPI) for the operator.
+ * It provides operator meta-data and is a factory for new operator instances.
+ */
+ public static class Spi extends OperatorSpi {
+
+ public Spi() {
+ super(IdepixAatsrOp.class);
+ }
+ }
+
+ static class PathAndHeightInfo {
+ final int[][] illuPathSteps;
+ final double[] illuPathHeight;
+ final double threshHeight;
+
+ public PathAndHeightInfo(int[][] stepIndex, double[] theoretHeight, double threshHeight) {
+ this.illuPathSteps = stepIndex;
+ this.illuPathHeight = theoretHeight;
+ this.threshHeight = threshHeight;
+ }
+ }
+}
diff --git a/idepix-aatsr/src/main/java/org/esa/snap/idepix/aatsr/OrientationOpImage.java b/idepix-aatsr/src/main/java/org/esa/snap/idepix/aatsr/OrientationOpImage.java
new file mode 100644
index 00000000..7c0da415
--- /dev/null
+++ b/idepix-aatsr/src/main/java/org/esa/snap/idepix/aatsr/OrientationOpImage.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2022. Brockmann Consult GmbH (info@brockmann-consult.de)
+ *
+ * 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.snap.idepix.aatsr;
+
+import org.esa.snap.core.image.ImageManager;
+import org.esa.snap.core.util.math.MathUtils;
+
+import javax.media.jai.OpImage;
+import javax.media.jai.PlanarImage;
+import java.awt.Rectangle;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+import java.awt.image.RenderedImage;
+import java.awt.image.WritableRaster;
+
+/**
+ * @author Marco Peters
+ */
+public class OrientationOpImage extends OpImage {
+
+ public OrientationOpImage(RenderedImage lats, RenderedImage lons) {
+ super(vectorize(lats, lons), ImageManager.createSingleBandedImageLayout(DataBuffer.TYPE_DOUBLE, lats.getWidth(), lats.getHeight(), lats.getTileWidth(), lats.getTileHeight()),
+ null, false);
+ }
+
+ @Override
+ protected void computeRect(PlanarImage[] planarImages, WritableRaster writableRaster, Rectangle destRect) {
+ PlanarImage lats = planarImages[0];
+ PlanarImage lons = planarImages[1];
+ int x0 = destRect.x;
+ int y0 = destRect.y;
+ final int x1 = x0 + writableRaster.getWidth()-1;
+ final int y1 = y0 + writableRaster.getHeight()-1;
+ final Raster latsData = lats.getData(destRect);
+ final Raster lonsData = lons.getData(destRect);
+ for (int y = y0; y <= y1; y++) {
+ for (int x = x0; x <= x1; x++) {
+ final int x_a = MathUtils.crop(x - 1, x0, x1);
+ final int x_b = MathUtils.crop(x + 1, x0, x1);
+ final float lat1 = latsData.getSampleFloat(x_a, y, 0);
+ final float lon1 = lonsData.getSampleFloat(x_a, y, 0);
+ final float lat2 = latsData.getSampleFloat(x_b, y, 0);
+ final float lon2 = lonsData.getSampleFloat(x_b, y, 0);
+ final double orientation = computeOrientation(lat1, lon1, lat2, lon2);
+ writableRaster.setSample(x, y, 0, orientation);
+ }
+ }
+ }
+
+ static double computeOrientation(float lat1, float lon1, float lat2, float lon2) {
+ return Math.atan2(-(lat2 - lat1), (lon2 - lon1) * Math.cos(lat1 * Math.PI / 180.0)) * 180.0 / Math.PI;
+ }
+
+ @Override
+ public Rectangle mapSourceRect(Rectangle rectangle, int i) {
+ return new Rectangle(rectangle);
+ }
+
+ @Override
+ public Rectangle mapDestRect(Rectangle rectangle, int i) {
+ return new Rectangle(rectangle);
+ }
+
+}
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/AatsrAlgo.html b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/AatsrAlgo.html
new file mode 100644
index 00000000..97528e78
--- /dev/null
+++ b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/AatsrAlgo.html
@@ -0,0 +1,141 @@
+
+
+
+
+ SNAP Data Processors - IdePix AATSR Cloud Shadow
+
+
+
+
+
+
+
+Algorithm Description
+The cloud shadow algorithm is based on geometrical considerations alone. With the cloud top height and the
+ illumination direction the shadow position can be determined. As elevated objects are mapped in their apparent
+ position on the ground if not viewed directly from nadir, the sun and observation angles have to be transformed
+ accordingly. In that way, the algorithm translates the question of shadows from spherical geometry of the Earth
+ observation and geo-positions into the space of the regular projection grid.
+
+North Direction
+As all calculations of the shadow are translated to the geometry of the pixel grid, it is necessary to calculate the
+ north direction (also called orientation or bearing) for each pixel individually.
+ The orientation for a pixel (i,j) is derived from the neighboring pixel (i, j-1) and (i, j+1) from pixel-geocoded
+ location:
+
+Defining a cloud mask
+The following cloud flags have been combined in a cloud mask:
+Cloud_in.visible (bit 0), cloud_in.gross_cloud (bit 7), cloud_in.thin_cirrus (bit 8) and the cloud_in.medium_high (bit
+9).
+Adjustment of Sun Zenith Angle for Elevated Objects
+Under tilted view (view zenith angle > 0°) elevated objects of unknown height like clouds are projected along the line
+of view on the surface, so that their apparent location differs from the actual position over ground (nadir view).
+If view and sun azimuth angle are positioned in the same halfspace, both left or right of the nadir line (center line of
+grid) in the projected grid, x_tx>0 (VAA*180°)
, SAA*>180°
or x_tx<0
+ (VAA*<180°)
,
+SAA*<180°
(angles are corrected by
+orientation), the geometry of the apparent sun zenith angle which causes the shadow position can be described in the
+following way:
+From
+
+follows
+
+For view and sun direction in opposite directions (x_tx>0 (VAA*>180°)
, SAA*<180°
+ or
+ x_tx<0 (VAA*<180°)
, SAA*>180°
, angles corrected for orientation)
+ follows accordingly:
+
+
+The sun azimuth angles are corrected by the orientation (North direction) at the current cloud pixel, so that they
+ represent the azimuth angle in the projected grid coordinates with 0° in upwards direction on the grid. The view
+ azimuth angle is replaced by the tie point grid x_tx, which gives the distance from the nadir line at the center
+ position of a pixel. It changes its sign from left of nadir x_tx <0 to right of nadir x_tx>0. This is the easiest
+ way to find the viewing direction (without interpolation and corrections), and it allows the algorithm to process
+ subsets of the swath width.
+
+
+ |
+ |
+
+
+ Geometrical correction for apparent sun zenith angle (VAA>180°, SAA<180° or VAA<180°,
+ SAA>180°)
+ |
+ Geometrical correction for apparent sun zenith angle (VAA>180°, SAA>180° or VAA<180°,
+ SAA<180°)
+ |
+
+
+
+Determining the search path in illumination direction
+Starting from a cloud pixel, which is defined by the cloud flag expression, the illumination path is projected on the
+ grid and all pixels up to a maximum distance are identified which are intersected by this path.
+With the adjusted sun zenith angle θ*S and the azimuth angles adjusted for North
+ direction, so that they represent
+ the azimuth on the grid against the Y-direction, the geometry of the illumination path on the projection grid can be
+ fully described.
+Orientation (North direction) at pixel [i, j] is calculated by the positions at neighboring pixels
+ p[i, j-1] = (lon1, lat1) and p[i, j+1]= (lon2, lat2)
.
+
+The theoretical maximum length of the projected path in grid coordinates is defined by the range of surface elevation
+ and the adjusted angles in x and y direction (spatial resolution 1km in AATSR products):
+
+The relative grid coordinates of the start point at the cloud pixel at set to (x0, y0) = (0, 0)
.
+As the spatial resolution of the grid is quite coarse with regard to the expected cloud top heights, which are
+ currently fixed at 6km, the maximum extent of the search path is defined as
+
+With sign(x)
defined as: for x>=0, sign(x)=1; for x<0, sign(x)=-1
.
+ For all integer value combinations between (x0,y0)
and (x1,y1)
, the center position of the
+ pixel is set to relative grid coordinates + (0.5, 0.5)
. The distance from each pixel center to the
+ theoretical line of illumination is calculated. If the distance is smaller than 0.5*√2
, the pixel
+ area is intersected by the line, and the pixel is potentially a shadow pixel.
+
+For the intersected pixels (distance < 0.5*√2
, relative grid coordinates x, y) the theoretical
+ height of the illumination path is calculated as:
+
+Where the theoretical line intersects the surface defined by its altitude, the shadow can be found. The discreet
+ nature of the grid calls for a height threshold, so that shadow pixels can be identified:
+
+The cloud shadow flag is raised for all the pixels of the path, which are not masked as cloud and the theoretical
+ height intersects with the surface elevation:
+
+
+Algorithmic Limitations
+Cloud Top Height
+The AATSR Level-1 product does not provide cloud top height information. Therefor this values is predefined to a
+ value of 6000 meter. The processor interface allows to adjust this value for each processed scene.
+
+Limited to Land
+The detection of cloud shadow is limited to pixels marked as land. Above water no shadow detection is performed.
+
+Limited to Daytime
+The cloud shadow calculation is limited to the daytime. As daytime is the part of the orgbit considered where SZA<85°
+ and the confidence_in flag day (bit value 10) is raised.
+
+
+
+
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/AatsrIntro.html b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/AatsrIntro.html
new file mode 100644
index 00000000..59c252be
--- /dev/null
+++ b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/AatsrIntro.html
@@ -0,0 +1,67 @@
+
+
+
+
+ SNAP Data Processors - IdePix - AATSR Cloud Shadow
+
+
+
+
+
+
+
+Overview
+
+The nadir product of AATSR 4th reprocessing is missing a flag indicating cloud shadows. The IdePix AATSR Cloud Shadow
+ processor aims to provide such a flag over land.
+ It has been the goal to calculate this cloud shadow flag based on the information in the Level-1 product only. From
+ the
+ cloud flag and the geometry of sun illumination and observation almost all necessary information is given, which
+ allows a translation of the cloud shadow algorithm for OLCI to AATSR. The information which is missing in the
+ Level-1 product, is the cloud top height value. Thus this value can be provided as a parameter which is then used
+ for the whole scene.
+ The general idea of the shadow detection algorithm is to start from a pixel, which has been flagged as a cloud, and
+ follow the direction of illumination until the surface is reached.
+ This Idepix processor does not provide all flags which are defined by other IdePix processors. This one sets only
+ 3 of the general IdePix flags.
+ These are:
+ IDEPIX_CLOUD, IDEPIX_CLOUD_SHADOW and IDEPIX_LAND
+
+
+Further details are provided
+
+
+
+
+
+
+
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/AatsrProcessor.html b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/AatsrProcessor.html
new file mode 100644
index 00000000..b05ee881
--- /dev/null
+++ b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/AatsrProcessor.html
@@ -0,0 +1,91 @@
+
+
+
+
+ SNAP Data Processors - IdePix AATSR Cloud Shadow
+
+
+
+
+
+
+
+Processor Description
+
+I/O Parameters Tab
+
+
+
+Source Product Group
+
+
+ Name:
+ Used to select the spectral source product. The source product shall
+ contain spectral bands providing a source spectrum at each pixel. Use the ... button to
+ open a data product currently not opened in the Sentinel Toolbox.
+ Supported Source Products: The processor supports AATSR L1 product from the 4th reprocessing in the Safe
+ format or converted to e.g. BEAM-DIMAP.
+
+
+Target Product Group
+
+
+ Name:
+ Used to specify the name of the target product.
+
+
+
+ Save to:
+ Used to specify whether the target product should be saved to the file system. The
+ combo box presents a list of file formats.
+
+
+
+ Open in SNAP:
+ Used to specify whether the target product should be opened in the Sentinel Toolbox.
+ When the target product is not saved, it is opened in the Sentinel Toolbox automatically.
+
+
+The Processing Parameters
+
+
+
+
+ Copy source bands:
+ If enabled the bands of the source product are copied to the target product. If disabled, the target will only contain the flag band.
+
+
+
+ Assumed cloud top height:
+ This defines the cloud top height value used by the algorithm. The default is 6000 meter.
+
+
+
+
+
+
+
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/Aatsr_IO-Params.png b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/Aatsr_IO-Params.png
new file mode 100644
index 00000000..4f2857a9
Binary files /dev/null and b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/Aatsr_IO-Params.png differ
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/Aatsr_Proc-Params.png b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/Aatsr_Proc-Params.png
new file mode 100644
index 00000000..e6e1dbaa
Binary files /dev/null and b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/Aatsr_Proc-Params.png differ
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/IlluminationPath_geometry1_small.png b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/IlluminationPath_geometry1_small.png
new file mode 100644
index 00000000..3933454f
Binary files /dev/null and b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/IlluminationPath_geometry1_small.png differ
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/IlluminationPath_geometry2_small.png b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/IlluminationPath_geometry2_small.png
new file mode 100644
index 00000000..87613259
Binary files /dev/null and b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/IlluminationPath_geometry2_small.png differ
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq001_orientation.png b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq001_orientation.png
new file mode 100644
index 00000000..fb207ba3
Binary files /dev/null and b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq001_orientation.png differ
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq002_apparentSza_1.png b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq002_apparentSza_1.png
new file mode 100644
index 00000000..767c4948
Binary files /dev/null and b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq002_apparentSza_1.png differ
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq002_apparentSza_2.png b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq002_apparentSza_2.png
new file mode 100644
index 00000000..5ceb7349
Binary files /dev/null and b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq002_apparentSza_2.png differ
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq002_apparentSza_3.png b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq002_apparentSza_3.png
new file mode 100644
index 00000000..9bdbe8eb
Binary files /dev/null and b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq002_apparentSza_3.png differ
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_1.png b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_1.png
new file mode 100644
index 00000000..406d4d7a
Binary files /dev/null and b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_1.png differ
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_2.png b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_2.png
new file mode 100644
index 00000000..9fe3190a
Binary files /dev/null and b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_2.png differ
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_3.png b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_3.png
new file mode 100644
index 00000000..8f00a633
Binary files /dev/null and b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_3.png differ
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_4.png b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_4.png
new file mode 100644
index 00000000..71af0317
Binary files /dev/null and b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_4.png differ
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_5.png b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_5.png
new file mode 100644
index 00000000..705943ad
Binary files /dev/null and b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_5.png differ
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_6.png b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_6.png
new file mode 100644
index 00000000..067918bc
Binary files /dev/null and b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_6.png differ
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_7.png b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_7.png
new file mode 100644
index 00000000..608a052d
Binary files /dev/null and b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/aatsr/images/eq003_searchPath_7.png differ
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/help.hs b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/help.hs
new file mode 100644
index 00000000..6b261657
--- /dev/null
+++ b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/help.hs
@@ -0,0 +1,24 @@
+
+
+
+
+ IdePix AATSR Help
+
+ top
+
+
+
+ TOC
+
+ javax.help.TOCView
+ toc.xml
+
+
+ Search
+
+ javax.help.SearchView
+ JavaHelpSearch
+
+
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/images/snap_header.jpg b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/images/snap_header.jpg
new file mode 100644
index 00000000..d034251e
Binary files /dev/null and b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/images/snap_header.jpg differ
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/map.jhm b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/map.jhm
new file mode 100644
index 00000000..05fa8c97
--- /dev/null
+++ b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/map.jhm
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/style.css b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/style.css
new file mode 100644
index 00000000..6f023170
--- /dev/null
+++ b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/style.css
@@ -0,0 +1,181 @@
+body {
+ background-color: #FFFFFF;
+ color: #000000;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12;
+ padding: 6pt;
+ margin: 6pt;
+}
+
+p {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ margin: 6pt;
+}
+
+.i1 {
+ margin-left: 30;
+}
+
+.i2 {
+ margin-left: 50;
+}
+
+.note {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ color: red;
+}
+
+.inote {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ background-color: #DDDDDD;
+}
+
+.code {
+ color: blue;
+ font-family: courier new, courier, Monospaced, cursive;
+ text-align: left;
+ font-weight: normal;
+ text-decoration: none;
+ font-style: normal;
+}
+
+h1 {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 25;
+ font-weight: bold;
+ margin-top: 18pt;
+ margin-right: 6pt;
+ margin-bottom: 12pt;
+ margin-left: 6pt;
+}
+
+h2 {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 21;
+ font-weight: bold;
+ margin-top: 18pt;
+ margin-right: 6pt;
+ margin-bottom: 12pt;
+ margin-left: 6pt;
+}
+
+h3 {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 18;
+ font-weight: bold;
+ margin-top: 18pt;
+ margin-right: 6pt;
+ margin-bottom: 12pt;
+ margin-left: 6pt;
+}
+
+h4 {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 15;
+ font-weight: bold;
+ margin-top: 18pt;
+ margin-right: 6pt;
+ margin-bottom: 6pt;
+ margin-left: 6pt;
+}
+
+h5 {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12;
+ font-weight: bold;
+ margin-top: 18pt;
+ margin-right: 6pt;
+ margin-bottom: 6pt;
+ margin-left: 6pt;
+}
+
+h6 {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11;
+ font-weight: bold;
+ margin-top: 18pt;
+ margin-right: 6pt;
+ margin-bottom: 6pt;
+ margin-left: 6pt;
+}
+
+li {
+ margin-top: 4pt;
+ margin-bottom: 4pt;
+}
+
+table {
+ border-width: 0;
+ border-collapse: collapse;
+ padding: 1px;
+ margin: 0;
+}
+
+invisibletable {
+ border-width: 0;
+ border-style: solid;
+ border-color: #FFFFFF;
+ border-collapse: collapse;
+ padding: 1px;
+ margin: 0;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ font-style: normal;
+}
+
+th {
+ font-size: 12;
+ text-align: left;
+ border-width: 1;
+ border-color: #BBBBBB;
+ border-style: solid;
+ border-collapse: collapse;
+ padding-left: 6px;
+ padding-right: 6px;
+ padding-top: 3px;
+ padding-bottom: 3px;
+}
+
+td {
+ font-size: 11;
+ border-width: 1;
+ border-color: #BBBBBB;
+ border-style: solid;
+ border-collapse: collapse;
+ border-spacing: 0;
+ padding: 0;
+ padding-left: 6px;
+ padding-right: 6px;
+ vertical-align: top;
+}
+
+table.header {
+ width: 100%;
+ height: 29;
+ font-size: 17;
+ color: #ffffff;
+ vertical-align: middle;
+ background-color: #274351;
+ border-width: 0;
+ padding: 0;
+ margin: 0;
+}
+
+.header {
+ font-size: 17;
+ height: 29;
+ color: #ffffff;
+ vertical-align: middle;
+ background-color: #274351;
+ border-width: 0;
+ padding: 0;
+ white-space: nowrap;
+}
+
+hr {
+ height: 4;
+ color: #000063;
+ border-color: #000063;
+ border-style: solid;
+ border-width: 1;
+}
diff --git a/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/toc.xml b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/toc.xml
new file mode 100644
index 00000000..fd6596d2
--- /dev/null
+++ b/idepix-aatsr/src/main/javahelp/org/esa/snap/idepix/aatsr/docs/toc.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/idepix-aatsr/src/main/nbm/manifest.mf b/idepix-aatsr/src/main/nbm/manifest.mf
new file mode 100644
index 00000000..1d767356
--- /dev/null
+++ b/idepix-aatsr/src/main/nbm/manifest.mf
@@ -0,0 +1,14 @@
+Manifest-Version: 1.0
+AutoUpdate-Show-In-Client: true
+AutoUpdate-Essential-Module: false
+OpenIDE-Module-Java-Dependencies: Java > 1.8
+OpenIDE-Module-Display-Category: SNAP Supported Plugins
+OpenIDE-Module-Long-Description: Classification of pixels (cloud, snow, ice, land, water) originating from AATSR 4th reprocessing data
+ Vendor: Brockmann Consult GmbH
+ Release notes: Release notes on GitHub
+
Contact address: Chrysanderstr. 1, 21029 Hamburg (Germany)
+ Copyright: (C) 2022 by Brockmann Consult GmbH
+ Vendor: Brockmann Consult GmbH
+ License: GPLv3
+OpenIDE-Module-Layer: layer.xml
+
diff --git a/idepix-aatsr/src/main/resources/META-INF/services/org.esa.snap.core.gpf.OperatorSpi b/idepix-aatsr/src/main/resources/META-INF/services/org.esa.snap.core.gpf.OperatorSpi
new file mode 100644
index 00000000..aa028b2c
--- /dev/null
+++ b/idepix-aatsr/src/main/resources/META-INF/services/org.esa.snap.core.gpf.OperatorSpi
@@ -0,0 +1 @@
+org.esa.snap.idepix.aatsr.IdepixAatsrOp$Spi
diff --git a/idepix-aatsr/src/main/resources/helpset.xml b/idepix-aatsr/src/main/resources/helpset.xml
new file mode 100644
index 00000000..b5e1b35c
--- /dev/null
+++ b/idepix-aatsr/src/main/resources/helpset.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/idepix-aatsr/src/main/resources/layer.xml b/idepix-aatsr/src/main/resources/layer.xml
new file mode 100644
index 00000000..938eb6dc
--- /dev/null
+++ b/idepix-aatsr/src/main/resources/layer.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/idepix-aatsr/src/test/java/org/esa/snap/idepix/aatsr/IdepixAatsrOpTest.java b/idepix-aatsr/src/test/java/org/esa/snap/idepix/aatsr/IdepixAatsrOpTest.java
new file mode 100644
index 00000000..7d095b06
--- /dev/null
+++ b/idepix-aatsr/src/test/java/org/esa/snap/idepix/aatsr/IdepixAatsrOpTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2022 Brockmann Consult GmbH (info@brockmann-consult.de)
+ *
+ * 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.snap.idepix.aatsr;
+
+import org.esa.snap.core.datamodel.Mask;
+import org.esa.snap.core.datamodel.Product;
+import org.esa.snap.core.datamodel.ProductData;
+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.OperatorSpiRegistry;
+import org.esa.snap.core.util.DummyProductBuilder;
+import org.esa.snap.core.util.math.Range;
+import org.junit.Test;
+
+import java.awt.Color;
+import java.awt.Rectangle;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+public class IdepixAatsrOpTest {
+
+ @Test
+ public void testOperatorSpiIsLoaded() {
+ OperatorSpiRegistry registry = GPF.getDefaultInstance().getOperatorSpiRegistry();
+ OperatorSpi operatorSpi = registry.getOperatorSpi("Idepix.Aatsr");
+ assertNotNull(operatorSpi);
+ assertEquals("Idepix.Aatsr", operatorSpi.getOperatorAlias());
+ assertNotNull(operatorSpi.getOperatorDescriptor());
+ assertSame(operatorSpi.getOperatorClass(), operatorSpi.getOperatorDescriptor().getOperatorClass());
+ }
+
+ @Test
+ public void testTargetProductSignature() {
+ final Product aatsr = createDummyAatsrSource();
+ final IdepixAatsrOp idepixAatsrOp = new IdepixAatsrOp();
+ idepixAatsrOp.setSourceProduct(aatsr);
+
+ idepixAatsrOp.setParameterDefaultValues();
+
+ final Product targetProduct = idepixAatsrOp.getTargetProduct();
+ assertEquals(aatsr.getName() + "_idepix", targetProduct.getName());
+ assertEquals("AATSR_IDEPIX", targetProduct.getProductType());
+ assertEquals(aatsr.getSceneRasterSize(), targetProduct.getSceneRasterSize());
+
+ }
+
+ @Test
+ public void arangeTest() {
+ double[] expected = {-22.5, -21.5, -20.5, -19.5, -18.5, -17.5, -16.5, -15.5, -14.5, -13.5, -12.5, -11.5, -10.5, -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5};
+ final double[] arange = IdepixAatsrOp.arange(-22.5, 0.5, 1);
+ assertArrayEquals(expected, arange, 1.0e-6);
+ }
+
+ @Test
+ public void validate_ValidSourceProduct() {
+ final Product aatsr = createDummyAatsrSource();
+
+ try {
+ final IdepixAatsrOp idepixAatsrOp = new IdepixAatsrOp();
+ idepixAatsrOp.validate(aatsr);
+ } catch (Throwable t) {
+ fail("No exception expected here! Source should be valid.");
+ }
+
+ }
+
+ @Test(expected = OperatorException.class)
+ public void validate_InvalidSourceProduct_WrongType() {
+ final Product aatsr = createDummyAatsrSource();
+ aatsr.setProductType("differentType");
+
+ final IdepixAatsrOp idepixAatsrOp = new IdepixAatsrOp();
+ idepixAatsrOp.validate(aatsr);
+ }
+
+ @Test(expected = OperatorException.class)
+ public void validate_InvalidSourceProduct_MissingBand() {
+ final Product aatsr = createDummyAatsrSource();
+ aatsr.removeBand(aatsr.getBand("cloud_in"));
+
+ final IdepixAatsrOp idepixAatsrOp = new IdepixAatsrOp();
+ idepixAatsrOp.validate(aatsr);
+ }
+
+ @Test
+ public void detectFirstLastMaskedPixel() {
+ final Product aatsr = createDummyAatsrSource();
+ // Col 0 = 25-30
+ // Col 560 = 10-12
+ // Col 1300 = 300-400
+ final Mask testMask = Mask.BandMathsType.create("TEST_MASK", "", aatsr.getSceneRasterWidth(), aatsr.getSceneRasterHeight(),
+ "(X==0.5 && Y>=25.5 && Y<=30.5) ||" +
+ "(X==560.5 && Y>=10.5 && Y<=12.5) ||" +
+ "(X==1300.5 && Y>=300.5 && Y<=400.5)",
+ Color.yellow, 0.5f);
+
+
+ aatsr.addMask(testMask);
+ int[] range;
+ range = IdepixAatsrOp.detectMaskedPixelRangeInColumn(testMask, 0);
+ assertEquals(25, range[0], 1.0e-6);
+ assertEquals(30, range[1], 1.0e-6);
+
+ range = IdepixAatsrOp.detectMaskedPixelRangeInColumn(testMask, 560);
+ assertEquals(10, range[0], 1.0e-6);
+ assertEquals(12, range[1], 1.0e-6);
+
+ range = IdepixAatsrOp.detectMaskedPixelRangeInColumn(testMask, 1300);
+ assertEquals(300, range[0], 1.0e-6);
+ assertEquals(400, range[1], 1.0e-6);
+
+ }
+
+ @Test
+ public void sliceRectangle_atZero() {
+ final List rectangles =
+ IdepixAatsrOp.sliceRect(new Rectangle(0, 0, 512, 43138), 2000);
+
+ assertEquals(22, rectangles.size());
+ assertEquals(new Rectangle(0, 6000, 512, 2000), rectangles.get(3));
+ assertEquals(new Rectangle(0, 42000, 512, 1138), rectangles.get(21));
+ }
+
+ @Test
+ public void sliceRectangle_withYOffset() {
+ final List rectangles =
+ IdepixAatsrOp.sliceRect(new Rectangle(0, 12356, 512, 20222), 2000);
+
+ assertEquals(11, rectangles.size());
+ assertEquals(new Rectangle(0, 12356, 512, 2000), rectangles.get(0));
+ assertEquals(new Rectangle(0, 18356, 512, 2000), rectangles.get(3));
+ assertEquals(new Rectangle(0, 32356, 512, 222), rectangles.get(10));
+ }
+
+ @Test
+ public void createDistanceArray() {
+ final int[] distanceArray = IdepixAatsrOp.createDistanceArray(25, 1000);
+ assertEquals(51, distanceArray.length);
+ assertEquals(-25000, distanceArray[0]);
+ assertEquals(-24000, distanceArray[1]);
+ assertEquals(-23000, distanceArray[2]);
+ assertEquals(24000, distanceArray[distanceArray.length - 2]);
+ assertEquals(25000, distanceArray[distanceArray.length - 1]);
+ }
+
+ @Test
+ public void calcPathAndTheoreticalHeight() {
+ // input values taken from Python
+ float maxObjAlt = 6000;
+ float minSurfAlt = -65;
+ double orientation = 154.91488;
+ float oza = 16.4364f;
+ float saa = 313.2662f;
+ float spatialRes = 1000;
+ float sza = 83.9982f;
+ float x_tx = 173500.0f;
+
+ // expected values taken from Python
+ double[] illuPathHeight = {-40.56836, 53.990723, 148.30078, 186.63232, 281.31152, 375.73584, 469.8916, 413.7661, 508.5747, 603.1211, 697.3926, 735.772, 830.4507, 924.84717, 1018.9448, 962.89453, 1057.7168, 1152.2485, 1246.4727, 1284.9097, 1379.5894, 1473.9512, 1512.0195, 1606.8594, 1701.3726, 1795.5366, 1834.0474, 1928.7266, 2023.0447, 2061.1404, 2156.0017, 2250.4902, 2344.5764, 2288.1187, 2383.1836, 2477.8623, 2572.122, 2610.2534, 2705.1443, 2799.6, 2893.5803, 2837.186, 2932.3176, 3026.9954, 3121.175, 3159.3547, 3254.2874, 3348.6963, 3386.2214, 3481.4473, 3576.1243, 3670.1855, 3708.437, 3803.4297, 3897.7683, 3935.1997, 4030.5703, 4125.245, 4219.1143, 4161.652, 4257.481, 4352.5723, 4446.79, 4484.059, 4579.6772, 4674.348, 4767.853, 4710.1294, 4806.434, 4901.715, 4995.68, 5032.597, 5128.74, 5223.395, 5257.624, 5355.065, 5450.8574, 5543.962, 5579.5537, 5677.532, 5771.981, 5796.0537, 5898.027, 6000.0};
+ int[][] illuPathSteps = {{-22, -55}, {-22, -54}, {-22, -53}, {-21, -53}, {-21, -52}, {-21, -51}, {-21, -50}, {-20, -51}, {-20, -50}, {-20, -49}, {-20, -48}, {-19, -48}, {-19, -47}, {-19, -46}, {-19, -45}, {-18, -46}, {-18, -45}, {-18, -44}, {-18, -43}, {-17, -43}, {-17, -42}, {-17, -41}, {-16, -41}, {-16, -40}, {-16, -39}, {-16, -38}, {-15, -38}, {-15, -37}, {-15, -36}, {-14, -36}, {-14, -35}, {-14, -34}, {-14, -33}, {-13, -34}, {-13, -33}, {-13, -32}, {-13, -31}, {-12, -31}, {-12, -30}, {-12, -29}, {-12, -28}, {-11, -29}, {-11, -28}, {-11, -27}, {-11, -26}, {-10, -26}, {-10, -25}, {-10, -24}, {-9, -24}, {-9, -23}, {-9, -22}, {-9, -21}, {-8, -21}, {-8, -20}, {-8, -19}, {-7, -19}, {-7, -18}, {-7, -17}, {-7, -16}, {-6, -17}, {-6, -16}, {-6, -15}, {-6, -14}, {-5, -14}, {-5, -13}, {-5, -12}, {-5, -11}, {-4, -12}, {-4, -11}, {-4, -10}, {-4, -9}, {-3, -9}, {-3, -8}, {-3, -7}, {-2, -7}, {-2, -6}, {-2, -5}, {-2, -4}, {-1, -4}, {-1, -3}, {-1, -2}, {0, -2}, {0, -1}, {0, 0}};
+ double thresHeight = 101.97246511092132;
+
+ final IdepixAatsrOp.PathAndHeightInfo pathAndHeightInfo = IdepixAatsrOp.calcPathAndTheoreticalHeight(sza, saa, oza, x_tx, orientation, (int) spatialRes, (int) maxObjAlt, minSurfAlt);
+
+ assertArrayEquals(illuPathHeight, pathAndHeightInfo.illuPathHeight, 1.0e-2);
+ for (int i = 0; i < illuPathSteps.length; i++) {
+ int[] illuPathStep = illuPathSteps[i];
+ assertArrayEquals(illuPathStep, pathAndHeightInfo.illuPathSteps[i]);
+ }
+ assertEquals(thresHeight, pathAndHeightInfo.threshHeight, 1.0e-2);
+ }
+
+ private Product createDummyAatsrSource() {
+ final DummyProductBuilder pb = new DummyProductBuilder();
+ pb.size(DummyProductBuilder.Size.MEDIUM);
+ pb.gc(DummyProductBuilder.GC.PER_PIXEL);
+ final Product product = pb.create();
+ product.setProductType("ENV_AT_1_RBT");
+ product.addBand("solar_zenith_tn", ProductData.TYPE_FLOAT32);
+ product.addBand("solar_azimuth_tn", ProductData.TYPE_FLOAT32);
+ product.addBand("sat_zenith_tn", ProductData.TYPE_FLOAT32);
+ product.addBand("confidence_in", ProductData.TYPE_FLOAT32);
+ product.addBand("elevation_in", ProductData.TYPE_FLOAT32);
+ product.addBand("cloud_in", ProductData.TYPE_FLOAT32);
+ product.addBand("latitude_tx", ProductData.TYPE_FLOAT32);
+ product.addBand("longitude_tx", ProductData.TYPE_FLOAT32);
+ product.addBand("x_tx", ProductData.TYPE_FLOAT32);
+ return product;
+ }
+
+}
diff --git a/idepix-aatsr/src/test/java/org/esa/snap/idepix/aatsr/OrientationOpImageTest.java b/idepix-aatsr/src/test/java/org/esa/snap/idepix/aatsr/OrientationOpImageTest.java
new file mode 100644
index 00000000..7e900704
--- /dev/null
+++ b/idepix-aatsr/src/test/java/org/esa/snap/idepix/aatsr/OrientationOpImageTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2022. Brockmann Consult GmbH (info@brockmann-consult.de)
+ *
+ * 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.snap.idepix.aatsr;
+
+import org.esa.snap.core.datamodel.ProductData;
+import org.esa.snap.core.util.ImageUtils;
+import org.junit.Test;
+
+import java.awt.image.RenderedImage;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Marco Peters
+ */
+public class OrientationOpImageTest {
+
+
+ @Test
+ public void testOrientationImage() {
+ float[] lats = {
+ 56.0214f, 55.7504f, 55.4608f, 56.0214f,
+ 55.1538f, 55.7504f, 55.4608f, 55.1538f,
+ 57.3770f, 57.3770f, 57.1410f, 56.8849f,
+ 57.1410f, 56.6093f, 56.3146f, 56.8849f,
+ };
+ float[] lons = {
+ -175.4993f, -177.0226f, -178.5242f, -175.4993f,
+ +179.9959f, -177.0226f, -178.5242f, -179.9959f,
+ -172.8034f, -174.4020f, -175.9795f, -174.4020f,
+ -177.5348f, -179.0667f, -175.9795f, +179.4245f
+ };
+
+ final RenderedImage latImage = ImageUtils.createRenderedImage(4, 4, ProductData.createInstance(lats));
+ final RenderedImage lonImage = ImageUtils.createRenderedImage(4, 4, ProductData.createInstance(lons));
+ final OrientationOpImage orientation = new OrientationOpImage(latImage, lonImage);
+ assertEquals(4, orientation.getWidth());
+ assertEquals(4, orientation.getHeight());
+
+ assertEquals(+162.342737, orientation.getData().getSampleDouble(0,0,0), 1.0e-6);
+ assertEquals(-179.914133, orientation.getData().getSampleDouble(1,1,0), 1.0e-6);
+ assertEquals(90.0, orientation.getData().getSampleDouble(2,2,0), 1.0e-6);
+ assertEquals(-0.165765, orientation.getData().getSampleDouble(3,3,0), 1.0e-6);
+ }
+
+ @Test
+ public void computeOrientation() {
+ assertEquals(-45.004363, OrientationOpImage.computeOrientation(1, 2, 2, 3f), 1.0e-6);
+ assertEquals(-26.917511, OrientationOpImage.computeOrientation(10f, 10f, 11f, 12f), 1.0e-6);
+ }
+
+}
\ No newline at end of file
diff --git a/idepix-aatsr/src/test/java/org/esa/snap/idepix/aatsr/RunOpMain.java b/idepix-aatsr/src/test/java/org/esa/snap/idepix/aatsr/RunOpMain.java
new file mode 100644
index 00000000..eedc5d42
--- /dev/null
+++ b/idepix-aatsr/src/test/java/org/esa/snap/idepix/aatsr/RunOpMain.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2022. Brockmann Consult GmbH (info@brockmann-consult.de)
+ *
+ * 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.snap.idepix.aatsr;
+
+import org.esa.snap.core.dataio.ProductIO;
+import org.esa.snap.core.datamodel.Product;
+import org.esa.snap.core.gpf.GPF;
+import org.esa.snap.core.util.SystemUtils;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.logging.Level;
+
+/**
+ * @author Marco Peters
+ */
+public class RunOpMain {
+
+ public static void main(String[] args) throws IOException {
+ SystemUtils.init3rdPartyLibs(RunOpMain.class);
+// final Product aatsr = ProductIO.readProduct("H:/SENTINEL3/AATSR4RP/v2.0.5/ENV_AT_1_RBT____20021129T235200_20021130T013735_20210315T024827_6334_011_359______DSI_R_NT_004.SEN3/xfdumanifest.xml");
+// final Product aatsr = ProductIO.readProduct("H:\\related\\QA4EO\\AATSR4th Cloud Shadow\\ENV_AT_1_RBT____20020810T083508_20020810T102042_20210303T040313_6334_008_264______DSI_R_NT_004.dim");
+ final Product aatsr = ProductIO.readProduct("H:\\related\\QA4EO\\AATSR4th Cloud Shadow\\ENV_AT_1_RBT____20090615T133401_20090615T151936_20210625T140648_6334_079_496______DSI_R_NT_004.SEN3");
+
+ Instant start = Instant.now();
+
+ final HashMap parameters = new HashMap<>();
+ parameters.put("copySourceBands", false);
+ parameters.put("cloudTopHeight", 6000);
+ final Product shadowProduct = GPF.createProduct("Idepix.Aatsr", parameters, aatsr);
+ ProductIO.writeProduct(shadowProduct, "H:\\related\\QA4EO\\AATSR4th Cloud Shadow\\" + aatsr.getName() + "_shadowOnly.dim", "BEAM-DIMAP");
+ Instant stop = Instant.now();
+ SystemUtils.LOG.log(Level.INFO, "DURATION: " + Duration.between(start, stop));
+ }
+}
diff --git a/pom.xml b/pom.xml
index 83665b3a..45ccb6d0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,8 +35,10 @@
+
idepix-core
+
idepix-landsat8
idepix-meris