From e1f3441669de6a056c14b7ff47bc730e46d74030 Mon Sep 17 00:00:00 2001 From: Benjamin Lutz Date: Mon, 24 Nov 2025 14:22:06 +0100 Subject: [PATCH 01/11] first implementation of cimr reader --- cimr-reader/pom.xml | 70 ++++ .../esa/snap/cimr/CimrL1BProductReader.java | 94 +++++ .../snap/cimr/CimrL1BProductReaderPlugin.java | 71 ++++ .../eu/esa/snap/cimr/CimrReaderContext.java | 76 ++++ .../snap/cimr/cimr/CimrBandDescriptor.java | 64 +++ .../snap/cimr/cimr/CimrDescriptorKind.java | 7 + .../esa/snap/cimr/cimr/CimrDescriptorSet.java | 41 ++ .../eu/esa/snap/cimr/cimr/CimrDimensions.java | 37 ++ .../esa/snap/cimr/cimr/CimrFrequencyBand.java | 9 + .../esa/snap/cimr/cimr/CimrGridBuilder.java | 27 ++ .../cimr/cimr/CimrGridMultiLevelSource.java | 39 ++ .../esa/snap/cimr/cimr/CimrGridOpImage.java | 69 ++++ .../esa/snap/cimr/cimr/CimrGridProduct.java | 63 +++ .../cimr/cimr/CimrSnapProductBuilder.java | 66 +++ .../esa/snap/cimr/config/CimrBandEntry.java | 13 + .../eu/esa/snap/cimr/config/CimrConfig.java | 36 ++ .../snap/cimr/config/CimrConfigLoader.java | 56 +++ .../java/eu/esa/snap/cimr/grid/CimrBand.java | 12 + .../eu/esa/snap/cimr/grid/CimrGeometry.java | 12 + .../esa/snap/cimr/grid/CimrGeometryBand.java | 63 +++ .../snap/cimr/grid/CimrTiepointGeometry.java | 87 ++++ .../cimr/grid/GeometryBandToGridMapper.java | 65 +++ .../eu/esa/snap/cimr/grid/GlobalGrid.java | 40 ++ .../cimr/grid/GlobalGridBandDataSource.java | 56 +++ .../esa/snap/cimr/grid/GlobalGridFactory.java | 18 + .../snap/cimr/grid/GridBandDataSource.java | 8 + .../eu/esa/snap/cimr/grid/GridProjection.java | 23 ++ .../cimr/grid/LazyGridBandDataSource.java | 47 +++ .../snap/cimr/grid/PlateCarreeProjection.java | 102 +++++ .../java/eu/esa/snap/cimr/netcdf/NcUtil.java | 27 ++ .../cimr/netcdf/NetcdfCimrBandFactory.java | 91 +++++ .../netcdf/NetcdfCimrGeometryFactory.java | 110 +++++ cimr-reader/src/main/nbm/manifest.mf | 7 + ...g.esa.snap.core.dataio.ProductReaderPlugIn | 1 + .../esa/snap/cimr/config/cimr-l1b-config.json | 94 +++++ .../eu/esa/snap/cimr/config/test-config.json | 94 +++++ .../cimr/CimrL1BProductReaderPluginTest.java | 95 +++++ .../esa/snap/cimr/CimrReaderContextTest.java | 328 +++++++++++++++ .../snap/cimr/cimr/CimrDescriptorSetTest.java | 102 +++++ .../snap/cimr/cimr/CimrDimensionsTest.java | 64 +++ .../snap/cimr/cimr/CimrGridBuilderTest.java | 65 +++ .../cimr/CimrGridMultiLevelSourceTest.java | 98 +++++ .../snap/cimr/cimr/CimrGridOpImageTest.java | 148 +++++++ .../snap/cimr/cimr/CimrGridProductTest.java | 123 ++++++ .../cimr/cimr/CimrSnapProductBuilderTest.java | 159 ++++++++ .../cimr/config/CimrConfigLoaderTest.java | 72 ++++ .../snap/cimr/grid/CimrGeometryBandTest.java | 113 ++++++ .../snap/cimr/grid/CimrGridBuilderTest.java | 82 ++++ .../cimr/grid/CimrTiepointGeometryTest.java | 139 +++++++ .../grid/GeometryBandToGridMapperTest.java | 76 ++++ .../grid/GlobalGridBandDataSourceTest.java | 63 +++ .../eu/esa/snap/cimr/grid/GlobalGridTest.java | 45 +++ .../cimr/grid/LazyGridBandDataSourceTest.java | 104 +++++ .../cimr/grid/PlateCarreeProjectionTest.java | 86 ++++ .../eu/esa/snap/cimr/netcdf/NcUtilTest.java | 63 +++ .../netcdf/NetcdfCimrBandFactoryTest.java | 218 ++++++++++ .../netcdf/NetcdfCimrGeometryFactoryTest.java | 380 ++++++++++++++++++ pom.xml | 1 + 58 files changed, 4419 insertions(+) create mode 100644 cimr-reader/pom.xml create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReaderPlugin.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/CimrReaderContext.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrBandDescriptor.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrDescriptorKind.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrDescriptorSet.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrDimensions.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFrequencyBand.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridBuilder.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSource.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridOpImage.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridProduct.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilder.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrBandEntry.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrConfig.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrConfigLoader.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrBand.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGeometry.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGeometryBand.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrTiepointGeometry.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GeometryBandToGridMapper.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGrid.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridBandDataSource.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridFactory.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GridBandDataSource.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GridProjection.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/grid/LazyGridBandDataSource.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/grid/PlateCarreeProjection.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NcUtil.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactory.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactory.java create mode 100644 cimr-reader/src/main/nbm/manifest.mf create mode 100644 cimr-reader/src/main/resources/META-INF/services/org.esa.snap.core.dataio.ProductReaderPlugIn create mode 100644 cimr-reader/src/main/resources/eu/esa/snap/cimr/config/cimr-l1b-config.json create mode 100644 cimr-reader/src/main/resources/eu/esa/snap/cimr/config/test-config.json create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/CimrL1BProductReaderPluginTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrDescriptorSetTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrDimensionsTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridBuilderTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSourceTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridOpImageTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridProductTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilderTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/config/CimrConfigLoaderTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGeometryBandTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridBuilderTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrTiepointGeometryTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GeometryBandToGridMapperTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GlobalGridBandDataSourceTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GlobalGridTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyGridBandDataSourceTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/grid/PlateCarreeProjectionTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NcUtilTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactoryTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactoryTest.java diff --git a/cimr-reader/pom.xml b/cimr-reader/pom.xml new file mode 100644 index 000000000..58d10ccbd --- /dev/null +++ b/cimr-reader/pom.xml @@ -0,0 +1,70 @@ + + 4.0.0 + + eu.esa.microwavetbx + microwave-toolbox + 14.0.0-SNAPSHOT + + + eu.esa.snap.cimr + cimr-reader + CIMR Reader + + nbm + + + 5.3.3 + + + + + org.esa.snap + snap-core + + + org.esa.snap + ceres-core + + + org.esa.snap + ceres-jai + + + org.esa.snap + snap-netcdf + + + edu.ucar + netcdfAll + ${netcdf.version} + + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + + + eu.esa.snap.cimr.* + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java new file mode 100644 index 000000000..6fe3e3a90 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java @@ -0,0 +1,94 @@ +package eu.esa.snap.cimr; + +import com.bc.ceres.core.ProgressMonitor; +import eu.esa.snap.cimr.cimr.*; +import eu.esa.snap.cimr.config.CimrConfigLoader; +import eu.esa.snap.cimr.grid.GlobalGrid; +import eu.esa.snap.cimr.grid.GlobalGridFactory; +import eu.esa.snap.cimr.netcdf.NetcdfCimrGeometryFactory; +import eu.esa.snap.cimr.netcdf.NetcdfCimrBandFactory; +import org.esa.snap.core.dataio.AbstractProductReader; +import org.esa.snap.core.dataio.ProductReaderPlugIn; +import org.esa.snap.core.datamodel.Band; +import org.esa.snap.core.datamodel.Product; +import org.esa.snap.core.datamodel.ProductData; +import org.esa.snap.dataio.netcdf.util.NetcdfFileOpener; +import ucar.nc2.NetcdfFile; + +import java.io.File; +import java.io.IOException; + + +public class CimrL1BProductReader extends AbstractProductReader { + + NetcdfFile ncFile; + CimrReaderContext readerContext; + + + public CimrL1BProductReader(ProductReaderPlugIn readerPlugIn) { + super(readerPlugIn); + } + + @Override + protected Product readProductNodesImpl() throws IOException { + final String path = getInputPath(); + + try { + this.ncFile = NetcdfFileOpener.open(path); + assert this.ncFile != null; + + this.readerContext = initContext(this.ncFile); + CimrGridProduct cimrGridProduct = CimrGridProduct.buildLazy(this.readerContext, true); + + // TODO: name and type from Metadata + Product snapProduct = CimrSnapProductBuilder.buildProduct("CIMR_L1B", "CIMR_L1B", cimrGridProduct, path); + + return snapProduct; + + } catch (Exception e) { + throw new IOException("Failed to read CIMR product from " + path, e); + } + } + + @Override + protected void readBandRasterDataImpl(int sourceOffsetX, int sourceOffsetY, int sourceWidth, int sourceHeight, int sourceStepX, int sourceStepY, Band destBand, int destOffsetX, int destOffsetY, int destWidth, int destHeight, ProductData destBuffer, ProgressMonitor pm) throws IOException { + // TODO add direct exception here, this should not be called + } + + @Override + public void close() throws IOException { + if (this.ncFile != null) { + this.ncFile.close(); + this.ncFile = null; + } + if (this.readerContext != null) { + this.readerContext.clearCache(); + this.readerContext = null; + } + super.close(); + } + + + private String getInputPath() { + Object input = getInput(); + if (!(input instanceof String || input instanceof File)) { + throw new IllegalArgumentException("Unsupported input: " + input); + } + + if (input instanceof File) { + return ((File) input).getPath(); + } + return (String) input; + } + + private CimrReaderContext initContext(NetcdfFile ncFile) throws IOException { + CimrDescriptorSet descriptorSet = CimrConfigLoader.load("cimr-l1b-config.json"); + CimrDimensions dimensions = CimrDimensions.from(ncFile); + + GlobalGrid globalGrid = GlobalGridFactory.createGlobalPlateCarree(0.1); + NetcdfCimrGeometryFactory geometryFactory = new NetcdfCimrGeometryFactory(ncFile, descriptorSet.getGeometries(), dimensions); + NetcdfCimrBandFactory bandFactory = new NetcdfCimrBandFactory(ncFile, dimensions); + + return new CimrReaderContext(ncFile, descriptorSet, globalGrid, geometryFactory, bandFactory); + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReaderPlugin.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReaderPlugin.java new file mode 100644 index 000000000..1c96715f8 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReaderPlugin.java @@ -0,0 +1,71 @@ +package eu.esa.snap.cimr; + +import org.esa.snap.core.dataio.DecodeQualification; +import org.esa.snap.core.dataio.ProductReader; +import org.esa.snap.core.dataio.ProductReaderPlugIn; +import org.esa.snap.core.util.io.FileUtils; +import org.esa.snap.core.util.io.SnapFileFilter; + +import java.io.File; +import java.util.Locale; + + +public class CimrL1BProductReaderPlugin implements ProductReaderPlugIn { + + private static final String EXTENSION = ".nc"; + private static final String NAME_PATTERN = "^W_[A-Za-z]{2}-[A-Za-z]{2,3}+-[A-Za-z]{1,11}+-SAT-CIMR-1B_C_(?:DME|ESA)_\\d{8}T\\d{6}_[A-Z]{1,2}+_\\d{8}T\\d{6}_\\d{8}T\\d{6}_[A-Z0-9_]{0,3}\\.nc$"; + + + @Override + public DecodeQualification getDecodeQualification(Object input) { + final File file = input instanceof File ? (File) input : new File(input.toString()); + final String fileName = file.getName(); + + final String extension = FileUtils.getExtension(fileName); + if (!EXTENSION.equals(extension)) { + return DecodeQualification.UNABLE; + } + + if (isValidCimrL1BProduct(fileName)) { + return DecodeQualification.INTENDED; + } + + return DecodeQualification.UNABLE; + } + + @Override + public Class[] getInputTypes() { + return new Class[]{File.class, String.class}; + } + + @Override + public ProductReader createReaderInstance() { + return new CimrL1BProductReader(this); + } + + + @Override + public String[] getFormatNames() { + return new String[]{"CIMR-L1B"}; + } + + @Override + public String[] getDefaultFileExtensions() { + return new String[]{EXTENSION}; + } + + @Override + public String getDescription(Locale locale) { + return "CIMR Level 1B Data Products in NetCDF Format"; + } + + @Override + public SnapFileFilter getProductFileFilter() { + return new SnapFileFilter(getFormatNames()[0], getDefaultFileExtensions(), getDescription(null)); + } + + + private boolean isValidCimrL1BProduct(String fileName) { + return fileName.matches(NAME_PATTERN); + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrReaderContext.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrReaderContext.java new file mode 100644 index 000000000..b43416d02 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrReaderContext.java @@ -0,0 +1,76 @@ +package eu.esa.snap.cimr; + +import eu.esa.snap.cimr.cimr.CimrBandDescriptor; +import eu.esa.snap.cimr.cimr.CimrDescriptorSet; +import eu.esa.snap.cimr.cimr.CimrGridBuilder; +import eu.esa.snap.cimr.grid.*; +import eu.esa.snap.cimr.netcdf.NetcdfCimrGeometryFactory; +import eu.esa.snap.cimr.netcdf.NetcdfCimrBandFactory; +import ucar.ma2.InvalidRangeException; +import ucar.nc2.NetcdfFile; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + +public class CimrReaderContext { + + private final NetcdfFile ncFile; + private final CimrDescriptorSet descriptorSet; + private final GlobalGrid globalGrid; + private final GeometryBandToGridMapper mapper; + private final NetcdfCimrGeometryFactory geometryFactory; + private final NetcdfCimrBandFactory bandFactory; + + private final Map bandCache = new ConcurrentHashMap<>(); + + + public CimrReaderContext(NetcdfFile ncFile, + CimrDescriptorSet descriptorSet, + GlobalGrid globalGrid, + NetcdfCimrGeometryFactory geomFactory, + NetcdfCimrBandFactory bandFactory) { + this.ncFile = ncFile; + this.descriptorSet = descriptorSet; + this.globalGrid = globalGrid; + this.mapper = new GeometryBandToGridMapper(); + this.geometryFactory = geomFactory; + this.bandFactory = bandFactory; + } + + + public GlobalGrid getGlobalGrid() { + return this.globalGrid; + } + + public CimrDescriptorSet getDescriptorSet() { + return this.descriptorSet; + } + + public GridBandDataSource getOrCreateGridForVariable(CimrBandDescriptor varDesc, boolean useAverage) { + return this.bandCache.computeIfAbsent(varDesc, d -> { + try { + CimrGeometry geom = getOrCreateGeometry(d); + CimrGeometryBand geometryBand = this.bandFactory.createGeometryBand(d, geom); + CimrGridBuilder gridBuilder = new CimrGridBuilder(this.mapper); + return gridBuilder.build(geometryBand, this.globalGrid, useAverage); + } catch (IOException | InvalidRangeException e) { + throw new RuntimeException("Failed to build grid for variable " + d.getName(), e); + } + }); + } + + public CimrGeometry getOrCreateGeometry(CimrBandDescriptor varDesc) { + try { + return this.geometryFactory.getOrCreateGeometry(varDesc); + } catch (IOException | InvalidRangeException e) { + throw new RuntimeException("Failed to build geometry for variable " + varDesc.getName(), e); + } + } + + public void clearCache() { + this.bandCache.clear(); + this.geometryFactory.clearCache(); + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrBandDescriptor.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrBandDescriptor.java new file mode 100644 index 000000000..068deb56e --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrBandDescriptor.java @@ -0,0 +1,64 @@ +package eu.esa.snap.cimr.cimr; + + +public class CimrBandDescriptor { + + private final String name; + private final String valueVarName; + private final CimrFrequencyBand band; + private final String[] geometryNames; + private final String groupPath; + private final int feedIndex; + private final CimrDescriptorKind kind; + private final String[] dimensions; + private final String dataType; + + + public CimrBandDescriptor(String name, String valueVarName, CimrFrequencyBand band, String[] geometryNames, String groupPath, int feedIndex, CimrDescriptorKind kind, String[] dimensions, String dataType) { + this.name = name; + this.valueVarName = valueVarName; + this.band = band; + this.geometryNames = geometryNames; + this.groupPath = groupPath; + this.feedIndex = feedIndex; + this.kind = kind; + this.dimensions = dimensions; + this.dataType = dataType; + } + + public String getName() { + return name; + } + + public String getValueVarName() { + return valueVarName; + } + + public CimrFrequencyBand getBand() { + return band; + } + + public String[] getGeometryNames() { + return geometryNames; + } + + public String getGroupPath() { + return groupPath; + } + + public int getFeedIndex() { + return feedIndex; + } + + public CimrDescriptorKind getKind() { + return kind; + } + + public String[] getDimensions() { + return dimensions; + } + + public String getDataType() { + return dataType; + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrDescriptorKind.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrDescriptorKind.java new file mode 100644 index 000000000..7029e7342 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrDescriptorKind.java @@ -0,0 +1,7 @@ +package eu.esa.snap.cimr.cimr; + +public enum CimrDescriptorKind { + VARIABLE, + TIEPOINT_VARIABLE, + GEOMETRY +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrDescriptorSet.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrDescriptorSet.java new file mode 100644 index 000000000..05ce4e0ab --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrDescriptorSet.java @@ -0,0 +1,41 @@ +package eu.esa.snap.cimr.cimr; + +import java.util.List; + + +public class CimrDescriptorSet { + + private final List measurements; + private final List geometries; + private final List tiepointVariables; + + + public CimrDescriptorSet(List measurements, + List geometries, + List tiepointVariables) { + this.measurements = measurements; + this.geometries = geometries; + this.tiepointVariables = tiepointVariables; + } + + public CimrBandDescriptor getGeometryByName(String name) { + for (CimrBandDescriptor descriptor : geometries) { + if (descriptor.getName().equals(name)) { + return descriptor; + } + } + return null; + } + + public List getMeasurements() { + return measurements; + } + + public List getGeometries() { + return geometries; + } + + public List getTiepointVariables() { + return tiepointVariables; + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrDimensions.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrDimensions.java new file mode 100644 index 000000000..110d3c280 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrDimensions.java @@ -0,0 +1,37 @@ +package eu.esa.snap.cimr.cimr; + +import ucar.nc2.Dimension; +import ucar.nc2.NetcdfFile; + +import java.util.HashMap; +import java.util.Map; + + +public class CimrDimensions { + + private final Map values = new HashMap<>(); + + public CimrDimensions() { + } + + public CimrDimensions(Map valMap) { + this.values.putAll(valMap); + } + + + public static CimrDimensions from(NetcdfFile ncFile) { + CimrDimensions dims = new CimrDimensions(); + for (Dimension dim : ncFile.getDimensions()) { + dims.values.put(dim.getShortName(), dim.getLength()); + } + return dims; + } + + public int get(String name) { + Integer v = values.get(name); + if (v == null) { + throw new IllegalArgumentException("Unknown dimension: " + name); + } + return v; + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFrequencyBand.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFrequencyBand.java new file mode 100644 index 000000000..00779f97c --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFrequencyBand.java @@ -0,0 +1,9 @@ +package eu.esa.snap.cimr.cimr; + +public enum CimrFrequencyBand { + L_BAND, + C_BAND, + X_BAND, + KU_BAND, + KA_BAND +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridBuilder.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridBuilder.java new file mode 100644 index 000000000..1130b38fa --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridBuilder.java @@ -0,0 +1,27 @@ +package eu.esa.snap.cimr.cimr; + +import eu.esa.snap.cimr.grid.CimrBand; +import eu.esa.snap.cimr.grid.GlobalGrid; +import eu.esa.snap.cimr.grid.GlobalGridBandDataSource; +import eu.esa.snap.cimr.grid.GeometryBandToGridMapper; + + +public class CimrGridBuilder { + + private final GeometryBandToGridMapper mapper; + + + public CimrGridBuilder(GeometryBandToGridMapper mapper) { + this.mapper = mapper; + } + + public GlobalGridBandDataSource build(CimrBand band, GlobalGrid grid, boolean useAverage) { + GlobalGridBandDataSource target = GlobalGridBandDataSource.createEmpty(grid.getWidth(), grid.getHeight()); + if (useAverage) { + mapper.mapAverage(band, grid, target); + } else { + mapper.mapNearest(band, grid, target); + } + return target; + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSource.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSource.java new file mode 100644 index 000000000..1f14fd280 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSource.java @@ -0,0 +1,39 @@ +package eu.esa.snap.cimr.cimr; + +import com.bc.ceres.multilevel.MultiLevelModel; +import com.bc.ceres.multilevel.MultiLevelSource; +import com.bc.ceres.multilevel.support.AbstractMultiLevelSource; +import com.bc.ceres.multilevel.support.DefaultMultiLevelImage; +import eu.esa.snap.cimr.grid.GridBandDataSource; +import org.esa.snap.core.datamodel.Band; +import org.esa.snap.core.image.ResolutionLevel; + +import java.awt.image.RenderedImage; + + +public class CimrGridMultiLevelSource extends AbstractMultiLevelSource { + + private final Band targetBand; + private final GridBandDataSource gridDataSource; + + + public CimrGridMultiLevelSource(MultiLevelModel multiLevelModel, Band targetBand, + GridBandDataSource gridDataSource) { + super(multiLevelModel); + this.targetBand = targetBand; + this.gridDataSource = gridDataSource; + } + + @Override + protected RenderedImage createImage(int level) { + ResolutionLevel resLevel = ResolutionLevel.create(getModel(), level); + return new CimrGridOpImage(targetBand, resLevel, gridDataSource); + } + + public static void attachToBand(Band band, + GridBandDataSource gridDataSource, + MultiLevelModel model) { + MultiLevelSource source = new CimrGridMultiLevelSource(model, band, gridDataSource); + band.setSourceImage(new DefaultMultiLevelImage(source)); + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridOpImage.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridOpImage.java new file mode 100644 index 000000000..1a7986cb1 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridOpImage.java @@ -0,0 +1,69 @@ +package eu.esa.snap.cimr.cimr; + +import eu.esa.snap.cimr.grid.GridBandDataSource; +import org.esa.snap.core.datamodel.ProductData; +import org.esa.snap.core.datamodel.RasterDataNode; +import org.esa.snap.core.image.RasterDataNodeOpImage; +import org.esa.snap.core.image.ResolutionLevel; + +import java.awt.*; + + +public class CimrGridOpImage extends RasterDataNodeOpImage { + + private final GridBandDataSource gridDataSource; + + + public CimrGridOpImage(RasterDataNode rasterDataNode, ResolutionLevel level, GridBandDataSource gridBandDataSource) { + super(rasterDataNode, level); + this.gridDataSource = gridBandDataSource; + } + + + @Override + protected void computeProductData(ProductData productData, Rectangle region) { + int w = region.width; + int h = region.height; + + int levelIndex = getLevel(); + double scale = Math.pow(2.0, levelIndex); + int blockSize = (int) Math.round(scale); + + int baseWidth = getRasterDataNode().getRasterWidth(); + int baseHeight = getRasterDataNode().getRasterHeight(); + + int idx = 0; + for (int dy = 0; dy < h; dy++) { + int yLevel = region.y + dy; + int y0 = (int) Math.floor(yLevel * scale); + int y1 = Math.min(y0 + blockSize, baseHeight); + + for (int dx = 0; dx < w; dx++) { + int xLevel = region.x + dx; + int x0 = (int) Math.floor(xLevel * scale); + int x1 = Math.min(x0 + blockSize, baseWidth); + + double sum = 0.0; + int count = 0; + + for (int yy = y0; yy < y1; yy++) { + for (int xx = x0; xx < x1; xx++) { + double v; + try { + v = gridDataSource.getSample(xx, yy); + } catch (IllegalArgumentException e) { + v = Double.NaN; + } + if (!Double.isNaN(v)) { + sum += v; + count++; + } + } + } + + double out = (count > 0) ? (sum / count) : Double.NaN; + productData.setElemDoubleAt(idx++, out); + } + } + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridProduct.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridProduct.java new file mode 100644 index 000000000..c52a19f82 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridProduct.java @@ -0,0 +1,63 @@ +package eu.esa.snap.cimr.cimr; + +import eu.esa.snap.cimr.CimrReaderContext; +import eu.esa.snap.cimr.grid.GlobalGrid; +import eu.esa.snap.cimr.grid.GridBandDataSource; +import eu.esa.snap.cimr.grid.LazyGridBandDataSource; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + + +public class CimrGridProduct { + + private final GlobalGrid globalGrid; + private final Map bands = new LinkedHashMap<>(); + + + public CimrGridProduct(GlobalGrid globalGrid) { + this.globalGrid = globalGrid; + } + + + public GlobalGrid getGlobalGrid() { + return globalGrid; + } + + public void addBand(CimrBandDescriptor descriptor, GridBandDataSource dataSource) { + bands.put(descriptor, dataSource); + } + + public GridBandDataSource getBandData(CimrBandDescriptor descriptor) { + return bands.get(descriptor); + } + + public Map getBands() { + return Collections.unmodifiableMap(bands); + } + + public int getBandCount() { + return bands.size(); + } + + + public static CimrGridProduct buildLazy(CimrReaderContext context, boolean useAverage) { + GlobalGrid globalGrid = context.getGlobalGrid(); + CimrDescriptorSet descriptorSet = context.getDescriptorSet(); + + CimrGridProduct product = new CimrGridProduct(globalGrid); + + for (CimrBandDescriptor desc : descriptorSet.getTiepointVariables()) { + GridBandDataSource dataSource = new LazyGridBandDataSource(context, desc, useAverage); + product.addBand(desc, dataSource); + } + + for (CimrBandDescriptor desc : descriptorSet.getMeasurements()) { + GridBandDataSource dataSource = new LazyGridBandDataSource(context, desc, useAverage); + product.addBand(desc, dataSource); + } + + return product; + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilder.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilder.java new file mode 100644 index 000000000..251df84a7 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilder.java @@ -0,0 +1,66 @@ +package eu.esa.snap.cimr.cimr; + +import com.bc.ceres.multilevel.MultiLevelModel; +import com.bc.ceres.multilevel.support.DefaultMultiLevelModel; +import eu.esa.snap.cimr.grid.GlobalGrid; +import eu.esa.snap.cimr.grid.GridBandDataSource; +import org.esa.snap.core.datamodel.Band; +import org.esa.snap.core.datamodel.Product; +import org.esa.snap.core.datamodel.ProductData; +import org.esa.snap.core.datamodel.GeoCoding; +import org.esa.snap.core.datamodel.CrsGeoCoding; +import org.opengis.referencing.FactoryException; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.TransformException; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.io.File; +import java.util.Map; + + +public class CimrSnapProductBuilder { + + private static final String AUTO_GROUPING = "L_BAND:C_BAND:X_BAND:KU_BAND:KA_BAND"; + + + public static Product buildProduct(String productName, String productType, CimrGridProduct cimrProduct, String path) throws Exception { + GlobalGrid grid = cimrProduct.getGlobalGrid(); + int width = grid.getWidth(); + int height = grid.getHeight(); + + Product product = new Product(productName, productType, width, height); + + addGeoCoding(grid, width, height, product); + addBands(cimrProduct, width, height, product); + + product.setFileLocation(new File(path)); + product.setAutoGrouping(AUTO_GROUPING); + + return product; + } + + private static void addGeoCoding(GlobalGrid grid, int width, int height, Product product) throws FactoryException, TransformException { + CoordinateReferenceSystem crs = grid.getProjection().getCrs(); + AffineTransform imageToModel = grid.getProjection().getAffineTransform(grid); + + GeoCoding geoCoding = new CrsGeoCoding(crs, new Rectangle(width, height), imageToModel); + product.setSceneGeoCoding(geoCoding); + } + + + private static void addBands(CimrGridProduct cimrProduct, int width, int height, Product product) { + int levelCount = 7; + AffineTransform imageToModel = (AffineTransform) product.getSceneGeoCoding().getImageToMapTransform(); + MultiLevelModel mlModel = new DefaultMultiLevelModel(levelCount, imageToModel, width, height); + + for (Map.Entry e : cimrProduct.getBands().entrySet()) { + CimrBandDescriptor desc = e.getKey(); + GridBandDataSource dataSource = e.getValue(); + + Band band = product.addBand(desc.getName(), ProductData.TYPE_FLOAT64); + // TODO set noDataValue, description, unit, spectral_wavelength on band + CimrGridMultiLevelSource.attachToBand(band, dataSource, mlModel); + } + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrBandEntry.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrBandEntry.java new file mode 100644 index 000000000..097135b46 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrBandEntry.java @@ -0,0 +1,13 @@ +package eu.esa.snap.cimr.config; + +public class CimrBandEntry { + + public String name; + public String valueVarName; + public String band; + public String[] geometryNames; + public String groupPath; + public int feedIndex; + public String[] dimensions; + public String dataType; +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrConfig.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrConfig.java new file mode 100644 index 000000000..6f47a8a93 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrConfig.java @@ -0,0 +1,36 @@ +package eu.esa.snap.cimr.config; + +import java.util.List; + + +public class CimrConfig { + + private List variables; + private List tiepointVariables; + private List geometries; + + + public List getVariables() { + return variables; + } + + public List getTiepointVariables() { + return tiepointVariables; + } + + public List getGeometries() { + return geometries; + } + + public void setVariables(List variables) { + this.variables = variables; + } + + public void setTiepointVariables(List tiepointVariables) { + this.tiepointVariables = tiepointVariables; + } + + public void setGeometries(List geometries) { + this.geometries = geometries; + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrConfigLoader.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrConfigLoader.java new file mode 100644 index 000000000..a58d6b1b3 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrConfigLoader.java @@ -0,0 +1,56 @@ +package eu.esa.snap.cimr.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.esa.snap.cimr.cimr.CimrBandDescriptor; +import eu.esa.snap.cimr.cimr.CimrDescriptorKind; +import eu.esa.snap.cimr.cimr.CimrDescriptorSet; +import eu.esa.snap.cimr.cimr.CimrFrequencyBand; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + + +public class CimrConfigLoader { + + + public static CimrDescriptorSet load(String jsonPath) throws IOException { + try (InputStream in = CimrConfigLoader.class.getResourceAsStream(jsonPath)) { + if (in == null) { + throw new IOException("Config resource 'cimr-config.json' not found on classpath"); + } + + ObjectMapper mapper = new ObjectMapper(); + CimrConfig cfg = mapper.readValue(in, CimrConfig.class); + + List meas = cfg.getVariables().stream() + .map(e -> toDescriptor(e, CimrDescriptorKind.VARIABLE)) + .toList(); + + List tpVal = cfg.getTiepointVariables().stream() + .map(e -> toDescriptor(e, CimrDescriptorKind.TIEPOINT_VARIABLE)) + .toList(); + + List tpGeo = cfg.getGeometries().stream() + .map(e -> toDescriptor(e, CimrDescriptorKind.GEOMETRY)) + .toList(); + + return new CimrDescriptorSet(meas, tpGeo, tpVal); + } + } + + private static CimrBandDescriptor toDescriptor(CimrBandEntry e, CimrDescriptorKind kind) { + CimrFrequencyBand band = CimrFrequencyBand.valueOf(e.band); + return new CimrBandDescriptor( + e.name, + e.valueVarName, + band, + e.geometryNames, + e.groupPath, + e.feedIndex, + kind, + e.dimensions, + e.dataType + ); + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrBand.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrBand.java new file mode 100644 index 000000000..1587abd66 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrBand.java @@ -0,0 +1,12 @@ +package eu.esa.snap.cimr.grid; + +import org.esa.snap.core.datamodel.GeoPos; + + +public interface CimrBand { + + int getScanCount(); + int getSampleCount(); + double getValue(int scanIndex, int sampleIndex); + GeoPos getGeoPos(int scanIndex, int sampleIndex); +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGeometry.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGeometry.java new file mode 100644 index 000000000..dad0366c9 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGeometry.java @@ -0,0 +1,12 @@ +package eu.esa.snap.cimr.grid; + +import org.esa.snap.core.datamodel.GeoPos; + + +public interface CimrGeometry { + + int getScanCount(); + int getSampleCount(); + int getTiePointCount(); + GeoPos getGeoPos(int scanIndex, int sampleIndex, int feedIndex); +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGeometryBand.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGeometryBand.java new file mode 100644 index 000000000..bcbc670be --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGeometryBand.java @@ -0,0 +1,63 @@ +package eu.esa.snap.cimr.grid; + +import org.esa.snap.core.datamodel.GeoPos; + + +public class CimrGeometryBand implements CimrBand { + + private final double[][] values; + private final CimrGeometry geometry; + private final int feedIndex; + + + public CimrGeometryBand(double[][] values, + CimrGeometry geometry, + int feedIndex) { + if (values == null || geometry == null) { + throw new IllegalArgumentException("values and geometry must not be null"); + } + if (values.length == 0) { + throw new IllegalArgumentException("values must not be empty"); + } + int sampleCount = values[0].length; + for (double[] row : values) { + if (row.length != sampleCount) { + throw new IllegalArgumentException("all scan rows must have same sample count"); + } + } + if (geometry.getScanCount() != values.length) { + throw new IllegalArgumentException("geometry scanCount does not match values"); + } + if (geometry.getSampleCount() != sampleCount) { + throw new IllegalArgumentException("geometry sampleCount does not match values"); + } + + this.values = values; + this.geometry = geometry; + this.feedIndex = feedIndex; + } + + @Override + public int getScanCount() { + return values.length; + } + + @Override + public int getSampleCount() { + return values[0].length; + } + + @Override + public GeoPos getGeoPos(int scanIndex, int sampleIndex) { + return geometry.getGeoPos(scanIndex, sampleIndex, 0); + } + + @Override + public double getValue(int scanIndex, int sampleIndex) { + return values[scanIndex][sampleIndex]; + } + + public int getFeedIndex() { + return feedIndex; + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrTiepointGeometry.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrTiepointGeometry.java new file mode 100644 index 000000000..c177d1e31 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrTiepointGeometry.java @@ -0,0 +1,87 @@ +package eu.esa.snap.cimr.grid; + +import org.esa.snap.core.datamodel.GeoPos; + + +public class CimrTiepointGeometry implements CimrGeometry { + + private final int scanCount; + private final int sampleCount; + private final int tiePointCount; + private final GeoPos[][][] tiePoints; // [scan][tiePoint][feed] + + + public CimrTiepointGeometry(GeoPos[][][] tiePoints, int sampleCount) { + if (tiePoints.length == 0) { + throw new IllegalArgumentException("scan dimension must be > 0"); + } + int scans = tiePoints.length; + int tpCount = tiePoints[0].length; + if (tpCount == 0) { + throw new IllegalArgumentException("tiePoint dimension must be > 0"); + } + int feeds = tiePoints[0][0].length; + for (int s = 0; s < scans; s++) { + if (tiePoints[s].length != tpCount) { + throw new IllegalArgumentException("tiePoint dimension mismatch at scan " + s); + } + for (int tp = 0; tp < tpCount; tp++) { + if (tiePoints[s][tp].length != feeds) { + throw new IllegalArgumentException("feed dimension mismatch at scan " + s + ", tp " + tp); + } + } + } + if (sampleCount < 2) { + throw new IllegalArgumentException("sampleCount must be >= 2"); + } + + this.scanCount = scans; + this.tiePointCount = tpCount; + this.sampleCount = sampleCount; + this.tiePoints = tiePoints; + } + + @Override + public int getScanCount() { + return scanCount; + } + + @Override + public int getSampleCount() { + return sampleCount; + } + + @Override + public int getTiePointCount() { + return tiePointCount; + } + + @Override + public GeoPos getGeoPos(int scanIndex, int sampleIndex, int feedIndex) { + if (scanIndex < 0 || scanIndex >= scanCount) { + throw new IllegalArgumentException("scanIndex out of range: " + scanIndex); + } + if (sampleIndex < 0 || sampleIndex >= sampleCount) { + throw new IllegalArgumentException("sampleIndex out of range: " + sampleIndex); + } + if (feedIndex < 0 || feedIndex >= tiePoints[0][0].length) { + throw new IllegalArgumentException("feedIndex out of range: " + feedIndex); + } + + + // TODO: BL refactor interpolation method to be single class with methods for handling all cases + double t = (double) sampleIndex * (tiePointCount - 1) / (double) (sampleCount - 1); + + int tp0 = (int) Math.floor(t); + int tp1 = Math.min(tp0 + 1, tiePointCount - 1); + double f = t - tp0; + + GeoPos p0 = tiePoints[scanIndex][tp0][feedIndex]; + GeoPos p1 = tiePoints[scanIndex][tp1][feedIndex]; + + double lat = p0.getLat() + f * (p1.getLat() - p0.getLat()); + double lon = p0.getLon() + f * (p1.getLon() - p0.getLon()); + + return new GeoPos((float) lat, (float) lon); + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GeometryBandToGridMapper.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GeometryBandToGridMapper.java new file mode 100644 index 000000000..c634def7f --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GeometryBandToGridMapper.java @@ -0,0 +1,65 @@ +package eu.esa.snap.cimr.grid; + + +import org.esa.snap.core.datamodel.GeoPos; + +import java.awt.*; + + +public class GeometryBandToGridMapper { + + + public void mapNearest(CimrBand band, GlobalGrid grid, GridBandDataSource target) { + Point gridPoint = new Point(); + + for (int ss = 0; ss < band.getScanCount(); ss++) { + for (int cc = 0; cc < band.getSampleCount(); cc++) { + + GeoPos geoPos = band.getGeoPos(ss, cc); + boolean inside = grid.geoPosToGrid(geoPos, gridPoint); + + if (!inside) { + continue; + } + double value = band.getValue(ss, cc); + + target.setSample(gridPoint.x, gridPoint.y, value); + } + } + } + + public void mapAverage(CimrBand band, GlobalGrid grid, GridBandDataSource target) { + int width = grid.getWidth(); + int height = grid.getHeight(); + + double[] sum = new double[width * height]; + int[] count = new int[width * height]; + + Point p = new Point(); + + for (int ss = 0; ss < band.getScanCount(); ss++) { + for (int cc = 0; cc < band.getSampleCount(); cc++) { + GeoPos geo = band.getGeoPos(ss, cc); + if (!grid.geoPosToGrid(geo, p)) { + continue; + } + double value = band.getValue(ss, cc); + if (Double.isNaN(value)) { + continue; + } + int idx = p.y * width + p.x; + sum[idx] += value; + count[idx]++; + } + } + + for (int yy = 0; yy < height; yy++) { + for (int xx = 0; xx < width; xx++) { + int idx = yy * width + xx; + if (count[idx] > 0) { + target.setSample(xx, yy, sum[idx] / count[idx]); + } + } + } + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGrid.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGrid.java new file mode 100644 index 000000000..7cf3d0f05 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGrid.java @@ -0,0 +1,40 @@ +package eu.esa.snap.cimr.grid; + +import org.esa.snap.core.datamodel.GeoPos; + +import java.awt.*; + + +public class GlobalGrid { + + private int width; + private int height; + private final GridProjection projection; + + + public GlobalGrid(GridProjection projection, int width, int height) { + this.projection = projection; + this.height = height; + this.width = width; + } + + public GeoPos gridToGeoPos(int x, int y) { + return projection.gridToGeoPos(x, y); + } + + public boolean geoPosToGrid(GeoPos pos, Point out) { + return projection.geoPosToGrid(pos, out); + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public GridProjection getProjection() { + return projection; + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridBandDataSource.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridBandDataSource.java new file mode 100644 index 000000000..a1d0c3978 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridBandDataSource.java @@ -0,0 +1,56 @@ +package eu.esa.snap.cimr.grid; + +import java.util.Arrays; + + +public class GlobalGridBandDataSource implements GridBandDataSource { + + private final int width; + private final int height; + private final double[] data; + + + public GlobalGridBandDataSource(int width, int height, double[] data) { + if (data.length != width * height) { + throw new IllegalArgumentException("data length must be width * height"); + } + this.width = width; + this.height = height; + this.data = data; + } + + + public static GlobalGridBandDataSource createEmpty(int width, int height) { + double[] data = new double[width * height]; + Arrays.fill(data, Double.NaN); + return new GlobalGridBandDataSource(width, height, data); + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + @Override + public double getSample(int x, int y) { + checkBounds(x, y); + int index = y * width + x; + return data[index]; + } + + @Override + public void setSample(int x, int y, double value) { + checkBounds(x, y); + int index = y * width + x; + data[index] = value; + } + + private void checkBounds(int x, int y) { + if (x < 0 || x >= width || y < 0 || y >= height) { + throw new IllegalArgumentException("Grid index out of range: x=" + x + ", y=" + y); + } + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridFactory.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridFactory.java new file mode 100644 index 000000000..bcb93efd3 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridFactory.java @@ -0,0 +1,18 @@ +package eu.esa.snap.cimr.grid; + + +public class GlobalGridFactory { + + public static GlobalGrid createGlobalPlateCarree(double cellSizeDeg) { + int width = (int) Math.round(360.0 / cellSizeDeg); + int height = (int) Math.round(180.0 / cellSizeDeg); + + double lonMin = 0.0; + double latMax = 90.0; + + PlateCarreeProjection proj = new PlateCarreeProjection( + width, height, lonMin, latMax, cellSizeDeg, cellSizeDeg + ); + return new GlobalGrid(proj, width, height); + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GridBandDataSource.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GridBandDataSource.java new file mode 100644 index 000000000..d02d9accc --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GridBandDataSource.java @@ -0,0 +1,8 @@ +package eu.esa.snap.cimr.grid; + + +public interface GridBandDataSource { + + double getSample(int x, int y); + void setSample(int x, int y, double value); +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GridProjection.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GridProjection.java new file mode 100644 index 000000000..7448c35f4 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GridProjection.java @@ -0,0 +1,23 @@ +package eu.esa.snap.cimr.grid; + +import org.esa.snap.core.datamodel.GeoPos; +import org.opengis.referencing.FactoryException; +import org.opengis.referencing.crs.CoordinateReferenceSystem; + +import java.awt.*; +import java.awt.geom.AffineTransform; + + +public interface GridProjection { + + GeoPos gridToGeoPos(int x, int y); + boolean geoPosToGrid(GeoPos lat, Point out); + + CoordinateReferenceSystem getCrs() throws FactoryException; + AffineTransform getAffineTransform(GlobalGrid grid); + + double getLonMin(); + double getLatMax(); + double getDeltaLon(); + double getDeltaLat(); +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/LazyGridBandDataSource.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/LazyGridBandDataSource.java new file mode 100644 index 000000000..e717466a5 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/LazyGridBandDataSource.java @@ -0,0 +1,47 @@ +package eu.esa.snap.cimr.grid; + +import eu.esa.snap.cimr.CimrReaderContext; +import eu.esa.snap.cimr.cimr.CimrBandDescriptor; + + +public class LazyGridBandDataSource implements GridBandDataSource { + + private final CimrReaderContext context; + private final CimrBandDescriptor descriptor; + private final boolean useAverage; + + private volatile GridBandDataSource delegate; + + + public LazyGridBandDataSource(CimrReaderContext context, + CimrBandDescriptor descriptor, + boolean useAverage) { + this.context = context; + this.descriptor = descriptor; + this.useAverage = useAverage; + } + + private GridBandDataSource getDelegate() { + GridBandDataSource local = delegate; + if (local == null) { + synchronized (this) { + local = delegate; + if (local == null) { + local = context.getOrCreateGridForVariable(descriptor, useAverage); + delegate = local; + } + } + } + return local; + } + + @Override + public double getSample(int x, int y) { + return getDelegate().getSample(x, y); + } + + @Override + public void setSample(int x, int y, double value) { + getDelegate().setSample(x, y, value); + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/PlateCarreeProjection.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/PlateCarreeProjection.java new file mode 100644 index 000000000..99891f4a4 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/PlateCarreeProjection.java @@ -0,0 +1,102 @@ +package eu.esa.snap.cimr.grid; + +import org.esa.snap.core.datamodel.GeoPos; +import org.geotools.referencing.CRS; +import org.opengis.referencing.FactoryException; +import org.opengis.referencing.crs.CoordinateReferenceSystem; + +import java.awt.*; +import java.awt.geom.AffineTransform; + + +public class PlateCarreeProjection implements GridProjection { + + private final int width; + private final int height; + private final double lonMin; + private final double latMax; + private final double deltaLon; + private final double deltaLat; + + + public PlateCarreeProjection(int width, int height, double lonMin, double latMax, double deltaLon, double deltaLat) { + this.width = width; + this.height = height; + this.lonMin = lonMin; + this.latMax = latMax; + this.deltaLon = deltaLon; + this.deltaLat = deltaLat; + } + + @Override + public GeoPos gridToGeoPos(int x, int y) { + if (x < 0 || x >= width || y < 0 || y >= height) { + throw new IllegalArgumentException("Grid index out of range: x=" + x + ", y=" + y); + } + + double lon = lonMin + (x + 0.5) * deltaLon; + double lat = latMax - (y + 0.5) * deltaLat; + + return new GeoPos((float) lat, (float) lon); + } + + @Override + public boolean geoPosToGrid(GeoPos geoPos, Point out) { + double lat = geoPos.getLat(); + double lon = geoPos.getLon(); + + double lonMax = lonMin + width * deltaLon; + double latMin = latMax - height * deltaLat; + + if (lon < lonMin || lon >= lonMax || lat <= latMin || lat > latMax) { + return false; + } + + int x = (int) ((lon - lonMin) / deltaLon); + int y = (int) ((latMax - lat) / deltaLat); + + out.x = x; + out.y = y; + return true; + } + + @Override + public double getLonMin() { + return lonMin; + } + + @Override + public double getLatMax() { + return latMax; + } + + @Override + public double getDeltaLon() { + return deltaLon; + } + + @Override + public double getDeltaLat() { + return deltaLat; + } + + @Override + public CoordinateReferenceSystem getCrs() throws FactoryException { + return CRS.decode("EPSG:4326", true); + } + + @Override + public AffineTransform getAffineTransform(GlobalGrid grid) { + double lonMin = grid.getProjection().getLonMin(); + double latMax = grid.getProjection().getLatMax(); + double deltaLon = grid.getProjection().getDeltaLon(); + double deltaLat = grid.getProjection().getDeltaLat(); + + return new AffineTransform( + deltaLon, 0.0, + 0.0, -deltaLat, + lonMin + 0.5 * deltaLon, + latMax - 0.5 * deltaLat + ); + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NcUtil.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NcUtil.java new file mode 100644 index 000000000..da9ae57c8 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NcUtil.java @@ -0,0 +1,27 @@ +package eu.esa.snap.cimr.netcdf; + +import ucar.nc2.Group; +import ucar.nc2.NetcdfFile; +import ucar.nc2.Variable; + + +public class NcUtil { + + public static Group findGroupOrThrow(NetcdfFile ncFile, String path) { + Group g = ncFile.findGroup(path); + if (g == null) { + throw new IllegalArgumentException("Group not found: " + path); + } + return g; + } + + public static Variable findVarOrThrow(Group group, String name) { + Variable v = group.findVariable(name); + if (v == null) { + throw new IllegalArgumentException( + "Variable '" + name + "' not found in group " + group.getFullName() + ); + } + return v; + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactory.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactory.java new file mode 100644 index 000000000..e31ee9c52 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactory.java @@ -0,0 +1,91 @@ +package eu.esa.snap.cimr.netcdf; + +import eu.esa.snap.cimr.cimr.CimrBandDescriptor; +import eu.esa.snap.cimr.cimr.CimrDescriptorKind; +import eu.esa.snap.cimr.cimr.CimrDimensions; +import eu.esa.snap.cimr.grid.CimrGeometry; +import eu.esa.snap.cimr.grid.CimrGeometryBand; +import ucar.ma2.Array; +import ucar.ma2.Index3D; +import ucar.ma2.InvalidRangeException; +import ucar.nc2.Group; +import ucar.nc2.NetcdfFile; +import ucar.nc2.Variable; + +import java.io.IOException; + + +public class NetcdfCimrBandFactory { + + private final NetcdfFile ncFile; + private final CimrDimensions dimensions; + + + public NetcdfCimrBandFactory(NetcdfFile ncFile, CimrDimensions dimensions) { + this.ncFile = ncFile; + this.dimensions = dimensions; + } + + + public CimrGeometryBand createGeometryBand(CimrBandDescriptor desc, CimrGeometry geometry) throws IOException, InvalidRangeException { + + Group group = NcUtil.findGroupOrThrow(this.ncFile, desc.getGroupPath()); + Variable var = NcUtil.findVarOrThrow(group, desc.getValueVarName()); + + if (var.getRank() != 3) { + throw new IllegalArgumentException("Expected 3D variable for '" + + desc.getValueVarName() + "', but rank=" + var.getRank()); + } + + int nScans = dimensions.get(desc.getDimensions()[0]); + int nSamples = dimensions.get(desc.getDimensions()[1]); + int feedIdx = desc.getFeedIndex(); + + int[] origin = new int[] {0, 0, feedIdx}; + int[] shape = new int[] {nScans, nSamples, 1}; + + final Array data; + synchronized (this.ncFile) { + data = var.read(origin, shape); + } + + double[][] values; + Index3D idx = new Index3D(data.getShape()); + + if (desc.getKind() == CimrDescriptorKind.TIEPOINT_VARIABLE) { + int sampleCount = getSampleCount(desc); + values = new double[nScans][sampleCount]; + + for (int s = 0; s < nScans; s++) { + for (int smp = 0; smp < sampleCount; smp++) { + double t = (double) smp * (nSamples - 1) / (double) (sampleCount - 1); + int tp0 = (int) Math.floor(t); + int tp1 = Math.min(tp0 + 1, nSamples - 1); + double f = t - tp0; + + idx.set(s, tp0, 0); + double v0 = data.getDouble(idx); + idx.set(s, tp1, 0); + double v1 = data.getDouble(idx); + + values[s][smp] = v0 + f * (v1 - v0); + } + } + } else { + values = new double[nScans][nSamples]; + + for (int s = 0; s < nScans; s++) { + for (int smp = 0; smp < nSamples; smp++) { + idx.set(s, smp, 0); + values[s][smp] = data.getDouble(idx); + } + } + } + + return new CimrGeometryBand(values, geometry, feedIdx); + } + + private int getSampleCount(CimrBandDescriptor d) { + return dimensions.get("n_samples_" + d.getBand()); + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactory.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactory.java new file mode 100644 index 000000000..8276d6e94 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactory.java @@ -0,0 +1,110 @@ +package eu.esa.snap.cimr.netcdf; + +import eu.esa.snap.cimr.cimr.CimrBandDescriptor; +import eu.esa.snap.cimr.cimr.CimrDimensions; +import eu.esa.snap.cimr.grid.CimrTiepointGeometry; +import eu.esa.snap.cimr.grid.CimrGeometry; +import org.esa.snap.core.datamodel.GeoPos; +import ucar.ma2.Array; +import ucar.ma2.Index3D; +import ucar.ma2.InvalidRangeException; +import ucar.nc2.Group; +import ucar.nc2.NetcdfFile; +import ucar.nc2.Variable; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +public class NetcdfCimrGeometryFactory { + + + private final NetcdfFile ncFile; + private final CimrDimensions dimensions; + private final Map geometryByName = new HashMap<>(); + private final Map cache = new HashMap<>(); + + + public NetcdfCimrGeometryFactory(NetcdfFile ncFile, List geometryDescriptors, CimrDimensions dimensions) { + this.ncFile = ncFile; + this.dimensions = dimensions; + for (CimrBandDescriptor d : geometryDescriptors) { + this.geometryByName.put(d.getName(), d); + } + } + + + public CimrGeometry getOrCreateGeometry(CimrBandDescriptor variableDesc) throws IOException, InvalidRangeException { + CimrGeometry geometry = this.cache.get(key(variableDesc)); + if (geometry != null) { + return geometry; + } + + String[] geomNames = variableDesc.getGeometryNames(); + if (geomNames == null || geomNames.length != 2) { + throw new IllegalStateException( + "Descriptor '" + variableDesc.getName() + "' must define exactly two geometryNames (lat, lon)"); + } + + CimrBandDescriptor latDesc = geometryByName.get(geomNames[0]); + CimrBandDescriptor lonDesc = geometryByName.get(geomNames[1]); + + if (latDesc == null || lonDesc == null) { + throw new IllegalStateException("Geometry descriptors not found for variable '" + variableDesc.getName() + "': expected '" + geomNames[0] + "' and '" + geomNames[1] + "'"); + } + + Group latGroup = NcUtil.findGroupOrThrow(this.ncFile, latDesc.getGroupPath()); + Group lonGroup = NcUtil.findGroupOrThrow(this.ncFile, lonDesc.getGroupPath()); + Variable latVar = NcUtil.findVarOrThrow(latGroup, latDesc.getValueVarName()); + Variable lonVar = NcUtil.findVarOrThrow(lonGroup, lonDesc.getValueVarName()); + + int nScans = dimensions.get(latDesc.getDimensions()[0]); + int nTiePoints = dimensions.get(latDesc.getDimensions()[1]); + + int feedIndex = variableDesc.getFeedIndex(); + + int[] origin = {0, 0, feedIndex}; + int[] shape = {nScans, nTiePoints, 1}; + + Array latData; + Array lonData; + synchronized (this.ncFile) { + lonData = lonVar.read(origin, shape); + latData = latVar.read(origin, shape); + } + + + GeoPos[][][] tiePoints = new GeoPos[nScans][nTiePoints][1]; + Index3D idx = new Index3D(new int[]{nScans, nTiePoints, 1}); + + for (int s = 0; s < nScans; s++) { + for (int tp = 0; tp < nTiePoints; tp++) { + idx.set(s, tp, 0); + float lat = (float) latData.getDouble(idx); + float lon = (float) lonData.getDouble(idx); + tiePoints[s][tp][0] = new GeoPos(lat, lon); + } + } + + int sampleCount = getSampleCount(variableDesc); + geometry = new CimrTiepointGeometry(tiePoints, sampleCount); + this.cache.put(key(variableDesc), geometry); + + return geometry; + } + + + private String key(CimrBandDescriptor d) { + return d.getBand().name() + "#" + d.getFeedIndex(); + } + + private int getSampleCount(CimrBandDescriptor d) { + return dimensions.get("n_samples_" + d.getBand()); + } + + public void clearCache() { + this.cache.clear(); + } +} diff --git a/cimr-reader/src/main/nbm/manifest.mf b/cimr-reader/src/main/nbm/manifest.mf new file mode 100644 index 000000000..ac61e99d3 --- /dev/null +++ b/cimr-reader/src/main/nbm/manifest.mf @@ -0,0 +1,7 @@ +Manifest-Version: 1.0 +OpenIDE-Module-Specification-Version: ${microwavetbx.nbmSpecVersion} +OpenIDE-Module-Implementation-Version: ${microwavetbx.nbmImplVersion} +AutoUpdate-Show-In-Client: false +AutoUpdate-Essential-Module: false +OpenIDE-Module-Java-Dependencies: Java > 11 +OpenIDE-Module-Display-Category: SNAP Toolboxes diff --git a/cimr-reader/src/main/resources/META-INF/services/org.esa.snap.core.dataio.ProductReaderPlugIn b/cimr-reader/src/main/resources/META-INF/services/org.esa.snap.core.dataio.ProductReaderPlugIn new file mode 100644 index 000000000..93bfeb723 --- /dev/null +++ b/cimr-reader/src/main/resources/META-INF/services/org.esa.snap.core.dataio.ProductReaderPlugIn @@ -0,0 +1 @@ +eu.esa.snap.cimr.CimrL1BProductReaderPlugin \ No newline at end of file diff --git a/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/cimr-l1b-config.json b/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/cimr-l1b-config.json new file mode 100644 index 000000000..2871fd2e5 --- /dev/null +++ b/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/cimr-l1b-config.json @@ -0,0 +1,94 @@ +{ + "variables": [ + { + "name": "C_BAND_raw_bt_h_feed1", + "valueVarName": "raw_bt_h", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + }, + { + "name": "C_BAND_raw_bt_v_feed1", + "valueVarName": "raw_bt_v", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + }, + { + "name": "C_BAND_raw_bt_h_feed2", + "valueVarName": "raw_bt_h", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 1, + "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + }, + { + "name": "C_BAND_raw_bt_v_feed2", + "valueVarName": "raw_bt_v", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 1, + "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + } + ], + "tiepointVariables": [ + { + "name": "C_BAND_altitude_feed1", + "valueVarName": "altitude", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "groupPath": "/Data/Navigation_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + } + ], + "geometries": [ + { + "name": "C_BAND_latitude_feed1", + "valueVarName": "latitude", + "band": "C_BAND", + "groupPath": "/Data/Navigation_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + }, + { + "name": "C_BAND_longitude_feed1", + "valueVarName": "longitude", + "band": "C_BAND", + "groupPath": "/Data/Navigation_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + }, + { + "name": "C_BAND_latitude_feed2", + "valueVarName": "latitude", + "band": "C_BAND", + "groupPath": "/Data/Navigation_Data/C_BAND/", + "feedIndex": 1, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + }, + { + "name": "C_BAND_longitude_feed2", + "valueVarName": "longitude", + "band": "C_BAND", + "groupPath": "/Data/Navigation_Data/C_BAND/", + "feedIndex": 1, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + } + ] +} \ No newline at end of file diff --git a/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/test-config.json b/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/test-config.json new file mode 100644 index 000000000..2871fd2e5 --- /dev/null +++ b/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/test-config.json @@ -0,0 +1,94 @@ +{ + "variables": [ + { + "name": "C_BAND_raw_bt_h_feed1", + "valueVarName": "raw_bt_h", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + }, + { + "name": "C_BAND_raw_bt_v_feed1", + "valueVarName": "raw_bt_v", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + }, + { + "name": "C_BAND_raw_bt_h_feed2", + "valueVarName": "raw_bt_h", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 1, + "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + }, + { + "name": "C_BAND_raw_bt_v_feed2", + "valueVarName": "raw_bt_v", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 1, + "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + } + ], + "tiepointVariables": [ + { + "name": "C_BAND_altitude_feed1", + "valueVarName": "altitude", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "groupPath": "/Data/Navigation_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + } + ], + "geometries": [ + { + "name": "C_BAND_latitude_feed1", + "valueVarName": "latitude", + "band": "C_BAND", + "groupPath": "/Data/Navigation_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + }, + { + "name": "C_BAND_longitude_feed1", + "valueVarName": "longitude", + "band": "C_BAND", + "groupPath": "/Data/Navigation_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + }, + { + "name": "C_BAND_latitude_feed2", + "valueVarName": "latitude", + "band": "C_BAND", + "groupPath": "/Data/Navigation_Data/C_BAND/", + "feedIndex": 1, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + }, + { + "name": "C_BAND_longitude_feed2", + "valueVarName": "longitude", + "band": "C_BAND", + "groupPath": "/Data/Navigation_Data/C_BAND/", + "feedIndex": 1, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double" + } + ] +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrL1BProductReaderPluginTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrL1BProductReaderPluginTest.java new file mode 100644 index 000000000..56142555a --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrL1BProductReaderPluginTest.java @@ -0,0 +1,95 @@ +package eu.esa.snap.cimr; + +import org.esa.snap.core.dataio.DecodeQualification; +import org.esa.snap.core.dataio.ProductReader; +import org.esa.snap.core.util.io.SnapFileFilter; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.*; + + +public class CimrL1BProductReaderPluginTest { + + private CimrL1BProductReaderPlugin plugIn; + + + @Before + public void setUp() { + plugIn = new CimrL1BProductReaderPlugin(); + } + + + @Test + public void getDecodeQualification_wrongExtension() { + final File file = new File("ignore_the_name.txt"); + + assertEquals(DecodeQualification.UNABLE, plugIn.getDecodeQualification(file)); + } + + @Test + public void getDecodeQualification_correctExtension_wrongFilePattern() { + final File file = new File("ignore_the_name.nc"); + + assertEquals(DecodeQualification.UNABLE, plugIn.getDecodeQualification(file)); + } + + @Test + public void getDecodeQualification_correctExtension_correctFilePattern() { + final File file = new File("W_PT-DME-Lisbon-SAT-CIMR-1B_C_DME_20230420T103323_LD_20280110T114800_20280110T115700_TN.nc"); + final File file2 = new File("W_xx-esa-Lisbon-SAT-CIMR-1B_C_DME_20251029T000420_G_20280105T121500_20280105T121600_002.nc"); + + assertEquals(DecodeQualification.INTENDED, plugIn.getDecodeQualification(file)); + assertEquals(DecodeQualification.INTENDED, plugIn.getDecodeQualification(file2)); + } + + + @Test + public void getInputTypes() { + final Class[] inputTypes = plugIn.getInputTypes(); + assertEquals(2, inputTypes.length); + assertEquals(File.class, inputTypes[0]); + assertEquals(String.class, inputTypes[1]); + } + + @Test + public void createReaderInstance() { + final ProductReader readerInstance = plugIn.createReaderInstance(); + + assertNotNull(readerInstance); + assertTrue(readerInstance instanceof CimrL1BProductReader); + } + + @Test + public void getFormatNames() { + final String[] formatNames = plugIn.getFormatNames(); + + assertEquals(1, formatNames.length); + assertEquals("CIMR-L1B", formatNames[0]); + } + + @Test + public void getDefaultFileExtensions() { + final String[] extensions = plugIn.getDefaultFileExtensions(); + + assertEquals(1, extensions.length); + assertEquals(".nc", extensions[0]); + } + + @Test + public void getDescription() { + final String description = plugIn.getDescription(null); + assertEquals("CIMR Level 1B Data Products in NetCDF Format", description); + } + + @Test + public void getProductFileFilter() { + final SnapFileFilter productFileFilter = plugIn.getProductFileFilter(); + assertArrayEquals(plugIn.getDefaultFileExtensions(), productFileFilter.getExtensions()); + assertEquals(plugIn.getFormatNames()[0], productFileFilter.getFormatName()); + + assertEquals("CIMR Level 1B Data Products in NetCDF Format (*.nc)", productFileFilter.getDescription()); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextTest.java new file mode 100644 index 000000000..b3fbbc53d --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextTest.java @@ -0,0 +1,328 @@ +package eu.esa.snap.cimr; + +import eu.esa.snap.cimr.cimr.*; +import eu.esa.snap.cimr.grid.*; +import eu.esa.snap.cimr.netcdf.NetcdfCimrBandFactory; +import eu.esa.snap.cimr.netcdf.NetcdfCimrGeometryFactory; +import org.esa.snap.core.datamodel.GeoPos; +import org.junit.Test; +import ucar.nc2.NetcdfFile; + +import java.io.IOException; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; + + +public class CimrReaderContextTest { + + private static final double doubleErr = 1e-6; + + + @Test + public void testConstructorAndGetters() { + GlobalGrid grid = createTestGrid(); + CimrDescriptorSet descriptorSet = createEmptyDescriptorSet(); + + NetcdfCimrGeometryFactory geomFactory = new NetcdfCimrGeometryFactory(null, Collections.emptyList(), null); + NetcdfCimrBandFactory bandFactory = new NetcdfCimrBandFactory(null, null); + + CimrReaderContext ctx = new CimrReaderContext( + null, + descriptorSet, + grid, + geomFactory, + bandFactory + ); + + assertSame(grid, ctx.getGlobalGrid()); + assertSame(descriptorSet, ctx.getDescriptorSet()); + } + + @Test + public void testGetOrCreateGeometry_DelegatesAndWrapsCheckedException() { + GlobalGrid grid = createTestGrid(); + CimrDescriptorSet descriptorSet = createEmptyDescriptorSet(); + CimrBandDescriptor desc = createTestDescriptor(); + + NetcdfCimrGeometryFactory geomFactory = new NetcdfCimrGeometryFactory(null, Collections.emptyList(), null) { + @Override + public CimrGeometry getOrCreateGeometry(CimrBandDescriptor variableDesc) + throws IOException { + throw new IOException("boom-geo"); + } + }; + NetcdfCimrBandFactory bandFactory = new NetcdfCimrBandFactory(null, null); + CimrReaderContext ctx = new CimrReaderContext( + null, + descriptorSet, + grid, + geomFactory, + bandFactory + ); + + try { + ctx.getOrCreateGeometry(desc); + fail("Expected RuntimeException"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("Failed to build geometry for variable testVar")); + assertNotNull(e.getCause()); + assertTrue(e.getCause() instanceof IOException); + assertEquals("boom-geo", e.getCause().getMessage()); + } + } + + @Test + public void testGetOrCreateGridForVariable_BuildsAndCachesOnce() { + GlobalGrid grid = createTestGrid(); + CimrDescriptorSet descriptorSet = createEmptyDescriptorSet(); + CimrBandDescriptor desc = createTestDescriptor(); + + CimrGeometry stubGeom = createStubGeometry(); + + AtomicInteger geomCalls = new AtomicInteger(); + AtomicInteger bandCalls = new AtomicInteger(); + + NetcdfCimrGeometryFactory geomFactory = new NetcdfCimrGeometryFactory(null, Collections.emptyList(), null) { + @Override + public CimrGeometry getOrCreateGeometry(CimrBandDescriptor variableDesc){ + geomCalls.incrementAndGet(); + return stubGeom; + } + }; + + NetcdfCimrBandFactory bandFactory = new NetcdfCimrBandFactory(null, null) { + @Override + public CimrGeometryBand createGeometryBand(CimrBandDescriptor desc, CimrGeometry geometry) { + bandCalls.incrementAndGet(); + double[][] values = {{42.0, 42.0}}; + return new CimrGeometryBand(values, geometry, desc.getFeedIndex()); + } + }; + + CimrReaderContext ctx = new CimrReaderContext( + null, + descriptorSet, + grid, + geomFactory, + bandFactory + ); + + GridBandDataSource grid1 = ctx.getOrCreateGridForVariable(desc, true); + GridBandDataSource grid2 = ctx.getOrCreateGridForVariable(desc, false); + + assertSame(grid1, grid2); + + assertEquals(1, geomCalls.get()); + assertEquals(1, bandCalls.get()); + + assertEquals(42.0, grid1.getSample(0, 0), doubleErr); + } + + @Test + public void testGetOrCreateGridForVariable_WrapsBandFactoryCheckedException() { + GlobalGrid grid = createTestGrid(); + CimrDescriptorSet descriptorSet = createEmptyDescriptorSet(); + CimrBandDescriptor desc = createTestDescriptor(); + + CimrGeometry stubGeom = createStubGeometry(); + + NetcdfCimrGeometryFactory geomFactory = new NetcdfCimrGeometryFactory(null, Collections.emptyList(), null) { + @Override + public CimrGeometry getOrCreateGeometry(CimrBandDescriptor variableDesc) { + return stubGeom; + } + }; + + NetcdfCimrBandFactory bandFactory = new NetcdfCimrBandFactory(null, null) { + @Override + public CimrGeometryBand createGeometryBand(CimrBandDescriptor desc, CimrGeometry geometry) + throws IOException { + throw new IOException("boom-band"); + } + }; + CimrReaderContext ctx = new CimrReaderContext( + null, + descriptorSet, + grid, + geomFactory, + bandFactory + ); + + try { + ctx.getOrCreateGridForVariable(desc, true); + fail("Expected RuntimeException"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("Failed to build grid for variable testVar")); + assertNotNull(e.getCause()); + assertTrue(e.getCause() instanceof IOException); + assertEquals("boom-band", e.getCause().getMessage()); + } + } + + @Test + public void testGetOrCreateGridForVariable_PropagatesGeometryRuntimeException() { + GlobalGrid grid = createTestGrid(); + CimrDescriptorSet descriptorSet = createEmptyDescriptorSet(); + CimrBandDescriptor desc = createTestDescriptor(); + + NetcdfCimrGeometryFactory geomFactory = new NetcdfCimrGeometryFactory(null, Collections.emptyList(), null) { + @Override + public CimrGeometry getOrCreateGeometry(CimrBandDescriptor variableDesc) + throws IOException { + throw new IOException("boom-geo"); + } + }; + NetcdfCimrBandFactory bandFactory = new NetcdfCimrBandFactory(null, null); + CimrReaderContext ctx = new CimrReaderContext( + null, + descriptorSet, + grid, + geomFactory, + bandFactory + ); + + RuntimeException geoEx; + try { + ctx.getOrCreateGeometry(desc); + fail("Expected RuntimeException from getOrCreateGeometry"); + return; + } catch (RuntimeException e) { + geoEx = e; + assertTrue(e.getMessage().contains("Failed to build geometry for variable testVar")); + assertTrue(e.getCause() instanceof IOException); + } + + try { + ctx.getOrCreateGridForVariable(desc, true); + fail("Expected RuntimeException from getOrCreateGridForVariable"); + } catch (RuntimeException e) { + assertEquals(geoEx.getMessage(), e.getMessage()); + assertNotNull(e.getCause()); + assertTrue(e.getCause() instanceof IOException); + } + } + + @Test + public void testClearCache_clearsBandCacheAndGeometryCache() { + PlateCarreeProjection proj = new PlateCarreeProjection( + 2, 1, + 0.0, 0.0, + 1.0, 1.0 + ); + GlobalGrid grid = new GlobalGrid(proj, 2, 1); + + CimrBandDescriptor varDesc = new CimrBandDescriptor( + "testVar", "v", CimrFrequencyBand.C_BAND, + new String[]{"lat", "lon"}, + "/Data", 0, CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + CimrDescriptorSet descriptorSet = new CimrDescriptorSet( + Collections.singletonList(varDesc), + Collections.emptyList(), + Collections.emptyList() + ); + + NetcdfFile ncFile = NetcdfFile.builder().setLocation("dummy").build(); + CimrDimensions dims = new CimrDimensions(Collections.emptyMap()); + + class CountingGeometryFactory extends NetcdfCimrGeometryFactory { + int getCalls = 0; + int clearCalls = 0; + + CountingGeometryFactory() { + super(ncFile, Collections.emptyList(), dims); + } + + @Override + public CimrGeometry getOrCreateGeometry(CimrBandDescriptor d) { + getCalls++; + return createStubGeometry(); + } + + @Override + public void clearCache() { + clearCalls++; + super.clearCache(); + } + } + + class CountingBandFactory extends NetcdfCimrBandFactory { + int calls = 0; + + CountingBandFactory() { + super(ncFile, dims); + } + + @Override + public CimrGeometryBand createGeometryBand(CimrBandDescriptor desc, CimrGeometry geometry) { + calls++; + double[][] values = {{1.0, 2.0}}; + return new CimrGeometryBand(values, geometry, desc.getFeedIndex()); + } + } + + CountingGeometryFactory geomFactory = new CountingGeometryFactory(); + CountingBandFactory bandFactory = new CountingBandFactory(); + + CimrReaderContext ctx = new CimrReaderContext(ncFile, descriptorSet, grid, geomFactory, bandFactory); + + GridBandDataSource ds1 = ctx.getOrCreateGridForVariable(varDesc, true); + GridBandDataSource ds2 = ctx.getOrCreateGridForVariable(varDesc, true); + + assertSame(ds1, ds2); + assertEquals(1, geomFactory.getCalls); + assertEquals(1, bandFactory.calls); + + ctx.clearCache(); + assertEquals(1, geomFactory.clearCalls); + + GridBandDataSource ds3 = ctx.getOrCreateGridForVariable(varDesc, true); + assertNotSame(ds1, ds3); + assertEquals(2, geomFactory.getCalls); + assertEquals(2, bandFactory.calls); + } + + + + private GlobalGrid createTestGrid() { + PlateCarreeProjection proj = new PlateCarreeProjection( + 1, 1, + -0.5, 0.5, + 1.0, 1.0 + ); + return new GlobalGrid(proj, 1, 1); + } + + private CimrBandDescriptor createTestDescriptor() { + return new CimrBandDescriptor( + "testVar", + "testVar", + CimrFrequencyBand.C_BAND, + new String[]{"lat", "lon"}, + "/dummy", + 0, + CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + } + + private CimrDescriptorSet createEmptyDescriptorSet() { + return new CimrDescriptorSet( + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList() + ); + } + + private CimrGeometry createStubGeometry() { + GeoPos[][][] tp = new GeoPos[1][2][1]; + tp[0][0][0] = new GeoPos(0f, 0f); + tp[0][1][0] = new GeoPos(0f, 1f); + return new CimrTiepointGeometry(tp, 2); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrDescriptorSetTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrDescriptorSetTest.java new file mode 100644 index 000000000..280fc0eaa --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrDescriptorSetTest.java @@ -0,0 +1,102 @@ +package eu.esa.snap.cimr.cimr; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.*; + + +public class CimrDescriptorSetTest { + + + @Test + public void getGeometryByName_returnsDescriptorWhenPresent() { + CimrBandDescriptor geom1 = descriptor("LAT"); + CimrBandDescriptor geom2 = descriptor("LON"); + + List measurements = Collections.emptyList(); + List geometries = Arrays.asList(geom1, geom2); + List tiepoints = Collections.emptyList(); + + CimrDescriptorSet set = new CimrDescriptorSet(measurements, geometries, tiepoints); + + CimrBandDescriptor result = set.getGeometryByName("LON"); + + assertSame("Expected to get the matching geometry descriptor", geom2, result); + } + + @Test + public void getGeometryByName_returnsNullWhenNameNotFound() { + CimrBandDescriptor geom1 = descriptor("LAT"); + + CimrDescriptorSet set = new CimrDescriptorSet( + Collections.emptyList(), + Collections.singletonList(geom1), + Collections.emptyList() + ); + + CimrBandDescriptor result = set.getGeometryByName("LON"); + + assertNull("Expected null when geometry name is not found", result); + } + + @Test + public void getGeometryByName_returnsNullWhenNoGeometries() { + CimrDescriptorSet set = new CimrDescriptorSet( + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList() + ); + + CimrBandDescriptor result = set.getGeometryByName("ANY"); + + assertNull("Expected null when geometries list is empty", result); + } + + @Test + public void getters_returnListsPassedToConstructor() { + List measurements = Collections.singletonList(descriptor("MEAS")); + List geometries = Collections.singletonList(descriptor("GEOM")); + List tiepoints = Collections.singletonList(descriptor("TP")); + + CimrDescriptorSet set = new CimrDescriptorSet(measurements, geometries, tiepoints); + + assertSame(measurements, set.getMeasurements()); + assertSame(geometries, set.getGeometries()); + assertSame(tiepoints, set.getTiepointVariables()); + } + + @Test + public void getGeometryByName_returnsFirstMatchWhenMultipleWithSameName() { + CimrBandDescriptor geom1 = descriptor("LAT"); + CimrBandDescriptor geom2 = descriptor("LAT"); + + CimrDescriptorSet set = new CimrDescriptorSet( + Collections.emptyList(), + Arrays.asList(geom1, geom2), + Collections.emptyList() + ); + + CimrBandDescriptor result = set.getGeometryByName("LAT"); + + assertSame("Expected first matching descriptor to be returned", geom1, result); + } + + + private static CimrBandDescriptor descriptor(String name) { + return new CimrBandDescriptor( + name, + "C_BAND_bt", + CimrFrequencyBand.C_BAND, + new String[] {"C_BAND_latitude", "C_BAND_longitude"}, + "/dummy/group", + 0, + CimrDescriptorKind.GEOMETRY, + new String[]{"n_scans", "n_samples_C_BAND"}, + "double" + ); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrDimensionsTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrDimensionsTest.java new file mode 100644 index 000000000..46e3cf8bb --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrDimensionsTest.java @@ -0,0 +1,64 @@ +package eu.esa.snap.cimr.cimr; + +import org.junit.Test; +import ucar.nc2.Dimension; +import ucar.nc2.NetcdfFile; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.*; + + +public class CimrDimensionsTest { + + + @Test + public void testFromCollectsAllDimensions() { + NetcdfFile ncFile = new NetcdfFile() { + @Override + public List getDimensions() { + List dims = new ArrayList<>(); + dims.add(new Dimension("n_scans", 100)); + dims.add(new Dimension("n_samples_C_BAND", 200)); + dims.add(new Dimension("n_feeds_C_BAND", 3)); + return dims; + } + }; + + CimrDimensions dims = CimrDimensions.from(ncFile); + assertNotNull(dims); + + assertEquals(100, dims.get("n_scans")); + assertEquals(200, dims.get("n_samples_C_BAND")); + assertEquals(3, dims.get("n_feeds_C_BAND")); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetThrowsOnUnknownDimension() { + NetcdfFile ncFile = new NetcdfFile() { + @Override + public List getDimensions() { + List dims = new ArrayList<>(); + dims.add(new Dimension("n_scans", 100)); + return dims; + } + }; + + CimrDimensions dims = CimrDimensions.from(ncFile); + dims.get("does_not_exist"); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetThrowsWhenNoDimensionsPresent() { + NetcdfFile ncFile = new NetcdfFile() { + @Override + public List getDimensions() { + return new ArrayList<>(); + } + }; + + CimrDimensions dims = CimrDimensions.from(ncFile); + dims.get("n_scans"); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridBuilderTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridBuilderTest.java new file mode 100644 index 000000000..f40e4a17f --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridBuilderTest.java @@ -0,0 +1,65 @@ +package eu.esa.snap.cimr.cimr; + +import eu.esa.snap.cimr.grid.*; +import org.junit.Test; + +import static org.junit.Assert.*; + + +public class CimrGridBuilderTest { + + + @Test + public void build_whenUseAverageTrue_usesMapAverage() { + RecordingMapper mapper = new RecordingMapper(); + CimrGridBuilder builder = new CimrGridBuilder(mapper); + GlobalGrid grid = GlobalGridFactory.createGlobalPlateCarree(10.0); + + GlobalGridBandDataSource result = builder.build(null, grid, true); + + assertTrue(mapper.mapAverageCalled); + assertFalse(mapper.mapNearestCalled); + assertSame(grid, mapper.lastGrid); + assertSame(result, mapper.lastTarget); + assertNull(mapper.lastBand); + } + + @Test + public void build_whenUseAverageFalse_usesMapNearest() { + RecordingMapper mapper = new RecordingMapper(); + CimrGridBuilder builder = new CimrGridBuilder(mapper); + GlobalGrid grid = GlobalGridFactory.createGlobalPlateCarree(10.0); + + GlobalGridBandDataSource result = builder.build(null, grid, false); + + assertFalse(mapper.mapAverageCalled); + assertTrue(mapper.mapNearestCalled); + assertSame(grid, mapper.lastGrid); + assertSame(result, mapper.lastTarget); + assertNull(mapper.lastBand); + } + + private static class RecordingMapper extends GeometryBandToGridMapper { + boolean mapAverageCalled; + boolean mapNearestCalled; + CimrBand lastBand; + GlobalGrid lastGrid; + GridBandDataSource lastTarget; + + @Override + public void mapAverage(CimrBand band, GlobalGrid grid, GridBandDataSource target) { + mapAverageCalled = true; + lastBand = band; + lastGrid = grid; + lastTarget = target; + } + + @Override + public void mapNearest(CimrBand band, GlobalGrid grid, GridBandDataSource target) { + mapNearestCalled = true; + lastBand = band; + lastGrid = grid; + lastTarget = target; + } + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSourceTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSourceTest.java new file mode 100644 index 000000000..04c24e955 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSourceTest.java @@ -0,0 +1,98 @@ +package eu.esa.snap.cimr.cimr; + + +import com.bc.ceres.multilevel.MultiLevelModel; +import com.bc.ceres.multilevel.support.DefaultMultiLevelImage; +import com.bc.ceres.multilevel.support.DefaultMultiLevelModel; +import eu.esa.snap.cimr.grid.GridBandDataSource; +import org.esa.snap.core.datamodel.Band; +import org.esa.snap.core.datamodel.Product; +import org.esa.snap.core.datamodel.ProductData; +import org.junit.Test; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; + +import static org.junit.Assert.*; + + +public class CimrGridMultiLevelSourceTest { + + private static final double doubleErr = 1e-6; + + + @Test + public void testLevel0ImageMatchesGridValues() { + int width = 2; + int height = 2; + + Product product = new Product("T", "T", width, height); + Band band = product.addBand("test", ProductData.TYPE_FLOAT64); + + GridBandDataSource grid = new GridBandDataSource() { + @Override + public double getSample(int x, int y) { + return x + 10 * y; + } + + @Override + public void setSample(int x, int y, double value) { + } + }; + + MultiLevelModel model = new DefaultMultiLevelModel(1, new AffineTransform(), width, height); + CimrGridMultiLevelSource source = new CimrGridMultiLevelSource(model, band, grid); + DefaultMultiLevelImage mli = new DefaultMultiLevelImage(source); + + RenderedImage level0 = mli.getImage(0); + assertEquals(width, level0.getWidth()); + assertEquals(height, level0.getHeight()); + + Raster raster = level0.getData(new Rectangle(0, 0, width, height)); + assertEquals(0.0, raster.getSampleDouble(0, 0, 0), doubleErr); + assertEquals(1.0, raster.getSampleDouble(1, 0, 0), doubleErr); + assertEquals(10.0, raster.getSampleDouble(0, 1, 0), doubleErr); + assertEquals(11.0, raster.getSampleDouble(1, 1, 0), doubleErr); + } + + @Test + public void testAttachToBand_setsSourceImageAndUsesGridValues() { + int width = 2; + int height = 2; + + Product product = new Product("T", "T", width, height); + Band band = product.addBand("test", ProductData.TYPE_FLOAT64); + + GridBandDataSource grid = new GridBandDataSource() { + @Override + public double getSample(int x, int y) { + return x + 10 * y; + } + + @Override + public void setSample(int x, int y, double value) { + // not needed for this test + } + }; + + MultiLevelModel model = new DefaultMultiLevelModel(1, new AffineTransform(), width, height); + + CimrGridMultiLevelSource.attachToBand(band, grid, model); + + assertNotNull(band.getSourceImage()); + assertTrue(band.getSourceImage() instanceof DefaultMultiLevelImage); + + DefaultMultiLevelImage mli = (DefaultMultiLevelImage) band.getSourceImage(); + RenderedImage level0 = mli.getImage(0); + assertEquals(width, level0.getWidth()); + assertEquals(height, level0.getHeight()); + + Raster raster = level0.getData(new Rectangle(0, 0, width, height)); + assertEquals(0.0, raster.getSampleDouble(0, 0, 0), doubleErr); + assertEquals(1.0, raster.getSampleDouble(1, 0, 0), doubleErr); + assertEquals(10.0, raster.getSampleDouble(0, 1, 0), doubleErr); + assertEquals(11.0, raster.getSampleDouble(1, 1, 0), doubleErr); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridOpImageTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridOpImageTest.java new file mode 100644 index 000000000..d238f044c --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridOpImageTest.java @@ -0,0 +1,148 @@ +package eu.esa.snap.cimr.cimr; + + +import com.bc.ceres.multilevel.MultiLevelModel; +import com.bc.ceres.multilevel.support.DefaultMultiLevelImage; +import com.bc.ceres.multilevel.support.DefaultMultiLevelModel; +import eu.esa.snap.cimr.grid.GridBandDataSource; +import org.esa.snap.core.datamodel.Band; +import org.esa.snap.core.datamodel.Product; +import org.esa.snap.core.datamodel.ProductData; +import org.esa.snap.core.image.ResolutionLevel; +import org.junit.Test; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; + +import static org.junit.Assert.*; + + +public class CimrGridOpImageTest { + + private static final double doubleErr = 1e-8; + + + @Test + public void testLevel0UsesBaseGridValues() throws Exception { + int width = 4; + int height = 4; + Product product = new Product("P", "T", width, height); + Band band = product.addBand("b", ProductData.TYPE_FLOAT64); + + SimpleGrid grid = new SimpleGrid(width, height); + CimrGridOpImage opImage = createOpImageLevel(band, 0, width, height, grid); + + ProductData data = ProductData.createInstance(ProductData.TYPE_FLOAT64, width * height); + Rectangle rect = new Rectangle(0, 0, width, height); + + opImage.computeProductData(data, rect); + + assertEquals(0.0, data.getElemDoubleAt(0), doubleErr); + assertEquals(1.0, data.getElemDoubleAt(1), doubleErr); + assertEquals(10.0, data.getElemDoubleAt(4), doubleErr); + assertEquals(33.0, data.getElemDoubleAt(3 + 3 * width), doubleErr); + } + + @Test + public void testLevel1AveragesBlocks() throws Exception { + int baseWidth = 4; + int baseHeight = 4; + Product product = new Product("P", "T", baseWidth, baseHeight); + Band band = product.addBand("b", ProductData.TYPE_FLOAT64); + + SimpleGrid grid = new SimpleGrid(baseWidth, baseHeight); + + int levelIndex = 1; + CimrGridOpImage opImage = createOpImageLevel(band, levelIndex, baseWidth, baseHeight, grid); + + int w = baseWidth / 2; + int h = baseHeight / 2; + ProductData data = ProductData.createInstance(ProductData.TYPE_FLOAT64, w * h); + Rectangle rect = new Rectangle(0, 0, w, h); + + opImage.computeProductData(data, rect); + + + assertEquals(5.5, data.getElemDoubleAt(0), doubleErr); + assertEquals(7.5, data.getElemDoubleAt(1), doubleErr); + assertEquals(25.5, data.getElemDoubleAt(2), doubleErr); + assertEquals(27.5, data.getElemDoubleAt(3), doubleErr); + } + + + @Test + public void testIllegalArgumentFromGridDataSourceProducesNaN() { + int width = 2; + int height = 2; + + Product product = new Product("T", "T", width, height); + Band band = product.addBand("test", ProductData.TYPE_FLOAT64); + + GridBandDataSource grid = new GridBandDataSource() { + @Override + public double getSample(int x, int y) { + if (x == 1 && y == 0) { + throw new IllegalArgumentException("Test exception"); + } + return 42.0; + } + + @Override + public void setSample(int x, int y, double value) { + // not needed for this test + } + }; + + MultiLevelModel model = new DefaultMultiLevelModel(1, new AffineTransform(), width, height); + CimrGridMultiLevelSource source = new CimrGridMultiLevelSource(model, band, grid); + DefaultMultiLevelImage mli = new DefaultMultiLevelImage(source); + + RenderedImage level0 = mli.getImage(0); + Raster raster = level0.getData(new Rectangle(0, 0, width, height)); + + + assertEquals(42.0, raster.getSampleDouble(0, 0, 0), doubleErr); + assertTrue(Double.isNaN(raster.getSampleDouble(1, 0, 0))); + assertEquals(42.0, raster.getSampleDouble(0, 1, 0), doubleErr); + assertEquals(42.0, raster.getSampleDouble(1, 1, 0), doubleErr); + } + + + private static class SimpleGrid implements GridBandDataSource { + + private final int width; + private final int height; + private final double[] data; + + SimpleGrid(int width, int height) { + this.width = width; + this.height = height; + this.data = new double[width * height]; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + data[y * width + x] = x + 10.0 * y; + } + } + } + + @Override + public double getSample(int x, int y) { + return data[y * width + x]; + } + + @Override + public void setSample(int x, int y, double value) { + data[y * width + x] = value; + } + } + + private CimrGridOpImage createOpImageLevel(Band band, int levelIndex, int baseWidth, int baseHeight, GridBandDataSource grid) { + AffineTransform at = new AffineTransform(); + MultiLevelModel model = new DefaultMultiLevelModel(2, at, baseWidth, baseHeight); + ResolutionLevel level = ResolutionLevel.create(model, levelIndex); + return new CimrGridOpImage(band, level, grid); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridProductTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridProductTest.java new file mode 100644 index 000000000..edcc40452 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridProductTest.java @@ -0,0 +1,123 @@ +package eu.esa.snap.cimr.cimr; + +import eu.esa.snap.cimr.CimrReaderContext; +import eu.esa.snap.cimr.grid.*; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.*; + + +public class CimrGridProductTest { + + + @Test + public void testAddAndGetBands() { + PlateCarreeProjection proj = new PlateCarreeProjection( + 2, 1, + 0.0, 1.0, + 1.0, 1.0 + ); + GlobalGrid grid = new GlobalGrid(proj, 2,1); + + CimrGridProduct product = new CimrGridProduct(grid); + + CimrBandDescriptor band1 = new CimrBandDescriptor( + "C_raw_bt_h_feed1", "raw_bt_h", CimrFrequencyBand.C_BAND, + new String[] {""}, + "/Data/Measurement_Data/C_BAND/", + 1, CimrDescriptorKind.VARIABLE, + new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + CimrBandDescriptor band2 = new CimrBandDescriptor( + "X_raw_bt_v_feed1", "raw_bt_h", CimrFrequencyBand.X_BAND, + new String[] {""}, + "/Data/Measurement_Data/C_BAND/", + 2, CimrDescriptorKind.VARIABLE, + new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + + double[] data1 = {1.0, 2.0}; + double[] data2 = {10.0, 20.0}; + GridBandDataSource ds1 = new GlobalGridBandDataSource(2, 1, data1); + GridBandDataSource ds2 = new GlobalGridBandDataSource(2, 1, data2); + + product.addBand(band1, ds1); + product.addBand(band2, ds2); + + assertEquals(2, product.getBandCount()); + assertSame(grid, product.getGlobalGrid()); + assertEquals(ds1, product.getBandData(band1)); + assertEquals(2, product.getBands().size()); + + assertEquals(1.0, product.getBandData(band1).getSample(0, 0), 1e-12); + assertEquals(20.0, product.getBandData(band2).getSample(1, 0), 1e-12); + } + + @Test + public void testBuildLazyCreatesBandsFromDescriptorSet() { + PlateCarreeProjection proj = new PlateCarreeProjection(2, 1, 0.0, 1.0, 1.0, 1.0); + GlobalGrid grid = new GlobalGrid(proj, 2, 1); + + CimrBandDescriptor tieDesc = new CimrBandDescriptor( + "altitude", "altitude", CimrFrequencyBand.C_BAND, + new String[] {"lat", "lon"}, + "/Geolocation/", 0, + CimrDescriptorKind.TIEPOINT_VARIABLE, + new String[] {"n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + CimrBandDescriptor measDesc = new CimrBandDescriptor( + "C_raw_bt_h_feed1", "raw_bt_h", CimrFrequencyBand.C_BAND, + new String[] {"lat", "lon"}, + "/Data/Measurement_Data/C_BAND/", 0, + CimrDescriptorKind.VARIABLE, + new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + + CimrDescriptorSet descriptorSet = new CimrDescriptorSet( + List.of(measDesc), + java.util.Collections.emptyList(), + List.of(tieDesc) + ); + + CimrReaderContext context = new CimrReaderContext( + null, descriptorSet, grid, null, null + ); + + CimrGridProduct product = CimrGridProduct.buildLazy(context, true); + + assertSame(grid, product.getGlobalGrid()); + assertEquals(2, product.getBandCount()); + assertTrue(product.getBands().containsKey(tieDesc)); + assertTrue(product.getBands().containsKey(measDesc)); + assertTrue(product.getBandData(tieDesc) instanceof LazyGridBandDataSource); + assertTrue(product.getBandData(measDesc) instanceof LazyGridBandDataSource); + } + + + @Test(expected = UnsupportedOperationException.class) + public void testGetBandsIsUnmodifiable() { + PlateCarreeProjection proj = new PlateCarreeProjection(2, 1, 0.0, 1.0, 1.0, 1.0); + GlobalGrid grid = new GlobalGrid(proj, 2, 1); + CimrGridProduct product = new CimrGridProduct(grid); + + CimrBandDescriptor band = new CimrBandDescriptor( + "C_raw_bt_h_feed1", "raw_bt_h", CimrFrequencyBand.C_BAND, + new String[] {""}, + "/Data/Measurement_Data/C_BAND/", + 1, CimrDescriptorKind.VARIABLE, + new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + GridBandDataSource ds = new GlobalGridBandDataSource(2, 1, new double[]{1.0, 2.0}); + product.addBand(band, ds); + + product.getBands().put(band, ds); + } + +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilderTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilderTest.java new file mode 100644 index 000000000..e86a07dc6 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilderTest.java @@ -0,0 +1,159 @@ +package eu.esa.snap.cimr.cimr; + +import eu.esa.snap.cimr.grid.GlobalGridBandDataSource; +import eu.esa.snap.cimr.grid.GlobalGrid; +import eu.esa.snap.cimr.grid.GridBandDataSource; +import eu.esa.snap.cimr.grid.PlateCarreeProjection; +import org.esa.snap.core.datamodel.Band; +import org.esa.snap.core.datamodel.Product; +import org.junit.Test; + +import java.awt.image.Raster; + +import static org.junit.Assert.*; + + +public class CimrSnapProductBuilderTest { + + private static final double doubleErr = 1e-6; + + @Test + public void testBuildSnapProduct_createsBandsAndValues() throws Exception { + PlateCarreeProjection proj = new PlateCarreeProjection( + 2, 1, + 0.0, 1.0, + 1.0, 1.0 + ); + GlobalGrid globalGrid = new GlobalGrid(proj, 2, 1); + + CimrGridProduct gridProduct = new CimrGridProduct(globalGrid); + + CimrBandDescriptor bandDesc = new CimrBandDescriptor( + "C_raw_bt_h_feed1", "raw_bt_h", CimrFrequencyBand.C_BAND, + new String[] {""}, + "/Data/Measurement_Data/C_BAND/", + 1, CimrDescriptorKind.VARIABLE, + new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + + double[] data = {1.0, 2.0}; + GridBandDataSource ds = new GlobalGridBandDataSource(2, 1, data); + gridProduct.addBand(bandDesc, ds); + + Product product = CimrSnapProductBuilder.buildProduct("TEST", "CIMR_GRID", gridProduct, "path"); + + assertEquals(2, product.getSceneRasterWidth()); + assertEquals(1, product.getSceneRasterHeight()); + assertNotNull(product.getSceneGeoCoding()); + + Band band = product.getBand("C_raw_bt_h_feed1"); + assertNotNull(band); + + Raster raster = band.getSourceImage().getImage(0).getData(); + assertEquals(1.0, raster.getSampleDouble(0,0,0), doubleErr); + assertEquals(2.0, raster.getSampleDouble(1,0,0), doubleErr); + } + + @Test + public void testBuildSnapProduct_setsMetadataAndAutoGrouping() throws Exception { + PlateCarreeProjection proj = new PlateCarreeProjection( + 2, 1, + 0.0, 1.0, + 1.0, 1.0 + ); + GlobalGrid globalGrid = new GlobalGrid(proj, 2, 1); + + CimrGridProduct gridProduct = new CimrGridProduct(globalGrid); + + CimrBandDescriptor bandDesc = new CimrBandDescriptor( + "C_raw_bt_h_feed1", "raw_bt_h", CimrFrequencyBand.C_BAND, + new String[] {""}, + "/Data/Measurement_Data/C_BAND/", + 1, CimrDescriptorKind.VARIABLE, + new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + + double[] data = {1.0, 2.0}; + GridBandDataSource ds = new GlobalGridBandDataSource(2, 1, data); + gridProduct.addBand(bandDesc, ds); + + String path = "some\\path\\file.nc"; + Product product = CimrSnapProductBuilder.buildProduct("TEST", "CIMR_GRID", gridProduct, path); + + assertEquals("TEST", product.getName()); + assertEquals("CIMR_GRID", product.getProductType()); + assertNotNull(product.getFileLocation()); + assertTrue(product.getFileLocation().getPath().endsWith(path)); + assertEquals("L_BAND:C_BAND:X_BAND:KU_BAND:KA_BAND", product.getAutoGrouping().toString()); + } + + @Test + public void testBuildSnapProduct_withMultipleBands_allPresentAndCorrect() throws Exception { + PlateCarreeProjection proj = new PlateCarreeProjection( + 2, 1, + 0.0, 1.0, + 1.0, 1.0 + ); + GlobalGrid globalGrid = new GlobalGrid(proj, 2, 1); + + CimrGridProduct gridProduct = new CimrGridProduct(globalGrid); + + CimrBandDescriptor band1 = new CimrBandDescriptor( + "band1", "raw1", CimrFrequencyBand.C_BAND, + new String[] {""}, + "/Data/Measurement_Data/C_BAND/", + 0, CimrDescriptorKind.VARIABLE, + new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + CimrBandDescriptor band2 = new CimrBandDescriptor( + "band2", "raw2", CimrFrequencyBand.X_BAND, + new String[] {""}, + "/Data/Measurement_Data/X_BAND/", + 0, CimrDescriptorKind.VARIABLE, + new String[] {"n_scans", "n_samples_X_BAND", "n_feeds_X_BAND"}, + "double" + ); + + GridBandDataSource ds1 = new GlobalGridBandDataSource(2, 1, new double[]{1.0, 2.0}); + GridBandDataSource ds2 = new GlobalGridBandDataSource(2, 1, new double[]{10.0, 20.0}); + + gridProduct.addBand(band1, ds1); + gridProduct.addBand(band2, ds2); + + Product product = CimrSnapProductBuilder.buildProduct("TEST", "CIMR_GRID", gridProduct, "path"); + + assertEquals(2, product.getNumBands()); + + Band b1 = product.getBand("band1"); + Band b2 = product.getBand("band2"); + assertNotNull(b1); + assertNotNull(b2); + + assertEquals(1.0, b1.getSourceImage().getImage(0).getData().getSampleDouble(0, 0, 0), 1e-6); + assertEquals(2.0, b1.getSourceImage().getImage(0).getData().getSampleDouble(1, 0, 0), 1e-6); + assertEquals(10.0, b2.getSourceImage().getImage(0).getData().getSampleDouble(0, 0, 0), 1e-6); + assertEquals(20.0, b2.getSourceImage().getImage(0).getData().getSampleDouble(1, 0, 0), 1e-6); + } + + @Test + public void testBuildSnapProduct_withNoBands_createsEmptyProductWithGeoCoding() throws Exception { + PlateCarreeProjection proj = new PlateCarreeProjection( + 4, 2, + 0.0, 1.0, + 1.0, 1.0 + ); + GlobalGrid globalGrid = new GlobalGrid(proj, 4, 2); + + CimrGridProduct gridProduct = new CimrGridProduct(globalGrid); + + Product product = CimrSnapProductBuilder.buildProduct("EMPTY", "CIMR_GRID", gridProduct, "path"); + + assertEquals(4, product.getSceneRasterWidth()); + assertEquals(2, product.getSceneRasterHeight()); + assertNotNull(product.getSceneGeoCoding()); + assertEquals(0, product.getNumBands()); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/config/CimrConfigLoaderTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/config/CimrConfigLoaderTest.java new file mode 100644 index 000000000..a76f43f26 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/config/CimrConfigLoaderTest.java @@ -0,0 +1,72 @@ +package eu.esa.snap.cimr.config; + +import eu.esa.snap.cimr.cimr.CimrBandDescriptor; +import eu.esa.snap.cimr.cimr.CimrDescriptorKind; +import eu.esa.snap.cimr.cimr.CimrDescriptorSet; +import eu.esa.snap.cimr.cimr.CimrFrequencyBand; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.*; + + +public class CimrConfigLoaderTest { + + + @Test + public void testLoadTestConfigJson() throws Exception { + CimrDescriptorSet set = CimrConfigLoader.load("test-config.json"); + assertNotNull(set); + + List meas = set.getMeasurements(); + List tpVars = set.getTiepointVariables(); + List tpGeoms = set.getGeometries(); + + assertEquals(4, meas.size()); + assertEquals(1, tpVars.size()); + assertEquals(4, tpGeoms.size()); + + + CimrBandDescriptor m0 = meas.get(0); + assertEquals("C_BAND_raw_bt_h_feed1", m0.getName()); + assertEquals("raw_bt_h", m0.getValueVarName()); + assertEquals(CimrFrequencyBand.C_BAND, m0.getBand()); + assertEquals("/Data/Measurement_Data/C_BAND/", m0.getGroupPath()); + assertEquals(0, m0.getFeedIndex()); + assertEquals(CimrDescriptorKind.VARIABLE, m0.getKind()); + assertArrayEquals(new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, m0.getDimensions()); + assertEquals("double", m0.getDataType()); + assertArrayEquals(new String[]{"C_BAND_latitude_feed1", "C_BAND_longitude_feed1"}, m0.getGeometryNames()); + + + CimrBandDescriptor tpVal0 = tpVars.get(0); + assertEquals("C_BAND_altitude_feed1", tpVal0.getName()); + assertEquals("altitude", tpVal0.getValueVarName()); + assertEquals(CimrFrequencyBand.C_BAND, tpVal0.getBand()); + assertEquals("/Data/Navigation_Data/C_BAND/", tpVal0.getGroupPath()); + assertEquals(0, tpVal0.getFeedIndex()); + assertEquals(CimrDescriptorKind.TIEPOINT_VARIABLE, tpVal0.getKind()); + assertArrayEquals(new String[]{"n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"}, tpVal0.getDimensions()); + assertEquals("double", tpVal0.getDataType()); + assertArrayEquals(new String[]{"C_BAND_latitude_feed1", "C_BAND_longitude_feed1"}, tpVal0.getGeometryNames()); + + + CimrBandDescriptor tpGeom0 = tpGeoms.get(0); + assertEquals("C_BAND_latitude_feed1", tpGeom0.getName()); + assertEquals("latitude", tpGeom0.getValueVarName()); + assertEquals(CimrFrequencyBand.C_BAND, tpGeom0.getBand()); + assertEquals("/Data/Navigation_Data/C_BAND/", tpGeom0.getGroupPath()); + assertEquals(0, tpGeom0.getFeedIndex()); + assertEquals(CimrDescriptorKind.GEOMETRY, tpGeom0.getKind()); + assertArrayEquals(new String[]{"n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"}, tpGeom0.getDimensions()); + assertEquals("double", tpGeom0.getDataType()); + } + + + @Test(expected = IOException.class) + public void testLoadTestConfigJson_throws() throws IOException { + CimrConfigLoader.load("not-existent.json"); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGeometryBandTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGeometryBandTest.java new file mode 100644 index 000000000..2c72c5f49 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGeometryBandTest.java @@ -0,0 +1,113 @@ +package eu.esa.snap.cimr.grid; + +import org.esa.snap.core.datamodel.GeoPos; +import org.junit.Test; + +import static org.junit.Assert.*; + + +public class CimrGeometryBandTest { + + private static final double doubleErr = 1e-6; + + + @Test + public void testValuesAndGeometryAreWiredCorrectly() { + double[][] values = { + {1.0, 2.0, 3.0, 4.0} + }; + + GeoPos[][][] tp = new GeoPos[1][2][1]; + tp[0][0][0] = new GeoPos(50f, 0f); + tp[0][1][0] = new GeoPos(50f, 10f); + + CimrTiepointGeometry geom = new CimrTiepointGeometry(tp, 4); + + CimrGeometryBand band = new CimrGeometryBand(values, geom, 5); + + assertEquals(1, band.getScanCount()); + assertEquals(4, band.getSampleCount()); + assertEquals(5, band.getFeedIndex()); + + assertEquals(1.0, band.getValue(0, 0), doubleErr); + assertEquals(4.0, band.getValue(0, 3), doubleErr); + + GeoPos g0 = band.getGeoPos(0, 0); + GeoPos g2 = band.getGeoPos(0, 2); + GeoPos g3 = band.getGeoPos(0, 3); + + assertEquals(50.0, g0.getLat(), doubleErr); + assertEquals(0.0, g0.getLon(), doubleErr); + assertEquals(50.0, g2.getLat(), doubleErr); + assertEquals(6.666666, g2.getLon(), doubleErr); + assertEquals(50.0, g3.getLat(), doubleErr); + assertEquals(10.0, g3.getLon(), doubleErr); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructor_FailsWhenValuesNull() { + GeoPos[][][] tp = new GeoPos[1][1][1]; + tp[0][0][0] = new GeoPos(0f, 0f); + CimrTiepointGeometry geom = new CimrTiepointGeometry(tp, 2); + + new CimrGeometryBand(new double[0][0], geom, 0); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructor_FailsWhenGeometryNull() { + double[][] values = { + {1.0, 2.0} + }; + new CimrGeometryBand(values, null, 0); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructor_FailsWhenNoScans() { + double[][] values = new double[0][]; + GeoPos[][][] tp = new GeoPos[1][1][1]; + tp[0][0][0] = new GeoPos(0f, 0f); + + CimrTiepointGeometry geom = new CimrTiepointGeometry(tp, 1); + new CimrGeometryBand(values, geom, 0); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructor_FailsWhenSampleDimMismatchInValues() { + double[][] values = new double[2][]; + values[0] = new double[]{1.0, 2.0}; + values[1] = new double[]{3.0}; + + GeoPos[][][] tp = new GeoPos[2][1][1]; + tp[0][0][0] = new GeoPos(0f, 0f); + tp[1][0][0] = new GeoPos(1f, 1f); + + CimrTiepointGeometry geom = new CimrTiepointGeometry(tp, 2); + new CimrGeometryBand(values, geom, 0); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructor_FailsWhenGeometryScanCountMismatch() { + double[][] values = { + {1.0, 2.0} + }; + + GeoPos[][][] tp = new GeoPos[2][1][1]; + tp[0][0][0] = new GeoPos(0f, 0f); + tp[1][0][0] = new GeoPos(1f, 1f); + + CimrTiepointGeometry geom = new CimrTiepointGeometry(tp, 2); + new CimrGeometryBand(values, geom, 0); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructor_FailsWhenGeometrySampleCountMismatch() { + double[][] values = { + {1.0, 2.0} + }; + GeoPos[][][] tp = new GeoPos[1][1][1]; + tp[0][0][0] = new GeoPos(0f, 0f); + + CimrTiepointGeometry geom = new CimrTiepointGeometry(tp, 3); + new CimrGeometryBand(values, geom, 0); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridBuilderTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridBuilderTest.java new file mode 100644 index 000000000..05f8d6101 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridBuilderTest.java @@ -0,0 +1,82 @@ +package eu.esa.snap.cimr.grid; + +import eu.esa.snap.cimr.cimr.CimrGridBuilder; +import org.esa.snap.core.datamodel.GeoPos; +import org.junit.Test; + +import static org.junit.Assert.*; + + +public class CimrGridBuilderTest { + + private static final double doubleErr = 1e-6; + + + @Test + public void testBuild_usesAverageWhenTrue() { + GeometryBandToGridMapper mapper = new GeometryBandToGridMapper(); + CimrGridBuilder builder = new CimrGridBuilder(mapper); + + CimrBand swath = createDummySwath(); + PlateCarreeProjection proj = new PlateCarreeProjection( + 1, 1, + 0.0, 1.0, + 1.0, 1.0 + ); + GlobalGrid grid = new GlobalGrid(proj, 1, 1); + + GlobalGridBandDataSource target = builder.build(swath, grid, true); + + assertEquals(41.0, target.getSample(0, 0), doubleErr); + assertEquals(1, target.getWidth()); + assertEquals(1, target.getHeight()); + } + + @Test + public void testBuild_usesNearestWhenFalse() { + GeometryBandToGridMapper mapper = new GeometryBandToGridMapper(); + CimrGridBuilder builder = new CimrGridBuilder(mapper); + + CimrBand swath = createDummySwath(); + PlateCarreeProjection proj = new PlateCarreeProjection( + 1, 1, + 0.0, 1.0, + 1.0, 1.0 + ); + GlobalGrid grid = new GlobalGrid(proj, 1, 1); + + GlobalGridBandDataSource target = builder.build(swath, grid, false); + + assertEquals(40.0, target.getSample(0, 0), doubleErr); + assertEquals(1, target.getWidth()); + assertEquals(1, target.getHeight()); + } + + private CimrBand createDummySwath() { + return new CimrBand() { + @Override + public int getScanCount() { + return 1; + } + + @Override + public int getSampleCount() { + return 2; + } + + @Override + public double getValue(int scanIndex, int sampleIndex) { + if (sampleIndex == 0) { + return 42.0; + } else { + return 40.0; + } + } + + @Override + public GeoPos getGeoPos(int scanIndex, int sampleIndex) { + return new GeoPos(0.5f, 0.5f); + } + }; + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrTiepointGeometryTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrTiepointGeometryTest.java new file mode 100644 index 000000000..9919d106d --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrTiepointGeometryTest.java @@ -0,0 +1,139 @@ +package eu.esa.snap.cimr.grid; + +import org.esa.snap.core.datamodel.GeoPos; +import org.junit.Test; + +import static org.junit.Assert.*; + + +public class CimrTiepointGeometryTest { + + private static final double doubleErr = 1e-6; + + @Test + public void testInterpolation_simple4Samples2TiePoints() { + int scans = 1; + int tiePoints = 2; + int feeds = 1; + int samples = 4; + + GeoPos[][][] tp = new GeoPos[scans][tiePoints][feeds]; + tp[0][0][0] = new GeoPos(50f, 0f); + tp[0][1][0] = new GeoPos(50f, 10f); + + CimrTiepointGeometry geom = new CimrTiepointGeometry(tp, samples); + + GeoPos g0 = geom.getGeoPos(0, 0, 0); + GeoPos g1 = geom.getGeoPos(0, 1, 0); + GeoPos g2 = geom.getGeoPos(0, 2, 0); + GeoPos g3 = geom.getGeoPos(0, 3, 0); + + assertEquals(2, geom.getTiePointCount()); + + assertEquals(50.0, g0.getLat(), doubleErr); + assertEquals(0.0, g0.getLon(), doubleErr); + + assertEquals(50.0, g1.getLat(), doubleErr); + assertEquals(3.333333, g1.getLon(), doubleErr); + + assertEquals(50.0, g2.getLat(), doubleErr); + assertEquals(6.666666, g2.getLon(), doubleErr); + + assertEquals(50.0, g3.getLat(), doubleErr); + assertEquals(10.0, g3.getLon(), doubleErr); + } + + @Test + public void testInterpolation_realisticCounts() { + int scans = 1; + int tiePoints = 274; + int feeds = 1; + int samples = 549; + + GeoPos[][][] tp = new GeoPos[scans][tiePoints][feeds]; + for (int i = 0; i < tiePoints; i++) { + float lon = (float) (10.0 * i / (tiePoints - 1)); + tp[0][i][0] = new GeoPos(60f, lon); + } + + CimrTiepointGeometry geom = new CimrTiepointGeometry(tp, samples); + + GeoPos gStart = geom.getGeoPos(0, 0, 0); + GeoPos gMiddle = geom.getGeoPos(0, 200, 0); + GeoPos gEnd = geom.getGeoPos(0, samples - 1, 0); + + assertEquals(60.0, gStart.getLat(), doubleErr); + assertEquals(0.0, gStart.getLon(), doubleErr); + + assertEquals(60.0, gMiddle.getLat(), doubleErr); + assertEquals(3.649635, gMiddle.getLon(), doubleErr); + + assertEquals(60.0, gEnd.getLat(), doubleErr); + assertEquals(10.0, gEnd.getLon(), doubleErr); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructor_failsWhenNoScans() { + GeoPos[][][] tp = new GeoPos[0][0][0]; + new CimrTiepointGeometry(tp, 10); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructor_failsWhenNoTiePoints() { + GeoPos[][][] tp = new GeoPos[1][0][0]; + new CimrTiepointGeometry(tp, 10); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructor_failsWhenTiePointDimMismatchBetweenScans() { + GeoPos[][][] tp = new GeoPos[2][][]; + tp[0] = new GeoPos[2][1]; + tp[1] = new GeoPos[3][1]; + + new CimrTiepointGeometry(tp, 10); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructor_failsWhenFeedDimMismatch() { + GeoPos[][][] tp = new GeoPos[1][][]; + tp[0] = new GeoPos[2][]; + tp[0][0] = new GeoPos[1]; + tp[0][1] = new GeoPos[2]; + + new CimrTiepointGeometry(tp, 10); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructor_failsWhenSampleCountTooSmall() { + GeoPos[][][] tp = new GeoPos[1][1][1]; + tp[0][0][0] = new GeoPos(0f, 0f); + new CimrTiepointGeometry(tp, 1); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetGeoPos_failsWhenScanIndexOutOfRange() { + GeoPos[][][] tp = new GeoPos[1][1][1]; + tp[0][0][0] = new GeoPos(0f, 0f); + CimrTiepointGeometry geom = new CimrTiepointGeometry(tp, 2); + + geom.getGeoPos(1, 0, 0); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetGeoPos_failsWhenSampleIndexOutOfRange() { + GeoPos[][][] tp = new GeoPos[1][1][1]; + tp[0][0][0] = new GeoPos(0f, 0f); + CimrTiepointGeometry geom = new CimrTiepointGeometry(tp, 2); + + geom.getGeoPos(0, 2, 0); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetGeoPos_failsWhenFeedIndexOutOfRange() { + GeoPos[][][] tp = new GeoPos[1][1][1]; + tp[0][0][0] = new GeoPos(0f, 0f); + CimrTiepointGeometry geom = new CimrTiepointGeometry(tp, 2); + + geom.getGeoPos(0, 0, 1); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GeometryBandToGridMapperTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GeometryBandToGridMapperTest.java new file mode 100644 index 000000000..6e84ba654 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GeometryBandToGridMapperTest.java @@ -0,0 +1,76 @@ +package eu.esa.snap.cimr.grid; + +import org.esa.snap.core.datamodel.GeoPos; +import org.junit.Test; + +import static org.junit.Assert.*; + + +public class GeometryBandToGridMapperTest { + + private static final double doubleErr = 1e-6; + + @Test + public void testMap_simpleCase_mapNearest() { + PlateCarreeProjection projection = new PlateCarreeProjection( + 4, 2, + -10.0, 81.0, + 1.0, 1.0 + ); + GlobalGrid grid = new GlobalGrid(projection, 4, 2); + CimrBand swath = new DummyCimrBand(); + + GlobalGridBandDataSource target = GlobalGridBandDataSource.createEmpty(4, 2); + + GeometryBandToGridMapper mapper = new GeometryBandToGridMapper(); + mapper.mapNearest(swath, grid, target); + + assertEquals(0.0, target.getSample(0, 0), doubleErr); + assertEquals(1.0, target.getSample(1, 0), doubleErr); + assertEquals(2.0, target.getSample(2, 0), doubleErr); + assertEquals(3.0, target.getSample(3, 0), doubleErr); + + assertEquals(10.0, target.getSample(0, 1), doubleErr); + assertEquals(11.0, target.getSample(1, 1), doubleErr); + assertEquals(12.0, target.getSample(2, 1), doubleErr); + assertEquals(13.0, target.getSample(3, 1), doubleErr); + } + + + @Test + public void testMap_simpleCase_mapAverage() { + PlateCarreeProjection proj = new PlateCarreeProjection(1, 1, -180, 90, 360, 180); + GlobalGrid grid = new GlobalGrid(proj, 1, 1); + + CimrBand swath = new DummyCimrBand(); + GlobalGridBandDataSource target = GlobalGridBandDataSource.createEmpty(1, 1); + GeometryBandToGridMapper mapper = new GeometryBandToGridMapper(); + mapper.mapAverage(swath, grid, target); + + assertEquals(6.5, target.getSample(0, 0), 1e-6); + } +} + +class DummyCimrBand implements CimrBand { + @Override + public int getScanCount() { + return 2; + } + + @Override + public int getSampleCount() { + return 4; + } + + @Override + public double getValue(int scanIndex, int sampleIndex) { + return scanIndex * 10 + sampleIndex; + } + + @Override + public GeoPos getGeoPos(int scanIndex, int sampleIndex) { + float lat = 80.5f - scanIndex; + float lon = -9.5f + sampleIndex; + return new GeoPos(lat, lon); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GlobalGridBandDataSourceTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GlobalGridBandDataSourceTest.java new file mode 100644 index 000000000..e4383c678 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GlobalGridBandDataSourceTest.java @@ -0,0 +1,63 @@ +package eu.esa.snap.cimr.grid; + +import org.junit.Test; + +import static org.junit.Assert.*; + + +public class GlobalGridBandDataSourceTest { + + @Test + public void testGetSample_basicLayout() { + int width = 2; + int height = 2; + double[] data = { + 1.0, 2.0, + 3.0, 4.0 + }; + GlobalGridBandDataSource ds = new GlobalGridBandDataSource(width, height, data); + + assertEquals(1.0, ds.getSample(0, 0), 1e-12); + assertEquals(2.0, ds.getSample(1, 0), 1e-12); + assertEquals(3.0, ds.getSample(0, 1), 1e-12); + assertEquals(4.0, ds.getSample(1, 1), 1e-12); + } + + @Test + public void testCreateEmpty_initialNaN() { + GlobalGridBandDataSource ds = GlobalGridBandDataSource.createEmpty(2, 2); + + assertTrue(Double.isNaN(ds.getSample(0, 0))); + assertTrue(Double.isNaN(ds.getSample(1, 0))); + assertTrue(Double.isNaN(ds.getSample(0, 1))); + assertTrue(Double.isNaN(ds.getSample(1, 1))); + } + + @Test + public void testSetSample() { + GlobalGridBandDataSource ds = GlobalGridBandDataSource.createEmpty(2, 1); + + ds.setSample(0, 0, 42.0); + ds.setSample(1, 0, 7.0); + + assertEquals(42.0, ds.getSample(0, 0), 1e-12); + assertEquals(7.0, ds.getSample(1, 0), 1e-12); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructor_invalidLength_throws() { + new GlobalGridBandDataSource(2, 2, new double[]{1.0, 2.0, 3.0}); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetSample_outOfBounds_throws() { + GlobalGridBandDataSource ds = GlobalGridBandDataSource.createEmpty(2, 2); + ds.getSample(2, 0); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetSample_outOfBounds_throws() { + GlobalGridBandDataSource ds = GlobalGridBandDataSource.createEmpty(2, 2); + ds.setSample(-1, 0, 5.0); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GlobalGridTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GlobalGridTest.java new file mode 100644 index 000000000..73ec87a86 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GlobalGridTest.java @@ -0,0 +1,45 @@ +package eu.esa.snap.cimr.grid; + +import org.esa.snap.core.datamodel.GeoPos; +import org.junit.Before; +import org.junit.Test; + +import java.awt.*; + +import static org.junit.Assert.*; + + +public class GlobalGridTest { + + GlobalGrid grid; + + @Before + public void setUp() { + PlateCarreeProjection proj = new PlateCarreeProjection( + 360, 180, + -180.0, 90.0, + 1.0, 1.0 + ); + grid = new GlobalGrid(proj, 180, 360); + } + + @Test + public void gridToGeoPos() { + GeoPos pos = grid.gridToGeoPos(50, 100); + assertEquals(-10.5, pos.getLat(), 1e-6); + assertEquals(-129.5, pos.getLon(), 1e-6); + } + + @Test + public void geoPosToGrid() { + Point p = new Point(); + int x = 50; + int y = 100; + GeoPos pos = grid.gridToGeoPos(x, y); + boolean inside = grid.geoPosToGrid(pos, p); + + assertTrue("Point should be inside grid", inside); + assertEquals("x mismatch", x, p.x); + assertEquals("y mismatch", y, p.y); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyGridBandDataSourceTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyGridBandDataSourceTest.java new file mode 100644 index 000000000..340221444 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyGridBandDataSourceTest.java @@ -0,0 +1,104 @@ +package eu.esa.snap.cimr.grid; + + +import eu.esa.snap.cimr.CimrReaderContext; +import eu.esa.snap.cimr.cimr.CimrBandDescriptor; +import eu.esa.snap.cimr.cimr.CimrDescriptorKind; +import eu.esa.snap.cimr.cimr.CimrDescriptorSet; +import eu.esa.snap.cimr.cimr.CimrFrequencyBand; +import org.junit.Test; +import ucar.nc2.NetcdfFile; + +import static org.junit.Assert.*; + + +public class LazyGridBandDataSourceTest { + + + private static final double doubleErr = 1e-6; + + @Test + public void testLazyInitializationAndGetSampleDelegation() { + double[] data = { + 1.0, 2.0, + 3.0, 4.0 + }; + GlobalGridBandDataSource delegate = new GlobalGridBandDataSource(2, 2, data); + + TestReaderContext context = new TestReaderContext(delegate); + CimrBandDescriptor desc = createDummyDescriptor("test_band"); + + LazyGridBandDataSource lazy = new LazyGridBandDataSource(context, desc, true); + + double v00 = lazy.getSample(0, 0); + double v11 = lazy.getSample(1, 1); + + assertEquals(1.0, v00, doubleErr); + assertEquals(4.0, v11, doubleErr); + + assertEquals(1, context.callCount); + assertSame(desc, context.lastDescriptor); + assertTrue(context.lastUseAverage); + } + + @Test + public void testSetSampleDelegationAndUseAverageFalse() { + double[] data = { + 0.0, 0.0, + 0.0, 0.0 + }; + GlobalGridBandDataSource delegate = new GlobalGridBandDataSource(2, 2, data); + + TestReaderContext context = new TestReaderContext(delegate); + CimrBandDescriptor desc = createDummyDescriptor("test_band_2"); + + LazyGridBandDataSource lazy = new LazyGridBandDataSource(context, desc, false); + + assertEquals(0.0, delegate.getSample(1, 1), doubleErr); + + lazy.setSample(1, 1, 42.0); + assertEquals(42.0, delegate.getSample(1, 1), doubleErr); + + assertEquals(1, context.callCount); + assertSame(desc, context.lastDescriptor); + assertFalse(context.lastUseAverage); + } + + + private static class TestReaderContext extends CimrReaderContext { + + int callCount = 0; + CimrBandDescriptor lastDescriptor; + boolean lastUseAverage; + private final GlobalGridBandDataSource delegate; + + TestReaderContext(GlobalGridBandDataSource delegate) { + super((NetcdfFile) null, + new CimrDescriptorSet(null, null, null), + null, null, null); + this.delegate = delegate; + } + + @Override + public GlobalGridBandDataSource getOrCreateGridForVariable(CimrBandDescriptor descriptor, boolean useAverage) { + callCount++; + lastDescriptor = descriptor; + lastUseAverage = useAverage; + return delegate; + } + } + + private static CimrBandDescriptor createDummyDescriptor(String name) { + return new CimrBandDescriptor( + name, + "valueVar", + CimrFrequencyBand.C_BAND, + new String[]{"lat", "lon"}, + "/dummy/path", + 0, + CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/PlateCarreeProjectionTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/PlateCarreeProjectionTest.java new file mode 100644 index 000000000..3c816a06a --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/PlateCarreeProjectionTest.java @@ -0,0 +1,86 @@ +package eu.esa.snap.cimr.grid; + +import org.esa.snap.core.datamodel.GeoPos; +import org.junit.Before; +import org.junit.Test; + +import java.awt.*; + +import static org.junit.Assert.*; + + +public class PlateCarreeProjectionTest { + + private PlateCarreeProjection proj; + private double doubleErr = 1e-6; + + @Before + public void setUp() { + proj = new PlateCarreeProjection( + 360, 180, + -180.0, 90.0, + 1.0, 1.0 + ); + } + + + @Test + public void testGridToGeoPos_corners() { + GeoPos ul = proj.gridToGeoPos(0, 0); + assertEquals(89.5, ul.getLat(), doubleErr); + assertEquals(-179.5, ul.getLon(), doubleErr); + + GeoPos ur = proj.gridToGeoPos(359, 0); + assertEquals(89.5, ur.getLat(), doubleErr); + assertEquals(179.5, ur.getLon(), doubleErr); + + GeoPos ll = proj.gridToGeoPos(0, 179); + assertEquals(-89.5, ll.getLat(), doubleErr); + assertEquals(-179.5, ll.getLon(), doubleErr); + + GeoPos lr = proj.gridToGeoPos(359, 179); + assertEquals(-89.5, lr.getLat(), doubleErr); + assertEquals(179.5, lr.getLon(), doubleErr); + } + + @Test + public void testGeoPosToGrid_roundTrip() { + Point p = new Point(); + + int[][] samples = { + {0, 0}, + {100, 50}, + {359, 0}, + {0, 179}, + {359, 179} + }; + + for (int[] s : samples) { + int x = s[0]; + int y = s[1]; + + GeoPos geo = proj.gridToGeoPos(x, y); + boolean inside = proj.geoPosToGrid(geo, p); + + assertTrue("Point should be inside grid", inside); + assertEquals("x mismatch", x, p.x); + assertEquals("y mismatch", y, p.y); + } + } + + @Test + public void testGeoPosToGrid_outside() { + Point p = new Point(); + + assertFalse(proj.geoPosToGrid(new GeoPos(0f, -181f), p)); + assertFalse(proj.geoPosToGrid(new GeoPos(0f, 181f), p)); + + assertFalse(proj.geoPosToGrid(new GeoPos(91f, 0f), p)); + assertFalse(proj.geoPosToGrid(new GeoPos(-91f, 0f), p)); + } + + @Test(expected = IllegalArgumentException.class) + public void testGridToGeoPos_outOfRange_throws() { + proj.gridToGeoPos(-1, 0); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NcUtilTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NcUtilTest.java new file mode 100644 index 000000000..e7c113dc1 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NcUtilTest.java @@ -0,0 +1,63 @@ +package eu.esa.snap.cimr.netcdf; + +import org.junit.Test; +import ucar.ma2.DataType; +import ucar.nc2.Group; +import ucar.nc2.NetcdfFile; +import ucar.nc2.Variable; + +import static org.junit.Assert.*; + + +public class NcUtilTest { + + @Test + public void testFindGroupOrThrow_Found() { + Group.Builder rootBuilder = Group.builder(null).setName("root"); + Group.Builder childBuilder = Group.builder(rootBuilder).setName("child"); + rootBuilder.addGroup(childBuilder); + + NetcdfFile ncFile = NetcdfFile.builder() + .setLocation("test") + .setRootGroup(rootBuilder) + .build(); + + Group g = NcUtil.findGroupOrThrow(ncFile, "/child"); + assertNotNull(g); + assertEquals("child", g.getShortName()); + } + + @Test(expected = IllegalArgumentException.class) + public void testFindGroupOrThrow_NotFound() { + Group.Builder rootBuilder = Group.builder(null).setName("root"); + NetcdfFile ncFile = NetcdfFile.builder() + .setLocation("test") + .setRootGroup(rootBuilder) + .build(); + + NcUtil.findGroupOrThrow(ncFile, "/does/not/exist"); + } + + @Test + public void testFindVarOrThrow_Found() { + Group.Builder groupBuilder = Group.builder(null).setName("g"); + Variable.Builder varBuilder = Variable.builder() + .setName("v") + .setDataType(DataType.DOUBLE); + groupBuilder.addVariable(varBuilder); + + Group group = groupBuilder.build(null); + + Variable v = NcUtil.findVarOrThrow(group, "v"); + assertNotNull(v); + assertEquals("v", v.getShortName()); + } + + @Test(expected = IllegalArgumentException.class) + public void testFindVarOrThrow_NotFound() { + Group.Builder groupBuilder = Group.builder(null).setName("g"); + Group group = groupBuilder.build(null); + + NcUtil.findVarOrThrow(group, "missing"); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactoryTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactoryTest.java new file mode 100644 index 000000000..d20847aed --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactoryTest.java @@ -0,0 +1,218 @@ +package eu.esa.snap.cimr.netcdf; + + +import eu.esa.snap.cimr.cimr.CimrBandDescriptor; +import eu.esa.snap.cimr.cimr.CimrDescriptorKind; +import eu.esa.snap.cimr.cimr.CimrDimensions; +import eu.esa.snap.cimr.cimr.CimrFrequencyBand; +import eu.esa.snap.cimr.grid.CimrGeometryBand; +import eu.esa.snap.cimr.grid.CimrTiepointGeometry; +import org.esa.snap.core.datamodel.GeoPos; +import org.junit.Test; +import ucar.ma2.ArrayDouble; +import ucar.ma2.DataType; +import ucar.ma2.InvalidRangeException; +import ucar.nc2.*; + +import java.io.IOException; + +import static org.junit.Assert.*; + + +public class NetcdfCimrBandFactoryTest { + + + private static final double doubleErr = 1e-6; + + + @Test + public void testCreateGeometryBand_NormalVariable() throws IOException, InvalidRangeException { + int nScans = 2; + int nSamples = 3; + int nFeeds = 1; + + Dimension scanDim = new Dimension("n_scans", nScans); + Dimension sampleDim = new Dimension("n_samples_C_BAND", nSamples); + Dimension feedDim = new Dimension("n_feeds_C_BAND", nFeeds); + + Group.Builder root = Group.builder(null).setName("root"); + root.addDimension(scanDim).addDimension(sampleDim).addDimension(feedDim); + + Group.Builder dataGroup = Group.builder(root).setName("Data"); + root.addGroup(dataGroup); + + ArrayDouble.D3 data = new ArrayDouble.D3(nScans, nSamples, nFeeds); + for (int s = 0; s < nScans; s++) { + for (int smp = 0; smp < nSamples; smp++) { + data.set(s, smp, 0, 100 * s + smp); + } + } + + Variable.Builder varBuilder = Variable.builder() + .setName("altitude") + .setDataType(DataType.DOUBLE) + .setDimensionsByName("n_scans n_samples_C_BAND n_feeds_C_BAND") + .setCachedData(data, false); + dataGroup.addVariable(varBuilder); + + NetcdfFile ncFile = NetcdfFile.builder() + .setLocation("test") + .setRootGroup(root) + .build(); + + CimrDimensions dims = CimrDimensions.from(ncFile); + + CimrBandDescriptor desc = new CimrBandDescriptor( + "altitude", + "altitude", + CimrFrequencyBand.C_BAND, + new String[] {}, + "/Data", + 0, + CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + + GeoPos[][][] tp = new GeoPos[nScans][nSamples][1]; + for (int s = 0; s < nScans; s++) { + for (int smp = 0; smp < nSamples; smp++) { + tp[s][smp][0] = new GeoPos(0f, 0f); + } + } + CimrTiepointGeometry geom = new CimrTiepointGeometry(tp, nSamples); + + NetcdfCimrBandFactory factory = new NetcdfCimrBandFactory(ncFile, dims); + CimrGeometryBand band = factory.createGeometryBand(desc, geom); + + assertEquals(nScans, band.getScanCount()); + assertEquals(nSamples, band.getSampleCount()); + + assertEquals(0.0, band.getValue(0, 0), doubleErr); + assertEquals(2.0, band.getValue(0, 2), doubleErr); + assertEquals(100.0, band.getValue(1, 0), doubleErr); + assertEquals(102.0, band.getValue(1, 2), doubleErr); + } + + @Test + public void testCreateGeometryBand_TiepointVariableInterpolates() throws IOException, InvalidRangeException { + int nScans = 1; + int nTiepoints = 2; + int nFeeds = 1; + int nSamplesOut = 4; + + Dimension scanDim = new Dimension("n_scans", nScans); + Dimension tpDim = new Dimension("n_tiepoints_C_BAND", nTiepoints); + Dimension feedDim = new Dimension("n_feeds_C_BAND", nFeeds); + Dimension samplesDim = new Dimension("n_samples_C_BAND", nSamplesOut); + + Group.Builder root = Group.builder(null).setName("root"); + root.addDimension(scanDim) + .addDimension(tpDim) + .addDimension(feedDim) + .addDimension(samplesDim); + + Group.Builder dataGroup = Group.builder(root).setName("Data"); + root.addGroup(dataGroup); + + ArrayDouble.D3 data = new ArrayDouble.D3(nScans, nTiepoints, nFeeds); + data.set(0, 0, 0, 0.0); + data.set(0, 1, 0, 10.0); + + Variable.Builder varBuilder = Variable.builder() + .setName("tie_var") + .setDataType(DataType.DOUBLE) + .setDimensionsByName("n_scans n_tiepoints_C_BAND n_feeds_C_BAND") + .setCachedData(data, false); + dataGroup.addVariable(varBuilder); + + NetcdfFile ncFile = NetcdfFile.builder() + .setLocation("test") + .setRootGroup(root) + .build(); + + CimrDimensions dims = CimrDimensions.from(ncFile); + + CimrBandDescriptor desc = new CimrBandDescriptor( + "tie_var", + "tie_var", + CimrFrequencyBand.C_BAND, + new String[]{}, + "/Data", + 0, + CimrDescriptorKind.TIEPOINT_VARIABLE, + new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + + GeoPos[][][] tp = new GeoPos[nScans][nTiepoints][1]; + tp[0][0][0] = new GeoPos(0f, 0f); + tp[0][1][0] = new GeoPos(0f, 10f); + CimrTiepointGeometry geom = new CimrTiepointGeometry(tp, nSamplesOut); + + NetcdfCimrBandFactory factory = new NetcdfCimrBandFactory(ncFile, dims); + CimrGeometryBand band = factory.createGeometryBand(desc, geom); + + assertEquals(nScans, band.getScanCount()); + assertEquals(nSamplesOut, band.getSampleCount()); + + assertEquals(0.0, band.getValue(0, 0), doubleErr); + assertEquals(10.0, band.getValue(0, 3), doubleErr); + assertEquals(10.0 / 3, band.getValue(0, 1), doubleErr); + assertEquals(20.0 / 3, band.getValue(0, 2), doubleErr); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateGeometryBand_FailsForNon3DVariable() throws IOException, InvalidRangeException { + int nScans = 1; + int nSamples = 2; + + Dimension scanDim = new Dimension("n_scans", nScans); + Dimension sampleDim = new Dimension("n_samples_C_BAND", nSamples); + Dimension feedDim = new Dimension("n_feeds_C_BAND", 1); + + Group.Builder root = Group.builder(null).setName("root"); + root.addDimension(scanDim).addDimension(sampleDim).addDimension(feedDim); + + Group.Builder dataGroup = Group.builder(root).setName("Data"); + root.addGroup(dataGroup); + + ArrayDouble.D2 data = new ArrayDouble.D2(nScans, nSamples); + data.set(0, 0, 1.0); + data.set(0, 1, 2.0); + + Variable.Builder varBuilder = Variable.builder() + .setName("bad_var") + .setDataType(DataType.DOUBLE) + .setDimensionsByName("n_scans n_samples_C_BAND") + .setCachedData(data, false); + dataGroup.addVariable(varBuilder); + + NetcdfFile ncFile = NetcdfFile.builder() + .setLocation("test") + .setRootGroup(root) + .build(); + + CimrDimensions dims = CimrDimensions.from(ncFile); + + CimrBandDescriptor desc = new CimrBandDescriptor( + "bad_var", + "bad_var", + CimrFrequencyBand.C_BAND, + new String[]{}, + "/Data", + 0, + CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + + GeoPos[][][] tp = new GeoPos[1][2][1]; + tp[0][0][0] = new GeoPos(0f, 0f); + tp[0][1][0] = new GeoPos(0f, 1f); + CimrTiepointGeometry geom = new CimrTiepointGeometry(tp, nSamples); + + NetcdfCimrBandFactory factory = new NetcdfCimrBandFactory(ncFile, dims); + factory.createGeometryBand(desc, geom); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactoryTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactoryTest.java new file mode 100644 index 000000000..86d374ff9 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactoryTest.java @@ -0,0 +1,380 @@ +package eu.esa.snap.cimr.netcdf; + + +import eu.esa.snap.cimr.cimr.CimrBandDescriptor; +import eu.esa.snap.cimr.cimr.CimrDimensions; +import eu.esa.snap.cimr.cimr.CimrFrequencyBand; +import eu.esa.snap.cimr.cimr.CimrDescriptorKind; +import eu.esa.snap.cimr.grid.CimrGeometry; +import org.esa.snap.core.datamodel.GeoPos; +import org.junit.Test; +import ucar.ma2.ArrayDouble; +import ucar.ma2.DataType; +import ucar.ma2.InvalidRangeException; +import ucar.nc2.Dimension; +import ucar.nc2.Group; +import ucar.nc2.NetcdfFile; +import ucar.nc2.Variable; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; + + +import static org.junit.Assert.*; + + +public class NetcdfCimrGeometryFactoryTest { + + private static final double doubleErr = 1e-6; + + + @Test + public void testGetOrCreateGeometry_BuildsGeometryAndCaches() throws IOException, InvalidRangeException { + int nScans = 2; + int nTiepoints = 2; + int nFeeds = 2; + int nSamplesOut = 4; + + Dimension scanDim = new Dimension("n_scans", nScans); + Dimension tpDim = new Dimension("n_tiepoints_C_BAND", nTiepoints); + Dimension feedDim = new Dimension("n_feeds_C_BAND", nFeeds); + Dimension samplesDim = new Dimension("n_samples_C_BAND", nSamplesOut); + + Group.Builder rootBuilder = Group.builder(null).setName("root"); + rootBuilder.addDimension(scanDim) + .addDimension(tpDim) + .addDimension(feedDim) + .addDimension(samplesDim); + + ArrayDouble.D3 latData = new ArrayDouble.D3(nScans, nTiepoints, nFeeds); + ArrayDouble.D3 lonData = new ArrayDouble.D3(nScans, nTiepoints, nFeeds); + for (int s = 0; s < nScans; s++) { + for (int tp = 0; tp < nTiepoints; tp++) { + latData.set(s, tp, 0, 10.0 * s); + lonData.set(s, tp, 0, 100.0 * tp); + } + } + + Variable.Builder latBuilder = Variable.builder() + .setName("lat_c") + .setDataType(DataType.DOUBLE) + .setDimensionsByName("n_scans n_tiepoints_C_BAND n_feeds_C_BAND") + .setCachedData(latData, false); + Variable.Builder lonBuilder = Variable.builder() + .setName("lon_c") + .setDataType(DataType.DOUBLE) + .setDimensionsByName("n_scans n_tiepoints_C_BAND n_feeds_C_BAND") + .setCachedData(lonData, false); + + rootBuilder.addVariable(latBuilder); + rootBuilder.addVariable(lonBuilder); + + NetcdfFile ncFile = NetcdfFile.builder() + .setLocation("test") + .setRootGroup(rootBuilder) + .build(); + + CimrDimensions dims = CimrDimensions.from(ncFile); + String rootPath = "/"; + + CimrBandDescriptor latDesc = new CimrBandDescriptor( + "lat_c", "lat_c", CimrFrequencyBand.C_BAND, + new String[]{}, + rootPath, + 0, CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + CimrBandDescriptor lonDesc = new CimrBandDescriptor( + "lon_c", "lon_c", CimrFrequencyBand.C_BAND, + new String[]{}, + rootPath, + 0, CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + + CimrBandDescriptor varDesc = new CimrBandDescriptor( + "altitude", "altitude", CimrFrequencyBand.C_BAND, + new String[]{"lat_c", "lon_c"}, + rootPath, + 0, CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + + NetcdfCimrGeometryFactory factory = new NetcdfCimrGeometryFactory(ncFile, Arrays.asList(latDesc, lonDesc), dims); + + CimrGeometry geom1 = factory.getOrCreateGeometry(varDesc); + assertNotNull(geom1); + assertEquals(nScans, geom1.getScanCount()); + assertEquals(nSamplesOut, geom1.getSampleCount()); + + GeoPos g0_start = geom1.getGeoPos(0, 0, 0); + GeoPos g0_end = geom1.getGeoPos(0, nSamplesOut - 1, 0); + assertEquals(0.0, g0_start.getLat(), doubleErr); + assertEquals(0.0, g0_start.getLon(), doubleErr); + assertEquals(0.0, g0_end.getLat(), doubleErr); + assertEquals(100.0, g0_end.getLon(), doubleErr); + + GeoPos g1_start = geom1.getGeoPos(1, 0, 0); + GeoPos g1_end = geom1.getGeoPos(1, nSamplesOut - 1, 0); + assertEquals(10.0, g1_start.getLat(), doubleErr); + assertEquals(0.0, g1_start.getLon(), doubleErr); + assertEquals(10.0, g1_end.getLat(), doubleErr); + assertEquals(100.0, g1_end.getLon(), doubleErr); + + CimrGeometry geom2 = factory.getOrCreateGeometry(varDesc); + assertSame(geom1, geom2); + + assertEquals(4, geom2.getSampleCount()); + } + + @Test + public void testClearCacheCreatesNewInstance() throws IOException, InvalidRangeException { + int nScans = 2; + int nTiepoints = 2; + int nFeeds = 2; + int nSamplesOut = 4; + + Dimension scanDim = new Dimension("n_scans", nScans); + Dimension tpDim = new Dimension("n_tiepoints_C_BAND", nTiepoints); + Dimension feedDim = new Dimension("n_feeds_C_BAND", nFeeds); + Dimension samplesDim = new Dimension("n_samples_C_BAND", nSamplesOut); + + Group.Builder rootBuilder = Group.builder(null).setName("root"); + rootBuilder.addDimension(scanDim) + .addDimension(tpDim) + .addDimension(feedDim) + .addDimension(samplesDim); + + ArrayDouble.D3 latData = new ArrayDouble.D3(nScans, nTiepoints, nFeeds); + ArrayDouble.D3 lonData = new ArrayDouble.D3(nScans, nTiepoints, nFeeds); + latData.set(0, 0, 0, 42.0); + lonData.set(0, 0, 0, 7.0); + + Variable.Builder latBuilder = Variable.builder() + .setName("lat_c") + .setDataType(DataType.DOUBLE) + .setDimensionsByName("n_scans n_tiepoints_C_BAND n_feeds_C_BAND") + .setCachedData(latData, false); + Variable.Builder lonBuilder = Variable.builder() + .setName("lon_c") + .setDataType(DataType.DOUBLE) + .setDimensionsByName("n_scans n_tiepoints_C_BAND n_feeds_C_BAND") + .setCachedData(lonData, false); + + rootBuilder.addVariable(latBuilder); + rootBuilder.addVariable(lonBuilder); + + NetcdfFile ncFile = NetcdfFile.builder() + .setLocation("test") + .setRootGroup(rootBuilder) + .build(); + + CimrDimensions dims = CimrDimensions.from(ncFile); + String rootPath = "/"; + + CimrBandDescriptor latDesc = new CimrBandDescriptor( + "lat_c", "lat_c", CimrFrequencyBand.C_BAND, + new String[]{}, + rootPath, 0, CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + CimrBandDescriptor lonDesc = new CimrBandDescriptor( + "lon_c", "lon_c", CimrFrequencyBand.C_BAND, + new String[]{}, + rootPath, 0, CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + + CimrBandDescriptor varDesc = new CimrBandDescriptor( + "altitude", "altitude", CimrFrequencyBand.C_BAND, + new String[]{"lat_c", "lon_c"}, + rootPath, 0, CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + + NetcdfCimrGeometryFactory factory = new NetcdfCimrGeometryFactory(ncFile, Arrays.asList(latDesc, lonDesc), dims); + + CimrGeometry g1 = factory.getOrCreateGeometry(varDesc); + factory.clearCache(); + CimrGeometry g2 = factory.getOrCreateGeometry(varDesc); + + assertNotSame(g1, g2); + GeoPos p = g2.getGeoPos(0, 0, 0); + assertEquals(42.0, p.getLat(), doubleErr); + assertEquals(7.0, p.getLon(), doubleErr); + } + + @Test(expected = IllegalStateException.class) + public void testGetOrCreateGeometry_FailsForInvalidGeometryNames() throws IOException, InvalidRangeException { + Group.Builder rootBuilder = Group.builder(null).setName("root"); + NetcdfFile ncFile = NetcdfFile.builder() + .setLocation("test") + .setRootGroup(rootBuilder) + .build(); + + CimrDimensions dims = CimrDimensions.from(ncFile); + + NetcdfCimrGeometryFactory factory = new NetcdfCimrGeometryFactory(ncFile, Collections.emptyList(), dims); + + CimrBandDescriptor varDesc = new CimrBandDescriptor( + "altitude", "altitude", CimrFrequencyBand.C_BAND, + null, + "root", 0, CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + + factory.getOrCreateGeometry(varDesc); + } + + @Test(expected = IllegalStateException.class) + public void testGetOrCreateGeometry_FailsWhenGeometryDescriptorsMissing() throws IOException, InvalidRangeException { + int nScans = 2; + int nTiepoints = 2; + int nFeeds = 2; + int nSamplesOut = 4; + + Dimension scanDim = new Dimension("n_scans", nScans); + Dimension tpDim = new Dimension("n_tiepoints_C_BAND", nTiepoints); + Dimension feedDim = new Dimension("n_feeds_C_BAND", nFeeds); + Dimension samplesDim = new Dimension("n_samples_C_BAND", nSamplesOut); + + Group.Builder rootBuilder = Group.builder(null).setName("root"); + rootBuilder.addDimension(scanDim) + .addDimension(tpDim) + .addDimension(feedDim) + .addDimension(samplesDim); + + ArrayDouble.D3 latData = new ArrayDouble.D3(nScans, nTiepoints, nFeeds); + ArrayDouble.D3 lonData = new ArrayDouble.D3(nScans, nTiepoints, nFeeds); + latData.set(0, 0, 0, 0.0); + lonData.set(0, 0, 0, 0.0); + + Variable.Builder latBuilder = Variable.builder() + .setName("lat_c") + .setDataType(DataType.DOUBLE) + .setDimensionsByName("n_scans n_tiepoints_C_BAND n_feeds_C_BAND") + .setCachedData(latData, false); + Variable.Builder lonBuilder = Variable.builder() + .setName("lon_c") + .setDataType(DataType.DOUBLE) + .setDimensionsByName("n_scans n_tiepoints_C_BAND n_feeds_C_BAND") + .setCachedData(lonData, false); + + rootBuilder.addVariable(latBuilder); + rootBuilder.addVariable(lonBuilder); + + NetcdfFile ncFile = NetcdfFile.builder() + .setLocation("test") + .setRootGroup(rootBuilder) + .build(); + + CimrDimensions dims = CimrDimensions.from(ncFile); + String rootPath = ncFile.getRootGroup().getShortName(); + + CimrBandDescriptor latDesc = new CimrBandDescriptor( + "lat_c", "lat_c", CimrFrequencyBand.C_BAND, + new String[]{}, + rootPath, 0, CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + + CimrBandDescriptor varDesc = new CimrBandDescriptor( + "altitude", "altitude", CimrFrequencyBand.C_BAND, + new String[]{"lat_c", "lon_c"}, + rootPath, 0, CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + + NetcdfCimrGeometryFactory factory = new NetcdfCimrGeometryFactory(ncFile, Collections.singletonList(latDesc), dims); + + factory.getOrCreateGeometry(varDesc); + } + + @Test + public void testGetOrCreateGeometry_UsesVariableFeedIndex() throws IOException, InvalidRangeException { + int nScans = 2; + int nTiepoints = 2; + int nFeeds = 2; + int nSamplesOut = 4; + + Dimension scanDim = new Dimension("n_scans", nScans); + Dimension tpDim = new Dimension("n_tiepoints_C_BAND", nTiepoints); + Dimension feedDim = new Dimension("n_feeds_C_BAND", nFeeds); + Dimension samplesDim = new Dimension("n_samples_C_BAND", nSamplesOut); + + Group.Builder rootBuilder = Group.builder(null).setName("root"); + rootBuilder.addDimension(scanDim) + .addDimension(tpDim) + .addDimension(feedDim) + .addDimension(samplesDim); + + ArrayDouble.D3 latData = new ArrayDouble.D3(nScans, nTiepoints, nFeeds); + ArrayDouble.D3 lonData = new ArrayDouble.D3(nScans, nTiepoints, nFeeds); + latData.set(0, 0, 0, 1.0); + lonData.set(0, 0, 0, 2.0); + latData.set(0, 0, 1, 10.0); + lonData.set(0, 0, 1, 20.0); + + Variable.Builder latBuilder = Variable.builder() + .setName("lat_c") + .setDataType(DataType.DOUBLE) + .setDimensionsByName("n_scans n_tiepoints_C_BAND n_feeds_C_BAND") + .setCachedData(latData, false); + Variable.Builder lonBuilder = Variable.builder() + .setName("lon_c") + .setDataType(DataType.DOUBLE) + .setDimensionsByName("n_scans n_tiepoints_C_BAND n_feeds_C_BAND") + .setCachedData(lonData, false); + + rootBuilder.addVariable(latBuilder); + rootBuilder.addVariable(lonBuilder); + + NetcdfFile ncFile = NetcdfFile.builder() + .setLocation("test") + .setRootGroup(rootBuilder) + .build(); + + CimrDimensions dims = CimrDimensions.from(ncFile); + String rootPath = "/"; + + CimrBandDescriptor latDesc = new CimrBandDescriptor( + "lat_c", "lat_c", CimrFrequencyBand.C_BAND, + new String[]{}, + rootPath, 0, CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + CimrBandDescriptor lonDesc = new CimrBandDescriptor( + "lon_c", "lon_c", CimrFrequencyBand.C_BAND, + new String[]{}, + rootPath, 0, CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + + CimrBandDescriptor varDesc = new CimrBandDescriptor( + "altitude", "altitude", CimrFrequencyBand.C_BAND, + new String[]{"lat_c", "lon_c"}, + rootPath, 1, CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double" + ); + + NetcdfCimrGeometryFactory factory = new NetcdfCimrGeometryFactory(ncFile, Arrays.asList(latDesc, lonDesc), dims); + + CimrGeometry geom = factory.getOrCreateGeometry(varDesc); + GeoPos p = geom.getGeoPos(0, 0, 0); + assertEquals(10.0, p.getLat(), doubleErr); + assertEquals(20.0, p.getLon(), doubleErr); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index c4eca328a..65d530465 100644 --- a/pom.xml +++ b/pom.xml @@ -84,6 +84,7 @@ + cimr-reader jlinda rstb sar-cloud From 491485a8f04865862b919432e2692466e51a089b Mon Sep 17 00:00:00 2001 From: Benjamin Lutz Date: Wed, 26 Nov 2025 12:42:39 +0100 Subject: [PATCH 02/11] added attributes to bands --- .../snap/cimr/cimr/CimrBandDescriptor.java | 14 +++++++- .../esa/snap/cimr/cimr/CimrFrequencyBand.java | 22 +++++++++--- .../cimr/cimr/CimrSnapProductBuilder.java | 7 +++- .../esa/snap/cimr/config/CimrBandEntry.java | 2 ++ .../snap/cimr/config/CimrConfigLoader.java | 4 ++- .../esa/snap/cimr/config/cimr-l1b-config.json | 36 ++++++++++++++----- .../eu/esa/snap/cimr/config/test-config.json | 36 ++++++++++++++----- .../esa/snap/cimr/CimrReaderContextTest.java | 6 ++-- .../snap/cimr/cimr/CimrDescriptorSetTest.java | 4 ++- .../snap/cimr/cimr/CimrFrequencyBandTest.java | 25 +++++++++++++ .../snap/cimr/cimr/CimrGridProductTest.java | 10 +++--- .../cimr/cimr/CimrSnapProductBuilderTest.java | 16 ++++++--- .../cimr/config/CimrConfigLoaderTest.java | 6 ++++ .../cimr/grid/LazyGridBandDataSourceTest.java | 4 ++- .../netcdf/NetcdfCimrBandFactoryTest.java | 12 +++++-- .../netcdf/NetcdfCimrGeometryFactoryTest.java | 24 ++++++------- 16 files changed, 174 insertions(+), 54 deletions(-) create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrFrequencyBandTest.java diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrBandDescriptor.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrBandDescriptor.java index 068deb56e..7d0a1092e 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrBandDescriptor.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrBandDescriptor.java @@ -12,9 +12,11 @@ public class CimrBandDescriptor { private final CimrDescriptorKind kind; private final String[] dimensions; private final String dataType; + private final String unit; + private final String description; - public CimrBandDescriptor(String name, String valueVarName, CimrFrequencyBand band, String[] geometryNames, String groupPath, int feedIndex, CimrDescriptorKind kind, String[] dimensions, String dataType) { + public CimrBandDescriptor(String name, String valueVarName, CimrFrequencyBand band, String[] geometryNames, String groupPath, int feedIndex, CimrDescriptorKind kind, String[] dimensions, String dataType, String unit, String description) { this.name = name; this.valueVarName = valueVarName; this.band = band; @@ -24,6 +26,8 @@ public CimrBandDescriptor(String name, String valueVarName, CimrFrequencyBand ba this.kind = kind; this.dimensions = dimensions; this.dataType = dataType; + this.unit = unit; + this.description = description; } public String getName() { @@ -61,4 +65,12 @@ public String[] getDimensions() { public String getDataType() { return dataType; } + + public String getUnit() { + return unit; + } + + public String getDescription() { + return description; + } } diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFrequencyBand.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFrequencyBand.java index 00779f97c..039a84736 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFrequencyBand.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFrequencyBand.java @@ -1,9 +1,21 @@ package eu.esa.snap.cimr.cimr; public enum CimrFrequencyBand { - L_BAND, - C_BAND, - X_BAND, - KU_BAND, - KA_BAND + + L_BAND(2.12e8f), + C_BAND(4.33e7f), + X_BAND(2.82e7f), + KU_BAND(1.6e7f), + KA_BAND(8.22e6f); + + + private final float spectralWaveLength; + + CimrFrequencyBand(float spectralWaveLength) { + this.spectralWaveLength = spectralWaveLength; + } + + public float getSpectralWaveLength() { + return spectralWaveLength; + } } diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilder.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilder.java index 251df84a7..48ae9e2e1 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilder.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilder.java @@ -59,7 +59,12 @@ private static void addBands(CimrGridProduct cimrProduct, int width, int height, GridBandDataSource dataSource = e.getValue(); Band band = product.addBand(desc.getName(), ProductData.TYPE_FLOAT64); - // TODO set noDataValue, description, unit, spectral_wavelength on band + band.setDescription(desc.getDescription()); + band.setUnit(desc.getUnit()); + band.setNoDataValue(Double.NaN); + band.setNoDataValueUsed(true); + band.setSpectralWavelength(desc.getBand().getSpectralWaveLength()); + CimrGridMultiLevelSource.attachToBand(band, dataSource, mlModel); } } diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrBandEntry.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrBandEntry.java index 097135b46..6ee63478a 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrBandEntry.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrBandEntry.java @@ -10,4 +10,6 @@ public class CimrBandEntry { public int feedIndex; public String[] dimensions; public String dataType; + public String unit = ""; + public String description = ""; } diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrConfigLoader.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrConfigLoader.java index a58d6b1b3..9fc03ad01 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrConfigLoader.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrConfigLoader.java @@ -50,7 +50,9 @@ private static CimrBandDescriptor toDescriptor(CimrBandEntry e, CimrDescriptorKi e.feedIndex, kind, e.dimensions, - e.dataType + e.dataType, + e.unit, + e.description ); } } diff --git a/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/cimr-l1b-config.json b/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/cimr-l1b-config.json index 2871fd2e5..ba11b4e02 100644 --- a/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/cimr-l1b-config.json +++ b/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/cimr-l1b-config.json @@ -8,7 +8,9 @@ "groupPath": "/Data/Measurement_Data/C_BAND/", "feedIndex": 0, "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "K", + "description": "Brightness temperature of the Earth, in H polarization, from raw counts (no RFI mitigation)" }, { "name": "C_BAND_raw_bt_v_feed1", @@ -18,7 +20,9 @@ "groupPath": "/Data/Measurement_Data/C_BAND/", "feedIndex": 0, "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "K", + "description": "Brightness temperature of the Earth, in V polarization, from raw counts (no RFI mitigation)" }, { "name": "C_BAND_raw_bt_h_feed2", @@ -28,7 +32,9 @@ "groupPath": "/Data/Measurement_Data/C_BAND/", "feedIndex": 1, "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "K", + "description": "Brightness temperature of the Earth, in H polarization, from raw counts (no RFI mitigation)" }, { "name": "C_BAND_raw_bt_v_feed2", @@ -38,7 +44,9 @@ "groupPath": "/Data/Measurement_Data/C_BAND/", "feedIndex": 1, "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "K", + "description": "Brightness temperature of the Earth, in V polarization, from raw counts (no RFI mitigation)" } ], "tiepointVariables": [ @@ -50,7 +58,9 @@ "groupPath": "/Data/Navigation_Data/C_BAND/", "feedIndex": 0, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "m", + "description": "Altitude for intersection of the LOS with the earth surface for the C band Earth views" } ], "geometries": [ @@ -61,7 +71,9 @@ "groupPath": "/Data/Navigation_Data/C_BAND/", "feedIndex": 0, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "deg", + "description": "Latitude of Earth surface point in the boresight direction for the C band acquisitions" }, { "name": "C_BAND_longitude_feed1", @@ -70,7 +82,9 @@ "groupPath": "/Data/Navigation_Data/C_BAND/", "feedIndex": 0, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "deg", + "description": "Longitude of Earth surface point in the boresight direction for the C band acquisitions" }, { "name": "C_BAND_latitude_feed2", @@ -79,7 +93,9 @@ "groupPath": "/Data/Navigation_Data/C_BAND/", "feedIndex": 1, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "deg", + "description": "Latitude of Earth surface point in the boresight direction for the C band acquisitions" }, { "name": "C_BAND_longitude_feed2", @@ -88,7 +104,9 @@ "groupPath": "/Data/Navigation_Data/C_BAND/", "feedIndex": 1, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "deg", + "description": "Longitude of Earth surface point in the boresight direction for the C band acquisitions" } ] } \ No newline at end of file diff --git a/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/test-config.json b/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/test-config.json index 2871fd2e5..ba11b4e02 100644 --- a/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/test-config.json +++ b/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/test-config.json @@ -8,7 +8,9 @@ "groupPath": "/Data/Measurement_Data/C_BAND/", "feedIndex": 0, "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "K", + "description": "Brightness temperature of the Earth, in H polarization, from raw counts (no RFI mitigation)" }, { "name": "C_BAND_raw_bt_v_feed1", @@ -18,7 +20,9 @@ "groupPath": "/Data/Measurement_Data/C_BAND/", "feedIndex": 0, "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "K", + "description": "Brightness temperature of the Earth, in V polarization, from raw counts (no RFI mitigation)" }, { "name": "C_BAND_raw_bt_h_feed2", @@ -28,7 +32,9 @@ "groupPath": "/Data/Measurement_Data/C_BAND/", "feedIndex": 1, "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "K", + "description": "Brightness temperature of the Earth, in H polarization, from raw counts (no RFI mitigation)" }, { "name": "C_BAND_raw_bt_v_feed2", @@ -38,7 +44,9 @@ "groupPath": "/Data/Measurement_Data/C_BAND/", "feedIndex": 1, "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "K", + "description": "Brightness temperature of the Earth, in V polarization, from raw counts (no RFI mitigation)" } ], "tiepointVariables": [ @@ -50,7 +58,9 @@ "groupPath": "/Data/Navigation_Data/C_BAND/", "feedIndex": 0, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "m", + "description": "Altitude for intersection of the LOS with the earth surface for the C band Earth views" } ], "geometries": [ @@ -61,7 +71,9 @@ "groupPath": "/Data/Navigation_Data/C_BAND/", "feedIndex": 0, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "deg", + "description": "Latitude of Earth surface point in the boresight direction for the C band acquisitions" }, { "name": "C_BAND_longitude_feed1", @@ -70,7 +82,9 @@ "groupPath": "/Data/Navigation_Data/C_BAND/", "feedIndex": 0, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "deg", + "description": "Longitude of Earth surface point in the boresight direction for the C band acquisitions" }, { "name": "C_BAND_latitude_feed2", @@ -79,7 +93,9 @@ "groupPath": "/Data/Navigation_Data/C_BAND/", "feedIndex": 1, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "deg", + "description": "Latitude of Earth surface point in the boresight direction for the C band acquisitions" }, { "name": "C_BAND_longitude_feed2", @@ -88,7 +104,9 @@ "groupPath": "/Data/Navigation_Data/C_BAND/", "feedIndex": 1, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], - "dataType": "double" + "dataType": "double", + "unit": "deg", + "description": "Longitude of Earth surface point in the boresight direction for the C band acquisitions" } ] } \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextTest.java index b3fbbc53d..155e0da64 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextTest.java @@ -218,7 +218,7 @@ public void testClearCache_clearsBandCacheAndGeometryCache() { new String[]{"lat", "lon"}, "/Data", 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); CimrDescriptorSet descriptorSet = new CimrDescriptorSet( Collections.singletonList(varDesc), @@ -307,7 +307,9 @@ private CimrBandDescriptor createTestDescriptor() { 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", + "", + "" ); } diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrDescriptorSetTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrDescriptorSetTest.java index 280fc0eaa..ed3cae1e0 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrDescriptorSetTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrDescriptorSetTest.java @@ -96,7 +96,9 @@ private static CimrBandDescriptor descriptor(String name) { 0, CimrDescriptorKind.GEOMETRY, new String[]{"n_scans", "n_samples_C_BAND"}, - "double" + "double", + "", + "" ); } } \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrFrequencyBandTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrFrequencyBandTest.java new file mode 100644 index 000000000..5015cfbe5 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrFrequencyBandTest.java @@ -0,0 +1,25 @@ +package eu.esa.snap.cimr.cimr; + +import org.junit.Test; + +import static org.junit.Assert.*; + + +public class CimrFrequencyBandTest { + + + @Test + public void testSpectralWaveLengths() { + float sw_L_BAND = CimrFrequencyBand.L_BAND.getSpectralWaveLength(); + float sw_C_BAND = CimrFrequencyBand.C_BAND.getSpectralWaveLength(); + float sw_X_BAND = CimrFrequencyBand.X_BAND.getSpectralWaveLength(); + float sw_KU_BAND = CimrFrequencyBand.KU_BAND.getSpectralWaveLength(); + float sw_KA_BAND = CimrFrequencyBand.KA_BAND.getSpectralWaveLength(); + + assertEquals(212000000f, sw_L_BAND, 0.1); + assertEquals(43300000f, sw_C_BAND, 0.1); + assertEquals(28200000f, sw_X_BAND, 0.1); + assertEquals(16000000f, sw_KU_BAND, 0.1); + assertEquals(8220000f, sw_KA_BAND, 0.1); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridProductTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridProductTest.java index edcc40452..908efcca5 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridProductTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridProductTest.java @@ -29,7 +29,7 @@ public void testAddAndGetBands() { "/Data/Measurement_Data/C_BAND/", 1, CimrDescriptorKind.VARIABLE, new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); CimrBandDescriptor band2 = new CimrBandDescriptor( "X_raw_bt_v_feed1", "raw_bt_h", CimrFrequencyBand.X_BAND, @@ -37,7 +37,7 @@ public void testAddAndGetBands() { "/Data/Measurement_Data/C_BAND/", 2, CimrDescriptorKind.VARIABLE, new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); double[] data1 = {1.0, 2.0}; @@ -68,7 +68,7 @@ public void testBuildLazyCreatesBandsFromDescriptorSet() { "/Geolocation/", 0, CimrDescriptorKind.TIEPOINT_VARIABLE, new String[] {"n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); CimrBandDescriptor measDesc = new CimrBandDescriptor( "C_raw_bt_h_feed1", "raw_bt_h", CimrFrequencyBand.C_BAND, @@ -76,7 +76,7 @@ public void testBuildLazyCreatesBandsFromDescriptorSet() { "/Data/Measurement_Data/C_BAND/", 0, CimrDescriptorKind.VARIABLE, new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); CimrDescriptorSet descriptorSet = new CimrDescriptorSet( @@ -112,7 +112,7 @@ public void testGetBandsIsUnmodifiable() { "/Data/Measurement_Data/C_BAND/", 1, CimrDescriptorKind.VARIABLE, new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); GridBandDataSource ds = new GlobalGridBandDataSource(2, 1, new double[]{1.0, 2.0}); product.addBand(band, ds); diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilderTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilderTest.java index e86a07dc6..6571ef1b2 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilderTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilderTest.java @@ -34,7 +34,7 @@ public void testBuildSnapProduct_createsBandsAndValues() throws Exception { "/Data/Measurement_Data/C_BAND/", 1, CimrDescriptorKind.VARIABLE, new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); double[] data = {1.0, 2.0}; @@ -72,7 +72,8 @@ public void testBuildSnapProduct_setsMetadataAndAutoGrouping() throws Exception "/Data/Measurement_Data/C_BAND/", 1, CimrDescriptorKind.VARIABLE, new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "K", + "Brightness temperature of the Earth, in H polarization, from raw counts (no RFI mitigation)" ); double[] data = {1.0, 2.0}; @@ -87,6 +88,13 @@ public void testBuildSnapProduct_setsMetadataAndAutoGrouping() throws Exception assertNotNull(product.getFileLocation()); assertTrue(product.getFileLocation().getPath().endsWith(path)); assertEquals("L_BAND:C_BAND:X_BAND:KU_BAND:KA_BAND", product.getAutoGrouping().toString()); + + Band band = product.getBand("C_raw_bt_h_feed1"); + assertEquals("K", band.getUnit()); + assertEquals("Brightness temperature of the Earth, in H polarization, from raw counts (no RFI mitigation)", band.getDescription()); + assertEquals(Double.NaN, band.getNoDataValue(), doubleErr); + assertTrue(band.isNoDataValueSet()); + assertEquals(43300000f, band.getSpectralWavelength(), doubleErr); } @Test @@ -106,7 +114,7 @@ public void testBuildSnapProduct_withMultipleBands_allPresentAndCorrect() throws "/Data/Measurement_Data/C_BAND/", 0, CimrDescriptorKind.VARIABLE, new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); CimrBandDescriptor band2 = new CimrBandDescriptor( "band2", "raw2", CimrFrequencyBand.X_BAND, @@ -114,7 +122,7 @@ public void testBuildSnapProduct_withMultipleBands_allPresentAndCorrect() throws "/Data/Measurement_Data/X_BAND/", 0, CimrDescriptorKind.VARIABLE, new String[] {"n_scans", "n_samples_X_BAND", "n_feeds_X_BAND"}, - "double" + "double", "", "" ); GridBandDataSource ds1 = new GlobalGridBandDataSource(2, 1, new double[]{1.0, 2.0}); diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/config/CimrConfigLoaderTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/config/CimrConfigLoaderTest.java index a76f43f26..dbbb616f7 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/config/CimrConfigLoaderTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/config/CimrConfigLoaderTest.java @@ -39,6 +39,8 @@ public void testLoadTestConfigJson() throws Exception { assertArrayEquals(new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, m0.getDimensions()); assertEquals("double", m0.getDataType()); assertArrayEquals(new String[]{"C_BAND_latitude_feed1", "C_BAND_longitude_feed1"}, m0.getGeometryNames()); + assertEquals("K", m0.getUnit()); + assertEquals("Brightness temperature of the Earth, in H polarization, from raw counts (no RFI mitigation)", m0.getDescription()); CimrBandDescriptor tpVal0 = tpVars.get(0); @@ -51,6 +53,8 @@ public void testLoadTestConfigJson() throws Exception { assertArrayEquals(new String[]{"n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"}, tpVal0.getDimensions()); assertEquals("double", tpVal0.getDataType()); assertArrayEquals(new String[]{"C_BAND_latitude_feed1", "C_BAND_longitude_feed1"}, tpVal0.getGeometryNames()); + assertEquals("m", tpVal0.getUnit()); + assertEquals("Altitude for intersection of the LOS with the earth surface for the C band Earth views", tpVal0.getDescription()); CimrBandDescriptor tpGeom0 = tpGeoms.get(0); @@ -62,6 +66,8 @@ public void testLoadTestConfigJson() throws Exception { assertEquals(CimrDescriptorKind.GEOMETRY, tpGeom0.getKind()); assertArrayEquals(new String[]{"n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"}, tpGeom0.getDimensions()); assertEquals("double", tpGeom0.getDataType()); + assertEquals("deg", tpGeom0.getUnit()); + assertEquals("Latitude of Earth surface point in the boresight direction for the C band acquisitions", tpGeom0.getDescription()); } diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyGridBandDataSourceTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyGridBandDataSourceTest.java index 340221444..9182314c2 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyGridBandDataSourceTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyGridBandDataSourceTest.java @@ -98,7 +98,9 @@ private static CimrBandDescriptor createDummyDescriptor(String name) { 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", + "", + "" ); } } \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactoryTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactoryTest.java index d20847aed..3bff54016 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactoryTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactoryTest.java @@ -71,7 +71,9 @@ public void testCreateGeometryBand_NormalVariable() throws IOException, InvalidR 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", + "", + "" ); GeoPos[][][] tp = new GeoPos[nScans][nSamples][1]; @@ -142,7 +144,9 @@ public void testCreateGeometryBand_TiepointVariableInterpolates() throws IOExcep 0, CimrDescriptorKind.TIEPOINT_VARIABLE, new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", + "", + "" ); GeoPos[][][] tp = new GeoPos[nScans][nTiepoints][1]; @@ -204,7 +208,9 @@ public void testCreateGeometryBand_FailsForNon3DVariable() throws IOException, I 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", + "", + "" ); GeoPos[][][] tp = new GeoPos[1][2][1]; diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactoryTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactoryTest.java index 86d374ff9..608bf65e1 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactoryTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactoryTest.java @@ -84,7 +84,7 @@ public void testGetOrCreateGeometry_BuildsGeometryAndCaches() throws IOException rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); CimrBandDescriptor lonDesc = new CimrBandDescriptor( "lon_c", "lon_c", CimrFrequencyBand.C_BAND, @@ -92,7 +92,7 @@ public void testGetOrCreateGeometry_BuildsGeometryAndCaches() throws IOException rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); CimrBandDescriptor varDesc = new CimrBandDescriptor( @@ -101,7 +101,7 @@ public void testGetOrCreateGeometry_BuildsGeometryAndCaches() throws IOException rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); NetcdfCimrGeometryFactory factory = new NetcdfCimrGeometryFactory(ncFile, Arrays.asList(latDesc, lonDesc), dims); @@ -181,14 +181,14 @@ public void testClearCacheCreatesNewInstance() throws IOException, InvalidRangeE new String[]{}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); CimrBandDescriptor lonDesc = new CimrBandDescriptor( "lon_c", "lon_c", CimrFrequencyBand.C_BAND, new String[]{}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); CimrBandDescriptor varDesc = new CimrBandDescriptor( @@ -196,7 +196,7 @@ public void testClearCacheCreatesNewInstance() throws IOException, InvalidRangeE new String[]{"lat_c", "lon_c"}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); NetcdfCimrGeometryFactory factory = new NetcdfCimrGeometryFactory(ncFile, Arrays.asList(latDesc, lonDesc), dims); @@ -228,7 +228,7 @@ public void testGetOrCreateGeometry_FailsForInvalidGeometryNames() throws IOExce null, "root", 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); factory.getOrCreateGeometry(varDesc); @@ -284,7 +284,7 @@ public void testGetOrCreateGeometry_FailsWhenGeometryDescriptorsMissing() throws new String[]{}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); CimrBandDescriptor varDesc = new CimrBandDescriptor( @@ -292,7 +292,7 @@ public void testGetOrCreateGeometry_FailsWhenGeometryDescriptorsMissing() throws new String[]{"lat_c", "lon_c"}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); NetcdfCimrGeometryFactory factory = new NetcdfCimrGeometryFactory(ncFile, Collections.singletonList(latDesc), dims); @@ -352,14 +352,14 @@ public void testGetOrCreateGeometry_UsesVariableFeedIndex() throws IOException, new String[]{}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); CimrBandDescriptor lonDesc = new CimrBandDescriptor( "lon_c", "lon_c", CimrFrequencyBand.C_BAND, new String[]{}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); CimrBandDescriptor varDesc = new CimrBandDescriptor( @@ -367,7 +367,7 @@ public void testGetOrCreateGeometry_UsesVariableFeedIndex() throws IOException, new String[]{"lat_c", "lon_c"}, rootPath, 1, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, - "double" + "double", "", "" ); NetcdfCimrGeometryFactory factory = new NetcdfCimrGeometryFactory(ncFile, Arrays.asList(latDesc, lonDesc), dims); From 82cee73c9f3ba0f36ec5b81f73a4a7a7d8b1f7ed Mon Sep 17 00:00:00 2001 From: Benjamin Lutz Date: Wed, 26 Nov 2025 14:10:41 +0100 Subject: [PATCH 03/11] implemented readBandRasterDataImpl --- cimr-reader/pom.xml | 5 + .../esa/snap/cimr/CimrL1BProductReader.java | 12 +- .../snap/cimr/CimrL1BProductReaderTest.java | 128 ++++++++++++++++++ 3 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/CimrL1BProductReaderTest.java diff --git a/cimr-reader/pom.xml b/cimr-reader/pom.xml index 58d10ccbd..1111b5be5 100644 --- a/cimr-reader/pom.xml +++ b/cimr-reader/pom.xml @@ -39,6 +39,11 @@ netcdfAll ${netcdf.version} + + org.mockito + mockito-core + test + diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java index 6fe3e3a90..96b208548 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java @@ -15,20 +15,24 @@ import org.esa.snap.dataio.netcdf.util.NetcdfFileOpener; import ucar.nc2.NetcdfFile; +import java.awt.*; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; import java.io.File; import java.io.IOException; public class CimrL1BProductReader extends AbstractProductReader { - NetcdfFile ncFile; - CimrReaderContext readerContext; + private NetcdfFile ncFile; + private CimrReaderContext readerContext; public CimrL1BProductReader(ProductReaderPlugIn readerPlugIn) { super(readerPlugIn); } + // TODO BL write tests @Override protected Product readProductNodesImpl() throws IOException { final String path = getInputPath(); @@ -52,7 +56,9 @@ protected Product readProductNodesImpl() throws IOException { @Override protected void readBandRasterDataImpl(int sourceOffsetX, int sourceOffsetY, int sourceWidth, int sourceHeight, int sourceStepX, int sourceStepY, Band destBand, int destOffsetX, int destOffsetY, int destWidth, int destHeight, ProductData destBuffer, ProgressMonitor pm) throws IOException { - // TODO add direct exception here, this should not be called + final RenderedImage image = destBand.getSourceImage(); + final Raster data = image.getData(new Rectangle(destOffsetX, destOffsetY, destWidth, destHeight)); + data.getDataElements(destOffsetX, destOffsetY, destWidth, destHeight, destBuffer.getElems()); } @Override diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrL1BProductReaderTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrL1BProductReaderTest.java new file mode 100644 index 000000000..3390dc772 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrL1BProductReaderTest.java @@ -0,0 +1,128 @@ +package eu.esa.snap.cimr; + +import com.bc.ceres.core.ProgressMonitor; +import com.bc.ceres.multilevel.MultiLevelImage; +import com.bc.ceres.multilevel.MultiLevelSource; +import com.bc.ceres.multilevel.support.DefaultMultiLevelImage; +import com.bc.ceres.multilevel.support.DefaultMultiLevelSource; +import org.esa.snap.core.datamodel.Band; +import org.esa.snap.core.datamodel.ProductData; +import org.junit.Test; +import ucar.nc2.NetcdfFile; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.lang.reflect.Field; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + + +public class CimrL1BProductReaderTest { + + + @Test + public void testReadBandRasterDataImplCopiesFromSourceImage() throws Exception { + int width = 4; + int height = 3; + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + int value = 1; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + image.setRGB(x, y, value++); + } + } + + MultiLevelSource source = new DefaultMultiLevelSource(image, 1); + MultiLevelImage multiLevelImage = new DefaultMultiLevelImage(source); + + Band band = new TestBand(multiLevelImage); + CimrL1BProductReader reader = new CimrL1BProductReader( null); + + int destOffsetX = 1; + int destOffsetY = 1; + int destWidth = 2; + int destHeight = 2; + + ProductData destBuffer = ProductData.createInstance(ProductData.TYPE_INT32, + destWidth * destHeight); + + reader.readBandRasterDataImpl( + destOffsetX, destOffsetY, + destWidth, destHeight, + 1, 1, + band, + destOffsetX, destOffsetY, + destWidth, destHeight, + destBuffer, + ProgressMonitor.NULL + ); + + int[] expected = new int[destWidth * destHeight]; + int idx = 0; + for (int y = destOffsetY; y < destOffsetY + destHeight; y++) { + for (int x = destOffsetX; x < destOffsetX + destWidth; x++) { + expected[idx++] = image.getRGB(x, y); + } + } + + assertArrayEquals(expected, (int[]) destBuffer.getElems()); + } + + @Test + public void close_closesNcFileAndClearsContextAndNullsFields() throws IOException, NoSuchFieldException, IllegalAccessException { + CimrL1BProductReader reader = new CimrL1BProductReader(null); + + NetcdfFile ncFile = mock(NetcdfFile.class); + CimrReaderContext ctx = mock(CimrReaderContext.class); + + setField(reader, "ncFile", ncFile); + setField(reader, "readerContext", ctx); + + assertNotNull(getField(reader, "ncFile")); + assertNotNull(getField(reader, "readerContext")); + + reader.close(); + + verify(ncFile).close(); + verify(ctx).clearCache(); + assertNull(getField(reader, "ncFile")); + assertNull(getField(reader, "readerContext")); + } + + @Test + public void close_NullFields() throws IOException { + CimrL1BProductReader reader = new CimrL1BProductReader(null); + + reader.close(); + reader.close(); + } + + + + private static class TestBand extends Band { + private final MultiLevelImage sourceImage; + + TestBand(MultiLevelImage sourceImage) { + super("test_band", ProductData.TYPE_INT32, sourceImage.getWidth(), sourceImage.getHeight()); + this.sourceImage = sourceImage; + } + + @Override + public MultiLevelImage getSourceImage() { + return sourceImage; + } + } + + private static void setField(Object target, String name, Object value) throws NoSuchFieldException, IllegalAccessException { + Field f = target.getClass().getDeclaredField(name); + f.setAccessible(true); + f.set(target, value); + } + + private static Object getField(Object target, String name) throws NoSuchFieldException, IllegalAccessException { + Field f = target.getClass().getDeclaredField(name); + f.setAccessible(true); + return f.get(target); + } +} \ No newline at end of file From 420d6674bba2c4d8ae3257b285139a6133f550df Mon Sep 17 00:00:00 2001 From: Benjamin Lutz Date: Thu, 27 Nov 2025 13:00:17 +0100 Subject: [PATCH 04/11] lazy geoCoding and refactoring --- .../cimr/cimr/CimrGridMultiLevelSource.java | 12 +- .../cimr/cimr/CimrSnapProductBuilder.java | 33 ++---- .../esa/snap/cimr/grid/LazyCrsGeoCoding.java | 109 ++++++++++++++++++ .../cimr/CimrGridMultiLevelSourceTest.java | 28 +++-- .../snap/cimr/grid/LazyCrsGeoCodingTest.java | 91 +++++++++++++++ 5 files changed, 236 insertions(+), 37 deletions(-) create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/grid/LazyCrsGeoCoding.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyCrsGeoCodingTest.java diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSource.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSource.java index 1f14fd280..213c9e91f 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSource.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSource.java @@ -4,15 +4,20 @@ import com.bc.ceres.multilevel.MultiLevelSource; import com.bc.ceres.multilevel.support.AbstractMultiLevelSource; import com.bc.ceres.multilevel.support.DefaultMultiLevelImage; +import com.bc.ceres.multilevel.support.DefaultMultiLevelModel; +import eu.esa.snap.cimr.grid.GlobalGrid; import eu.esa.snap.cimr.grid.GridBandDataSource; import org.esa.snap.core.datamodel.Band; import org.esa.snap.core.image.ResolutionLevel; +import java.awt.geom.AffineTransform; import java.awt.image.RenderedImage; public class CimrGridMultiLevelSource extends AbstractMultiLevelSource { + private static final int MLM_LEVEL_COUNT = 7; + private final Band targetBand; private final GridBandDataSource gridDataSource; @@ -30,9 +35,10 @@ protected RenderedImage createImage(int level) { return new CimrGridOpImage(targetBand, resLevel, gridDataSource); } - public static void attachToBand(Band band, - GridBandDataSource gridDataSource, - MultiLevelModel model) { + public static void attachToBand(Band band, GridBandDataSource gridDataSource, GlobalGrid grid) { + AffineTransform imageToModel = grid.getProjection().getAffineTransform(grid); + MultiLevelModel model = new DefaultMultiLevelModel(MLM_LEVEL_COUNT, imageToModel, grid.getWidth(), grid.getHeight()); + MultiLevelSource source = new CimrGridMultiLevelSource(model, band, gridDataSource); band.setSourceImage(new DefaultMultiLevelImage(source)); } diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilder.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilder.java index 48ae9e2e1..f23681004 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilder.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilder.java @@ -1,20 +1,13 @@ package eu.esa.snap.cimr.cimr; -import com.bc.ceres.multilevel.MultiLevelModel; -import com.bc.ceres.multilevel.support.DefaultMultiLevelModel; import eu.esa.snap.cimr.grid.GlobalGrid; import eu.esa.snap.cimr.grid.GridBandDataSource; +import eu.esa.snap.cimr.grid.LazyCrsGeoCoding; import org.esa.snap.core.datamodel.Band; import org.esa.snap.core.datamodel.Product; import org.esa.snap.core.datamodel.ProductData; import org.esa.snap.core.datamodel.GeoCoding; -import org.esa.snap.core.datamodel.CrsGeoCoding; -import org.opengis.referencing.FactoryException; -import org.opengis.referencing.crs.CoordinateReferenceSystem; -import org.opengis.referencing.operation.TransformException; -import java.awt.*; -import java.awt.geom.AffineTransform; import java.io.File; import java.util.Map; @@ -26,13 +19,10 @@ public class CimrSnapProductBuilder { public static Product buildProduct(String productName, String productType, CimrGridProduct cimrProduct, String path) throws Exception { GlobalGrid grid = cimrProduct.getGlobalGrid(); - int width = grid.getWidth(); - int height = grid.getHeight(); + Product product = new Product(productName, productType, grid.getWidth(), grid.getHeight()); - Product product = new Product(productName, productType, width, height); - - addGeoCoding(grid, width, height, product); - addBands(cimrProduct, width, height, product); + addGeoCoding(grid, product); + addBands(cimrProduct, product); product.setFileLocation(new File(path)); product.setAutoGrouping(AUTO_GROUPING); @@ -40,19 +30,14 @@ public static Product buildProduct(String productName, String productType, CimrG return product; } - private static void addGeoCoding(GlobalGrid grid, int width, int height, Product product) throws FactoryException, TransformException { - CoordinateReferenceSystem crs = grid.getProjection().getCrs(); - AffineTransform imageToModel = grid.getProjection().getAffineTransform(grid); - - GeoCoding geoCoding = new CrsGeoCoding(crs, new Rectangle(width, height), imageToModel); + private static void addGeoCoding(GlobalGrid grid, Product product) { + GeoCoding geoCoding = new LazyCrsGeoCoding(grid); product.setSceneGeoCoding(geoCoding); } - private static void addBands(CimrGridProduct cimrProduct, int width, int height, Product product) { - int levelCount = 7; - AffineTransform imageToModel = (AffineTransform) product.getSceneGeoCoding().getImageToMapTransform(); - MultiLevelModel mlModel = new DefaultMultiLevelModel(levelCount, imageToModel, width, height); + private static void addBands(CimrGridProduct cimrProduct, Product product) { + GlobalGrid grid = cimrProduct.getGlobalGrid(); for (Map.Entry e : cimrProduct.getBands().entrySet()) { CimrBandDescriptor desc = e.getKey(); @@ -65,7 +50,7 @@ private static void addBands(CimrGridProduct cimrProduct, int width, int height, band.setNoDataValueUsed(true); band.setSpectralWavelength(desc.getBand().getSpectralWaveLength()); - CimrGridMultiLevelSource.attachToBand(band, dataSource, mlModel); + CimrGridMultiLevelSource.attachToBand(band, dataSource, grid); } } } diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/LazyCrsGeoCoding.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/LazyCrsGeoCoding.java new file mode 100644 index 000000000..e267a4cbf --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/LazyCrsGeoCoding.java @@ -0,0 +1,109 @@ +package eu.esa.snap.cimr.grid; + +import org.esa.snap.core.datamodel.*; +import org.esa.snap.core.dataop.maptransf.Datum; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.MathTransform; + +import java.awt.*; +import java.awt.geom.AffineTransform; + + +public class LazyCrsGeoCoding implements GeoCoding { + + private final GlobalGrid grid; + private GeoCoding delegate; + + public LazyCrsGeoCoding(GlobalGrid grid) { + this.grid = grid; + } + + private GeoCoding getDelegate() { + if (delegate == null) { + synchronized (this) { + if (delegate == null) { + try { + final int width = grid.getWidth(); + final int height = grid.getHeight(); + CoordinateReferenceSystem crs = grid.getProjection().getCrs(); + AffineTransform imageToModel = grid.getProjection().getAffineTransform(grid); + delegate = new CrsGeoCoding(crs, new Rectangle(width, height), imageToModel); + } catch (Exception e) { + throw new RuntimeException("Failed to create CrsGeoCoding", e); + } + } + } + } + return delegate; + } + + @Override + public boolean isCrossingMeridianAt180() { + return getDelegate().isCrossingMeridianAt180(); + } + + @Override + public boolean canGetPixelPos() { + return true; + } + + @Override + public boolean canGetGeoPos() { + return true; + } + + @Override + public PixelPos getPixelPos(GeoPos geoPos, PixelPos pixelPos) { + return getDelegate().getPixelPos(geoPos, pixelPos); + } + + @Override + public GeoPos getGeoPos(PixelPos pixelPos, GeoPos geoPos) { + return getDelegate().getGeoPos(pixelPos, geoPos); + } + + @Override + public Datum getDatum() { + return getDelegate().getDatum(); + } + + @Override + public void dispose() { + getDelegate().dispose(); + } + + @Override + public CoordinateReferenceSystem getImageCRS() { + return getDelegate().getImageCRS(); + } + + @Override + public CoordinateReferenceSystem getMapCRS() { + return getDelegate().getMapCRS(); + } + + @Override + public CoordinateReferenceSystem getGeoCRS() { + return getDelegate().getGeoCRS(); + } + + @Override + public MathTransform getImageToMapTransform() { + return getDelegate().getImageToMapTransform(); + } + + @Override + public GeoCoding clone() { + return getDelegate().clone(); + } + + @Override + public boolean canClone() { + return getDelegate().canClone(); + } + + @Override + public boolean isGlobal() { + return true; + } +} diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSourceTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSourceTest.java index 04c24e955..c8f7bdfcb 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSourceTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSourceTest.java @@ -4,9 +4,10 @@ import com.bc.ceres.multilevel.MultiLevelModel; import com.bc.ceres.multilevel.support.DefaultMultiLevelImage; import com.bc.ceres.multilevel.support.DefaultMultiLevelModel; +import eu.esa.snap.cimr.grid.GlobalGrid; import eu.esa.snap.cimr.grid.GridBandDataSource; +import eu.esa.snap.cimr.grid.PlateCarreeProjection; import org.esa.snap.core.datamodel.Band; -import org.esa.snap.core.datamodel.Product; import org.esa.snap.core.datamodel.ProductData; import org.junit.Test; @@ -27,9 +28,7 @@ public class CimrGridMultiLevelSourceTest { public void testLevel0ImageMatchesGridValues() { int width = 2; int height = 2; - - Product product = new Product("T", "T", width, height); - Band band = product.addBand("test", ProductData.TYPE_FLOAT64); + Band band = new Band("test", ProductData.TYPE_FLOAT64, width, height); GridBandDataSource grid = new GridBandDataSource() { @Override @@ -61,11 +60,9 @@ public void setSample(int x, int y, double value) { public void testAttachToBand_setsSourceImageAndUsesGridValues() { int width = 2; int height = 2; + Band band = new Band("test", ProductData.TYPE_FLOAT64, width, height); - Product product = new Product("T", "T", width, height); - Band band = product.addBand("test", ProductData.TYPE_FLOAT64); - - GridBandDataSource grid = new GridBandDataSource() { + GridBandDataSource dataSource = new GridBandDataSource() { @Override public double getSample(int x, int y) { return x + 10 * y; @@ -77,9 +74,14 @@ public void setSample(int x, int y, double value) { } }; - MultiLevelModel model = new DefaultMultiLevelModel(1, new AffineTransform(), width, height); + PlateCarreeProjection projection = new PlateCarreeProjection( + width, height, + -180.0, 90.0, + 360.0 / width, 180.0 / height + ); + GlobalGrid globalGrid = new GlobalGrid(projection, width, height); - CimrGridMultiLevelSource.attachToBand(band, grid, model); + CimrGridMultiLevelSource.attachToBand(band, dataSource, globalGrid); assertNotNull(band.getSourceImage()); assertTrue(band.getSourceImage() instanceof DefaultMultiLevelImage); @@ -94,5 +96,11 @@ public void setSample(int x, int y, double value) { assertEquals(1.0, raster.getSampleDouble(1, 0, 0), doubleErr); assertEquals(10.0, raster.getSampleDouble(0, 1, 0), doubleErr); assertEquals(11.0, raster.getSampleDouble(1, 1, 0), doubleErr); + + RenderedImage level1 = mli.getImage(1); + assertEquals(1, level1.getWidth()); + assertEquals(1, level1.getHeight()); + raster = level1.getData(new Rectangle(0, 0, 1, 1)); + assertEquals(5.5, raster.getSampleDouble(0, 0, 0), doubleErr); } } \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyCrsGeoCodingTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyCrsGeoCodingTest.java new file mode 100644 index 000000000..36e847ee2 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyCrsGeoCodingTest.java @@ -0,0 +1,91 @@ +package eu.esa.snap.cimr.grid; + +import org.esa.snap.core.datamodel.GeoPos; +import org.esa.snap.core.datamodel.PixelPos; +import org.junit.Test; + +import java.lang.reflect.Field; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + + +public class LazyCrsGeoCodingTest { + + @Test + public void test_canGetFlagsAndIsGlobalDoNotInitDelegate() throws Exception { + GlobalGrid grid = mock(GlobalGrid.class); + + LazyCrsGeoCoding gc = new LazyCrsGeoCoding(grid); + + assertTrue(gc.canGetGeoPos()); + assertTrue(gc.canGetPixelPos()); + assertTrue(gc.isGlobal()); + + assertNull(getField(gc, "delegate")); + verifyNoInteractions(grid); + } + + @Test + public void test_delegateIsCreatedLazilyAndReusedForAllDelegatingMethods() throws Exception { + GlobalGrid grid = GlobalGridFactory.createGlobalPlateCarree(1.0); // ggf. Aufruf anpassen + LazyCrsGeoCoding gc = new LazyCrsGeoCoding(grid); + + assertNull(getField(gc, "delegate")); + + GeoPos gp1 = gc.getGeoPos(new PixelPos(0.5f, 0.5f), null); + + Object delegate1 = getField(gc, "delegate"); + assertNotNull(delegate1); + assertNotNull(gp1); + + GeoPos gp2 = gc.getGeoPos(new PixelPos(10.5f, 20.5f), null); + Object delegate2 = getField(gc, "delegate"); + assertSame(delegate1, delegate2); + assertNotNull(gp2); + + gc.isCrossingMeridianAt180(); + gc.getPixelPos(new GeoPos(0.0f, 0.0f), null); + assertNotNull(gc.getDatum()); + assertNotNull(gc.getImageCRS()); + assertNotNull(gc.getMapCRS()); + assertNotNull(gc.getGeoCRS()); + assertNotNull(gc.getImageToMapTransform()); + assertFalse(gc.canClone()); + + gc.dispose(); + } + + @Test + public void wrapsDelegateCreationFailuresInRuntimeException() { + GlobalGrid badGrid = mock(GlobalGrid.class); + when(badGrid.getWidth()).thenReturn(10); + when(badGrid.getHeight()).thenReturn(10); + when(badGrid.getProjection()).thenThrow(new RuntimeException("boom")); + + LazyCrsGeoCoding gc = new LazyCrsGeoCoding(badGrid); + + try { + gc.getGeoPos(new PixelPos(0.5f, 0.5f), null); + fail("Expected RuntimeException to be thrown"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("Failed to create CrsGeoCoding")); + assertNotNull(e.getCause()); + assertEquals("boom", e.getCause().getMessage()); + } + } + + @Test(expected = IllegalStateException.class) + public void cloneThrowsIllegalStateException() { + GlobalGrid grid = GlobalGridFactory.createGlobalPlateCarree(1.0); + LazyCrsGeoCoding gc = new LazyCrsGeoCoding(grid); + + gc.clone(); + } + + private static Object getField(Object target, String name) throws NoSuchFieldException, IllegalAccessException { + Field f = target.getClass().getDeclaredField(name); + f.setAccessible(true); + return f.get(target); + } +} \ No newline at end of file From aa994587e9cbbae8d797e9c117095bd971ff4845 Mon Sep 17 00:00:00 2001 From: Benjamin Lutz Date: Wed, 3 Dec 2025 17:17:11 +0100 Subject: [PATCH 05/11] added footprint logic --- .../esa/snap/cimr/CimrL1BProductReader.java | 12 ++ .../eu/esa/snap/cimr/CimrReaderContext.java | 60 ++++-- .../snap/cimr/cimr/CimrBandDescriptor.java | 8 +- .../esa/snap/cimr/cimr/CimrDescriptorSet.java | 26 ++- .../eu/esa/snap/cimr/cimr/CimrFootprint.java | 48 +++++ .../esa/snap/cimr/config/CimrBandEntry.java | 1 + .../snap/cimr/config/CimrConfigLoader.java | 1 + .../snap/cimr/grid/PlateCarreeProjection.java | 3 +- .../netcdf/NetcdfCimrFootprintFactory.java | 32 +++ .../esa/snap/cimr/config/cimr-l1b-config.json | 83 ++++++++ .../eu/esa/snap/cimr/config/test-config.json | 83 ++++++++ .../cimr/CimrReaderContextFootprintTest.java | 190 ++++++++++++++++++ .../esa/snap/cimr/CimrReaderContextTest.java | 14 +- .../snap/cimr/cimr/CimrDescriptorSetTest.java | 83 +++++++- .../esa/snap/cimr/cimr/CimrFootprintTest.java | 62 ++++++ .../snap/cimr/cimr/CimrGridProductTest.java | 10 +- .../cimr/cimr/CimrSnapProductBuilderTest.java | 8 +- .../cimr/config/CimrConfigLoaderTest.java | 10 +- .../cimr/grid/LazyGridBandDataSourceTest.java | 1 + .../netcdf/NetcdfCimrBandFactoryTest.java | 3 + .../NetcdfCimrFootprintFactoryTest.java | 110 ++++++++++ .../netcdf/NetcdfCimrGeometryFactoryTest.java | 24 +-- pom.xml | 1 + 23 files changed, 812 insertions(+), 61 deletions(-) create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFootprint.java create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrFootprintFactory.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextFootprintTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrFootprintTest.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrFootprintFactoryTest.java diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java index 96b208548..f13b3b8b7 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java @@ -20,6 +20,7 @@ import java.awt.image.RenderedImage; import java.io.File; import java.io.IOException; +import java.util.List; public class CimrL1BProductReader extends AbstractProductReader { @@ -74,6 +75,17 @@ public void close() throws IOException { super.close(); } + public List getFootprints(String name) { + CimrBandDescriptor desc = this.readerContext.getDescriptorSet().getMeasurementByName(name); + if (desc == null) { + desc = this.readerContext.getDescriptorSet().getTpVariableByName(name); + } + if (desc == null) { + return List.of(); + } + return this.readerContext.getOrCreateFootprints(desc); + } + private String getInputPath() { Object input = getInput(); diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrReaderContext.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrReaderContext.java index b43416d02..2482bf7a4 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrReaderContext.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrReaderContext.java @@ -2,14 +2,17 @@ import eu.esa.snap.cimr.cimr.CimrBandDescriptor; import eu.esa.snap.cimr.cimr.CimrDescriptorSet; +import eu.esa.snap.cimr.cimr.CimrFootprint; import eu.esa.snap.cimr.cimr.CimrGridBuilder; import eu.esa.snap.cimr.grid.*; +import eu.esa.snap.cimr.netcdf.NetcdfCimrFootprintFactory; import eu.esa.snap.cimr.netcdf.NetcdfCimrGeometryFactory; import eu.esa.snap.cimr.netcdf.NetcdfCimrBandFactory; import ucar.ma2.InvalidRangeException; import ucar.nc2.NetcdfFile; import java.io.IOException; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -22,8 +25,10 @@ public class CimrReaderContext { private final GeometryBandToGridMapper mapper; private final NetcdfCimrGeometryFactory geometryFactory; private final NetcdfCimrBandFactory bandFactory; + private final NetcdfCimrFootprintFactory footprintFactory; - private final Map bandCache = new ConcurrentHashMap<>(); + private final Map geometryBandCache = new ConcurrentHashMap<>(); + private final Map> footprintCache = new ConcurrentHashMap<>(); public CimrReaderContext(NetcdfFile ncFile, @@ -37,6 +42,7 @@ public CimrReaderContext(NetcdfFile ncFile, this.mapper = new GeometryBandToGridMapper(); this.geometryFactory = geomFactory; this.bandFactory = bandFactory; + this.footprintFactory = new NetcdfCimrFootprintFactory(); } @@ -49,16 +55,9 @@ public CimrDescriptorSet getDescriptorSet() { } public GridBandDataSource getOrCreateGridForVariable(CimrBandDescriptor varDesc, boolean useAverage) { - return this.bandCache.computeIfAbsent(varDesc, d -> { - try { - CimrGeometry geom = getOrCreateGeometry(d); - CimrGeometryBand geometryBand = this.bandFactory.createGeometryBand(d, geom); - CimrGridBuilder gridBuilder = new CimrGridBuilder(this.mapper); - return gridBuilder.build(geometryBand, this.globalGrid, useAverage); - } catch (IOException | InvalidRangeException e) { - throw new RuntimeException("Failed to build grid for variable " + d.getName(), e); - } - }); + CimrGeometryBand geometryBand = getOrCreateGeometryBand(varDesc); + CimrGridBuilder gridBuilder = new CimrGridBuilder(this.mapper); + return gridBuilder.build(geometryBand, this.globalGrid, useAverage); } public CimrGeometry getOrCreateGeometry(CimrBandDescriptor varDesc) { @@ -69,8 +68,45 @@ public CimrGeometry getOrCreateGeometry(CimrBandDescriptor varDesc) { } } + private CimrGeometryBand getOrCreateGeometryBand(CimrBandDescriptor varDesc) { + String key = getGeometryBandKey(varDesc); + return this.geometryBandCache.computeIfAbsent(key, k -> { + try { + CimrGeometry geom = getOrCreateGeometry(varDesc); + return this.bandFactory.createGeometryBand(varDesc, geom); + } catch (IOException | InvalidRangeException e) { + throw new RuntimeException("Failed to build geometry band for variable " + varDesc.getName(), e); + } + }); + } + + private String getGeometryBandKey(CimrBandDescriptor varDesc) { + return varDesc.getBand().name() + ":" + varDesc.getValueVarName() + ":" + varDesc.getFeedIndex(); + } + + public List getOrCreateFootprints(CimrBandDescriptor varDesc) { + String key = getFootprintKey(varDesc); + return this.footprintCache.computeIfAbsent(key, d -> { + CimrBandDescriptor minorAxisDesc = this.descriptorSet.getTpVariableByName(varDesc.getFootprintVars()[0]); + CimrBandDescriptor majorAxisDesc = this.descriptorSet.getTpVariableByName(varDesc.getFootprintVars()[1]); + CimrBandDescriptor angleDesc = this.descriptorSet.getTpVariableByName(varDesc.getFootprintVars()[2]); + + CimrGeometryBand geometryBand = getOrCreateGeometryBand(varDesc); + CimrGeometryBand minorAxisBand = getOrCreateGeometryBand(minorAxisDesc); + CimrGeometryBand majorAxisBand = getOrCreateGeometryBand(majorAxisDesc); + CimrGeometryBand angleBand = getOrCreateGeometryBand(angleDesc); + + return footprintFactory.createFootprints(geometryBand, minorAxisBand, majorAxisBand, angleBand); + }); + } + + private String getFootprintKey(CimrBandDescriptor varDesc) { + return varDesc.getBand().name() + ":" + varDesc.getFeedIndex(); + } + public void clearCache() { - this.bandCache.clear(); + this.geometryBandCache.clear(); + this.footprintCache.clear(); this.geometryFactory.clearCache(); } } diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrBandDescriptor.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrBandDescriptor.java index 7d0a1092e..0a26404a2 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrBandDescriptor.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrBandDescriptor.java @@ -7,6 +7,7 @@ public class CimrBandDescriptor { private final String valueVarName; private final CimrFrequencyBand band; private final String[] geometryNames; + private final String[] footprintVars; private final String groupPath; private final int feedIndex; private final CimrDescriptorKind kind; @@ -16,11 +17,12 @@ public class CimrBandDescriptor { private final String description; - public CimrBandDescriptor(String name, String valueVarName, CimrFrequencyBand band, String[] geometryNames, String groupPath, int feedIndex, CimrDescriptorKind kind, String[] dimensions, String dataType, String unit, String description) { + public CimrBandDescriptor(String name, String valueVarName, CimrFrequencyBand band, String[] geometryNames, String[] footprintVars, String groupPath, int feedIndex, CimrDescriptorKind kind, String[] dimensions, String dataType, String unit, String description) { this.name = name; this.valueVarName = valueVarName; this.band = band; this.geometryNames = geometryNames; + this.footprintVars = footprintVars; this.groupPath = groupPath; this.feedIndex = feedIndex; this.kind = kind; @@ -46,6 +48,10 @@ public String[] getGeometryNames() { return geometryNames; } + public String[] getFootprintVars() { + return footprintVars; + } + public String getGroupPath() { return groupPath; } diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrDescriptorSet.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrDescriptorSet.java index 05ce4e0ab..564a7fa62 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrDescriptorSet.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrDescriptorSet.java @@ -19,7 +19,25 @@ public CimrDescriptorSet(List measurements, } public CimrBandDescriptor getGeometryByName(String name) { - for (CimrBandDescriptor descriptor : geometries) { + for (CimrBandDescriptor descriptor : this.geometries) { + if (descriptor.getName().equals(name)) { + return descriptor; + } + } + return null; + } + + public CimrBandDescriptor getTpVariableByName(String name) { + for (CimrBandDescriptor descriptor : this.tiepointVariables) { + if (descriptor.getName().equals(name)) { + return descriptor; + } + } + return null; + } + + public CimrBandDescriptor getMeasurementByName(String name) { + for (CimrBandDescriptor descriptor : this.measurements) { if (descriptor.getName().equals(name)) { return descriptor; } @@ -28,14 +46,14 @@ public CimrBandDescriptor getGeometryByName(String name) { } public List getMeasurements() { - return measurements; + return this.measurements; } public List getGeometries() { - return geometries; + return this.geometries; } public List getTiepointVariables() { - return tiepointVariables; + return this.tiepointVariables; } } diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFootprint.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFootprint.java new file mode 100644 index 000000000..770a0c018 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFootprint.java @@ -0,0 +1,48 @@ +package eu.esa.snap.cimr.cimr; + +import org.esa.snap.core.datamodel.GeoPos; + +public class CimrFootprint { + + GeoPos geoPos; + double angle; // degree + double minor_axis; + double major_axis; + double value; + + public CimrFootprint(GeoPos geoPos, double angle, double minor_axis, double major_axis, double value) { + this.geoPos = geoPos; + this.angle = angle; + this.minor_axis = minor_axis; + this.major_axis = major_axis; + this.value = value; + } + + public GeoPos getGeoPos() { + return geoPos; + } + + public double getAngle() { + return angle; + } + + public double getValue() { + return value; + } + + public double getMinorAxisDegree() { + return metersToLatDeg(minor_axis); + } + + public double getMajorAxisDegree() { + return metersToLonDeg(major_axis, geoPos.getLat()); + } + + private double metersToLatDeg(double meters) { + return meters / 111320.0; + } + + private double metersToLonDeg(double meters, double latDeg) { + return meters / (111320.0 * Math.cos(Math.toRadians(latDeg))); + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrBandEntry.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrBandEntry.java index 6ee63478a..483b922fc 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrBandEntry.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrBandEntry.java @@ -6,6 +6,7 @@ public class CimrBandEntry { public String valueVarName; public String band; public String[] geometryNames; + public String[] footprintVars; public String groupPath; public int feedIndex; public String[] dimensions; diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrConfigLoader.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrConfigLoader.java index 9fc03ad01..aeed76970 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrConfigLoader.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/config/CimrConfigLoader.java @@ -46,6 +46,7 @@ private static CimrBandDescriptor toDescriptor(CimrBandEntry e, CimrDescriptorKi e.valueVarName, band, e.geometryNames, + e.footprintVars, e.groupPath, e.feedIndex, kind, diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/PlateCarreeProjection.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/PlateCarreeProjection.java index 99891f4a4..14d0b540b 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/PlateCarreeProjection.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/PlateCarreeProjection.java @@ -95,8 +95,7 @@ public AffineTransform getAffineTransform(GlobalGrid grid) { return new AffineTransform( deltaLon, 0.0, 0.0, -deltaLat, - lonMin + 0.5 * deltaLon, - latMax - 0.5 * deltaLat + lonMin, latMax ); } } diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrFootprintFactory.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrFootprintFactory.java new file mode 100644 index 000000000..ed48bddc7 --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrFootprintFactory.java @@ -0,0 +1,32 @@ +package eu.esa.snap.cimr.netcdf; + +import eu.esa.snap.cimr.cimr.CimrFootprint; +import eu.esa.snap.cimr.grid.CimrGeometryBand; +import org.esa.snap.core.datamodel.GeoPos; + +import java.util.ArrayList; +import java.util.List; + + +public class NetcdfCimrFootprintFactory { + + + public List createFootprints(CimrGeometryBand geometryBand, CimrGeometryBand minorAxisBand, CimrGeometryBand majorAxisBand, CimrGeometryBand angleBand) { + List footprints = new ArrayList<>(); + int scans = geometryBand.getScanCount(); + int samples = geometryBand.getSampleCount(); + + for (int scanIndex = 0; scanIndex < scans; scanIndex++) { + for (int sampleIndex = 0; sampleIndex < samples; sampleIndex++) { + final GeoPos pos = geometryBand.getGeoPos(scanIndex, sampleIndex); + final double angle = angleBand.getValue(scanIndex, sampleIndex); + final double minorAxis = minorAxisBand.getValue(scanIndex, sampleIndex); + final double majorAxis = majorAxisBand.getValue(scanIndex, sampleIndex); + final double value = geometryBand.getValue(scanIndex, sampleIndex); + footprints.add( new CimrFootprint(pos, angle, minorAxis, majorAxis, value)); + } + } + + return footprints; + } +} diff --git a/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/cimr-l1b-config.json b/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/cimr-l1b-config.json index ba11b4e02..d63b88f43 100644 --- a/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/cimr-l1b-config.json +++ b/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/cimr-l1b-config.json @@ -5,6 +5,7 @@ "valueVarName": "raw_bt_h", "band": "C_BAND", "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed1", "C_BAND_footprint_major_axis_feed1", "C_BAND_geometric_rot_angle_feed1"], "groupPath": "/Data/Measurement_Data/C_BAND/", "feedIndex": 0, "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], @@ -17,6 +18,7 @@ "valueVarName": "raw_bt_v", "band": "C_BAND", "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed1", "C_BAND_footprint_major_axis_feed1", "C_BAND_geometric_rot_angle_feed1"], "groupPath": "/Data/Measurement_Data/C_BAND/", "feedIndex": 0, "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], @@ -29,6 +31,7 @@ "valueVarName": "raw_bt_h", "band": "C_BAND", "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed2", "C_BAND_footprint_major_axis_feed2", "C_BAND_geometric_rot_angle_feed2"], "groupPath": "/Data/Measurement_Data/C_BAND/", "feedIndex": 1, "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], @@ -41,6 +44,7 @@ "valueVarName": "raw_bt_v", "band": "C_BAND", "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed2", "C_BAND_footprint_major_axis_feed2", "C_BAND_geometric_rot_angle_feed2"], "groupPath": "/Data/Measurement_Data/C_BAND/", "feedIndex": 1, "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], @@ -55,12 +59,91 @@ "valueVarName": "altitude", "band": "C_BAND", "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed1", "C_BAND_footprint_major_axis_feed1", "C_BAND_geometric_rot_angle_feed1"], "groupPath": "/Data/Navigation_Data/C_BAND/", "feedIndex": 0, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], "dataType": "double", "unit": "m", "description": "Altitude for intersection of the LOS with the earth surface for the C band Earth views" + }, + { + "name": "C_BAND_footprint_major_axis_feed1", + "valueVarName": "footprint_major_axis", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed1", "C_BAND_footprint_major_axis_feed1", "C_BAND_geometric_rot_angle_feed1"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double", + "unit": "m", + "description": "Elliptical footprint major semi-axis value" + }, + { + "name": "C_BAND_footprint_minor_axis_feed1", + "valueVarName": "footprint_minor_axis", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed1", "C_BAND_footprint_major_axis_feed1", "C_BAND_geometric_rot_angle_feed1"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double", + "unit": "m", + "description": "Elliptical footprint minor semi-axis value" + }, + { + "name": "C_BAND_geometric_rot_angle_feed1", + "valueVarName": "geometric_rot_angle", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed1", "C_BAND_footprint_major_axis_feed1", "C_BAND_geometric_rot_angle_feed1"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double", + "unit": "deg", + "description": "Geometric rotation angle value corresponding to the measured BT value" + }, + { + "name": "C_BAND_footprint_major_axis_feed2", + "valueVarName": "footprint_major_axis", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed2", "C_BAND_footprint_major_axis_feed2", "C_BAND_geometric_rot_angle_feed2"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double", + "unit": "m", + "description": "Elliptical footprint major semi-axis value" + }, + { + "name": "C_BAND_footprint_minor_axis_feed2", + "valueVarName": "footprint_minor_axis", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed2", "C_BAND_footprint_major_axis_feed2", "C_BAND_geometric_rot_angle_feed2"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double", + "unit": "m", + "description": "Elliptical footprint minor semi-axis value" + }, + { + "name": "C_BAND_geometric_rot_angle_feed2", + "valueVarName": "geometric_rot_angle", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed2", "C_BAND_footprint_major_axis_feed2", "C_BAND_geometric_rot_angle_feed2"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double", + "unit": "deg", + "description": "Geometric rotation angle value corresponding to the measured BT value" } ], "geometries": [ diff --git a/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/test-config.json b/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/test-config.json index ba11b4e02..d63b88f43 100644 --- a/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/test-config.json +++ b/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/test-config.json @@ -5,6 +5,7 @@ "valueVarName": "raw_bt_h", "band": "C_BAND", "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed1", "C_BAND_footprint_major_axis_feed1", "C_BAND_geometric_rot_angle_feed1"], "groupPath": "/Data/Measurement_Data/C_BAND/", "feedIndex": 0, "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], @@ -17,6 +18,7 @@ "valueVarName": "raw_bt_v", "band": "C_BAND", "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed1", "C_BAND_footprint_major_axis_feed1", "C_BAND_geometric_rot_angle_feed1"], "groupPath": "/Data/Measurement_Data/C_BAND/", "feedIndex": 0, "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], @@ -29,6 +31,7 @@ "valueVarName": "raw_bt_h", "band": "C_BAND", "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed2", "C_BAND_footprint_major_axis_feed2", "C_BAND_geometric_rot_angle_feed2"], "groupPath": "/Data/Measurement_Data/C_BAND/", "feedIndex": 1, "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], @@ -41,6 +44,7 @@ "valueVarName": "raw_bt_v", "band": "C_BAND", "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed2", "C_BAND_footprint_major_axis_feed2", "C_BAND_geometric_rot_angle_feed2"], "groupPath": "/Data/Measurement_Data/C_BAND/", "feedIndex": 1, "dimensions": ["n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"], @@ -55,12 +59,91 @@ "valueVarName": "altitude", "band": "C_BAND", "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed1", "C_BAND_footprint_major_axis_feed1", "C_BAND_geometric_rot_angle_feed1"], "groupPath": "/Data/Navigation_Data/C_BAND/", "feedIndex": 0, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], "dataType": "double", "unit": "m", "description": "Altitude for intersection of the LOS with the earth surface for the C band Earth views" + }, + { + "name": "C_BAND_footprint_major_axis_feed1", + "valueVarName": "footprint_major_axis", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed1", "C_BAND_footprint_major_axis_feed1", "C_BAND_geometric_rot_angle_feed1"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double", + "unit": "m", + "description": "Elliptical footprint major semi-axis value" + }, + { + "name": "C_BAND_footprint_minor_axis_feed1", + "valueVarName": "footprint_minor_axis", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed1", "C_BAND_footprint_major_axis_feed1", "C_BAND_geometric_rot_angle_feed1"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double", + "unit": "m", + "description": "Elliptical footprint minor semi-axis value" + }, + { + "name": "C_BAND_geometric_rot_angle_feed1", + "valueVarName": "geometric_rot_angle", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed1", "C_BAND_longitude_feed1"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed1", "C_BAND_footprint_major_axis_feed1", "C_BAND_geometric_rot_angle_feed1"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double", + "unit": "deg", + "description": "Geometric rotation angle value corresponding to the measured BT value" + }, + { + "name": "C_BAND_footprint_major_axis_feed2", + "valueVarName": "footprint_major_axis", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed2", "C_BAND_footprint_major_axis_feed2", "C_BAND_geometric_rot_angle_feed2"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double", + "unit": "m", + "description": "Elliptical footprint major semi-axis value" + }, + { + "name": "C_BAND_footprint_minor_axis_feed2", + "valueVarName": "footprint_minor_axis", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed2", "C_BAND_footprint_major_axis_feed2", "C_BAND_geometric_rot_angle_feed2"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double", + "unit": "m", + "description": "Elliptical footprint minor semi-axis value" + }, + { + "name": "C_BAND_geometric_rot_angle_feed2", + "valueVarName": "geometric_rot_angle", + "band": "C_BAND", + "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], + "footprintVars" : ["C_BAND_footprint_minor_axis_feed2", "C_BAND_footprint_major_axis_feed2", "C_BAND_geometric_rot_angle_feed2"], + "groupPath": "/Data/Measurement_Data/C_BAND/", + "feedIndex": 0, + "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], + "dataType": "double", + "unit": "deg", + "description": "Geometric rotation angle value corresponding to the measured BT value" } ], "geometries": [ diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextFootprintTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextFootprintTest.java new file mode 100644 index 000000000..2fc2f8b68 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextFootprintTest.java @@ -0,0 +1,190 @@ +package eu.esa.snap.cimr; + +import eu.esa.snap.cimr.cimr.CimrBandDescriptor; +import eu.esa.snap.cimr.cimr.CimrDescriptorSet; +import eu.esa.snap.cimr.cimr.CimrFootprint; +import eu.esa.snap.cimr.cimr.CimrFrequencyBand; +import eu.esa.snap.cimr.grid.CimrGeometry; +import eu.esa.snap.cimr.grid.CimrGeometryBand; +import eu.esa.snap.cimr.grid.GlobalGrid; +import eu.esa.snap.cimr.netcdf.NetcdfCimrBandFactory; +import eu.esa.snap.cimr.netcdf.NetcdfCimrFootprintFactory; +import eu.esa.snap.cimr.netcdf.NetcdfCimrGeometryFactory; +import org.esa.snap.core.datamodel.GeoPos; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import ucar.ma2.InvalidRangeException; +import ucar.nc2.NetcdfFile; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + + +@RunWith(MockitoJUnitRunner.class) +public class CimrReaderContextFootprintTest { + + @Mock + private NetcdfFile ncFile; + + @Mock + private CimrDescriptorSet descriptorSet; + + @Mock + private GlobalGrid globalGrid; + + @Mock + private NetcdfCimrGeometryFactory geometryFactory; + + @Mock + private NetcdfCimrBandFactory bandFactory; + + @Mock + private NetcdfCimrFootprintFactory footprintFactory; + + @Mock + private CimrBandDescriptor mainDesc; + + @Mock + private CimrBandDescriptor minorDesc; + + @Mock + private CimrBandDescriptor majorDesc; + + @Mock + private CimrBandDescriptor angleDesc; + + @Mock + private CimrGeometry mainGeom; + + @Mock + private CimrGeometry minorGeom; + + @Mock + private CimrGeometry majorGeom; + + @Mock + private CimrGeometry angleGeom; + + @Mock + private CimrGeometryBand mainGeomBand; + + @Mock + private CimrGeometryBand minorGeomBand; + + @Mock + private CimrGeometryBand majorGeomBand; + + @Mock + private CimrGeometryBand angleGeomBand; + + private CimrReaderContext context; + + @Before + public void setUp() throws Exception { + context = new CimrReaderContext(ncFile, descriptorSet, globalGrid, geometryFactory, bandFactory); + + Field ff = CimrReaderContext.class.getDeclaredField("footprintFactory"); + ff.setAccessible(true); + ff.set(context, footprintFactory); + + when(mainDesc.getFootprintVars()).thenReturn(new String[]{ + "FOOT_MINOR", + "FOOT_MAJOR", + "FOOT_ANGLE" + }); + when(mainDesc.getBand()).thenReturn(CimrFrequencyBand.C_BAND); + when(mainDesc.getFeedIndex()).thenReturn(0); + + when(descriptorSet.getTpVariableByName("FOOT_MINOR")).thenReturn(minorDesc); + when(descriptorSet.getTpVariableByName("FOOT_MAJOR")).thenReturn(majorDesc); + when(descriptorSet.getTpVariableByName("FOOT_ANGLE")).thenReturn(angleDesc); + + when(minorDesc.getBand()).thenReturn(CimrFrequencyBand.C_BAND); + when(minorDesc.getFeedIndex()).thenReturn(1); + when(minorDesc.getValueVarName()).thenReturn("FOOT_MINOR"); + + when(majorDesc.getBand()).thenReturn(CimrFrequencyBand.C_BAND); + when(majorDesc.getFeedIndex()).thenReturn(1); + when(majorDesc.getValueVarName()).thenReturn("FOOT_MAJOR"); + + when(angleDesc.getBand()).thenReturn(CimrFrequencyBand.C_BAND); + when(angleDesc.getFeedIndex()).thenReturn(1); + when(angleDesc.getValueVarName()).thenReturn("FOOT_ANGLE"); + + when(geometryFactory.getOrCreateGeometry(mainDesc)).thenReturn(mainGeom); + when(geometryFactory.getOrCreateGeometry(minorDesc)).thenReturn(minorGeom); + when(geometryFactory.getOrCreateGeometry(majorDesc)).thenReturn(majorGeom); + when(geometryFactory.getOrCreateGeometry(angleDesc)).thenReturn(angleGeom); + + when(bandFactory.createGeometryBand(eq(mainDesc), eq(mainGeom))).thenReturn(mainGeomBand); + when(bandFactory.createGeometryBand(eq(minorDesc), eq(minorGeom))).thenReturn(minorGeomBand); + when(bandFactory.createGeometryBand(eq(majorDesc), eq(majorGeom))).thenReturn(majorGeomBand); + when(bandFactory.createGeometryBand(eq(angleDesc), eq(angleGeom))).thenReturn(angleGeomBand); + } + + @Test + public void testGetOrCreateFootprints_createsFromDependenciesAndCaches() throws InvalidRangeException, IOException { + CimrFootprint dummyFp = new CimrFootprint(new GeoPos(10.f, 20.f), 45.0, 1000.0, 2000.0, 300.0); + List expectedList = Collections.singletonList(dummyFp); + + when(footprintFactory.createFootprints(mainGeomBand, minorGeomBand, majorGeomBand, angleGeomBand)) + .thenReturn(expectedList); + + List first = context.getOrCreateFootprints(mainDesc); + + assertSame(expectedList, first); + + verify(descriptorSet).getTpVariableByName("FOOT_MINOR"); + verify(descriptorSet).getTpVariableByName("FOOT_MAJOR"); + verify(descriptorSet).getTpVariableByName("FOOT_ANGLE"); + + verify(geometryFactory).getOrCreateGeometry(mainDesc); + verify(geometryFactory).getOrCreateGeometry(minorDesc); + verify(geometryFactory).getOrCreateGeometry(majorDesc); + verify(geometryFactory).getOrCreateGeometry(angleDesc); + + verify(bandFactory).createGeometryBand(mainDesc, mainGeom); + verify(bandFactory).createGeometryBand(minorDesc, minorGeom); + verify(bandFactory).createGeometryBand(majorDesc, majorGeom); + verify(bandFactory).createGeometryBand(angleDesc, angleGeom); + + verify(footprintFactory, times(1)) + .createFootprints(mainGeomBand, minorGeomBand, majorGeomBand, angleGeomBand); + + List second = context.getOrCreateFootprints(mainDesc); + + assertSame(first, second); + verify(footprintFactory, times(1)).createFootprints(mainGeomBand, minorGeomBand, majorGeomBand, angleGeomBand); + } + + @Test + public void testGetOrCreateFootprints_usesFootprintKeyBasedOnBandAndFeed() { + CimrBandDescriptor otherDesc = mock(CimrBandDescriptor.class); + + when(otherDesc.getBand()).thenReturn(CimrFrequencyBand.C_BAND); + when(otherDesc.getFeedIndex()).thenReturn(0); + + CimrFootprint fp = new CimrFootprint(new GeoPos(0.f, 0.f), 0.0, 500.0, 1000.0, 250.0); + List expected = Collections.singletonList(fp); + + when(footprintFactory.createFootprints( + mainGeomBand, minorGeomBand, majorGeomBand, angleGeomBand)) + .thenReturn(expected); + + List list1 = context.getOrCreateFootprints(mainDesc); + List list2 = context.getOrCreateFootprints(otherDesc); + + assertSame(list1, list2); + + verify(footprintFactory, times(1)).createFootprints(mainGeomBand, minorGeomBand, majorGeomBand, angleGeomBand); + } +} diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextTest.java index 155e0da64..9fbd4315b 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextTest.java @@ -74,7 +74,7 @@ public CimrGeometry getOrCreateGeometry(CimrBandDescriptor variableDesc) } @Test - public void testGetOrCreateGridForVariable_BuildsAndCachesOnce() { + public void testGetOrCreateGridForVariable() { GlobalGrid grid = createTestGrid(); CimrDescriptorSet descriptorSet = createEmptyDescriptorSet(); CimrBandDescriptor desc = createTestDescriptor(); @@ -112,7 +112,7 @@ public CimrGeometryBand createGeometryBand(CimrBandDescriptor desc, CimrGeometry GridBandDataSource grid1 = ctx.getOrCreateGridForVariable(desc, true); GridBandDataSource grid2 = ctx.getOrCreateGridForVariable(desc, false); - assertSame(grid1, grid2); + assertNotSame(grid1, grid2); assertEquals(1, geomCalls.get()); assertEquals(1, bandCalls.get()); @@ -154,7 +154,7 @@ public CimrGeometryBand createGeometryBand(CimrBandDescriptor desc, CimrGeometry ctx.getOrCreateGridForVariable(desc, true); fail("Expected RuntimeException"); } catch (RuntimeException e) { - assertTrue(e.getMessage().contains("Failed to build grid for variable testVar")); + assertTrue(e.getMessage().contains("Failed to build geometry band for variable testVar")); assertNotNull(e.getCause()); assertTrue(e.getCause() instanceof IOException); assertEquals("boom-band", e.getCause().getMessage()); @@ -215,7 +215,7 @@ public void testClearCache_clearsBandCacheAndGeometryCache() { CimrBandDescriptor varDesc = new CimrBandDescriptor( "testVar", "v", CimrFrequencyBand.C_BAND, - new String[]{"lat", "lon"}, + new String[]{"lat", "lon"}, new String[] {""}, "/Data", 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, "double", "", "" @@ -273,15 +273,14 @@ public CimrGeometryBand createGeometryBand(CimrBandDescriptor desc, CimrGeometry GridBandDataSource ds1 = ctx.getOrCreateGridForVariable(varDesc, true); GridBandDataSource ds2 = ctx.getOrCreateGridForVariable(varDesc, true); - assertSame(ds1, ds2); + assertNotSame(ds1, ds2); assertEquals(1, geomFactory.getCalls); assertEquals(1, bandFactory.calls); ctx.clearCache(); assertEquals(1, geomFactory.clearCalls); - GridBandDataSource ds3 = ctx.getOrCreateGridForVariable(varDesc, true); - assertNotSame(ds1, ds3); + ctx.getOrCreateGridForVariable(varDesc, true); assertEquals(2, geomFactory.getCalls); assertEquals(2, bandFactory.calls); } @@ -303,6 +302,7 @@ private CimrBandDescriptor createTestDescriptor() { "testVar", CimrFrequencyBand.C_BAND, new String[]{"lat", "lon"}, + new String[] {""}, "/dummy", 0, CimrDescriptorKind.VARIABLE, diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrDescriptorSetTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrDescriptorSetTest.java index ed3cae1e0..e58094e39 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrDescriptorSetTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrDescriptorSetTest.java @@ -14,8 +14,8 @@ public class CimrDescriptorSetTest { @Test public void getGeometryByName_returnsDescriptorWhenPresent() { - CimrBandDescriptor geom1 = descriptor("LAT"); - CimrBandDescriptor geom2 = descriptor("LON"); + CimrBandDescriptor geom1 = descriptor("LAT", CimrDescriptorKind.GEOMETRY); + CimrBandDescriptor geom2 = descriptor("LON", CimrDescriptorKind.GEOMETRY); List measurements = Collections.emptyList(); List geometries = Arrays.asList(geom1, geom2); @@ -30,7 +30,7 @@ public void getGeometryByName_returnsDescriptorWhenPresent() { @Test public void getGeometryByName_returnsNullWhenNameNotFound() { - CimrBandDescriptor geom1 = descriptor("LAT"); + CimrBandDescriptor geom1 = descriptor("LAT", CimrDescriptorKind.GEOMETRY); CimrDescriptorSet set = new CimrDescriptorSet( Collections.emptyList(), @@ -58,9 +58,9 @@ public void getGeometryByName_returnsNullWhenNoGeometries() { @Test public void getters_returnListsPassedToConstructor() { - List measurements = Collections.singletonList(descriptor("MEAS")); - List geometries = Collections.singletonList(descriptor("GEOM")); - List tiepoints = Collections.singletonList(descriptor("TP")); + List measurements = Collections.singletonList(descriptor("MEAS", CimrDescriptorKind.VARIABLE)); + List geometries = Collections.singletonList(descriptor("GEOM",CimrDescriptorKind.GEOMETRY)); + List tiepoints = Collections.singletonList(descriptor("TP",CimrDescriptorKind.TIEPOINT_VARIABLE)); CimrDescriptorSet set = new CimrDescriptorSet(measurements, geometries, tiepoints); @@ -71,8 +71,8 @@ public void getters_returnListsPassedToConstructor() { @Test public void getGeometryByName_returnsFirstMatchWhenMultipleWithSameName() { - CimrBandDescriptor geom1 = descriptor("LAT"); - CimrBandDescriptor geom2 = descriptor("LAT"); + CimrBandDescriptor geom1 = descriptor("LAT", CimrDescriptorKind.GEOMETRY); + CimrBandDescriptor geom2 = descriptor("LAT", CimrDescriptorKind.GEOMETRY); CimrDescriptorSet set = new CimrDescriptorSet( Collections.emptyList(), @@ -85,16 +85,79 @@ public void getGeometryByName_returnsFirstMatchWhenMultipleWithSameName() { assertSame("Expected first matching descriptor to be returned", geom1, result); } + @Test + public void getMeasurementByName_returnsDescriptorWhenPresent() { + CimrBandDescriptor var1 = descriptor("LAT", CimrDescriptorKind.VARIABLE); + CimrBandDescriptor var2 = descriptor("LON", CimrDescriptorKind.VARIABLE); + + List geometries = Collections.emptyList(); + List measurements = Arrays.asList(var1, var2); + List tiepoints = Collections.emptyList(); + + CimrDescriptorSet set = new CimrDescriptorSet(measurements, geometries, tiepoints); + + CimrBandDescriptor result = set.getMeasurementByName("LON"); + + assertSame("Expected to get the matching geometry descriptor", var2, result); + } + + @Test + public void getMeasurementByName_returnsNull() { + CimrBandDescriptor var1 = descriptor("LAT", CimrDescriptorKind.VARIABLE); + + List geometries = Collections.emptyList(); + List measurements = Arrays.asList(var1); + List tiepoints = Collections.emptyList(); + + CimrDescriptorSet set = new CimrDescriptorSet(measurements, geometries, tiepoints); + + CimrBandDescriptor result = set.getMeasurementByName("LON"); + + assertNull(result); + } + + @Test + public void getTPByName_returnsDescriptorWhenPresent() { + CimrBandDescriptor tp1 = descriptor("LAT", CimrDescriptorKind.TIEPOINT_VARIABLE); + CimrBandDescriptor tp2 = descriptor("LON", CimrDescriptorKind.TIEPOINT_VARIABLE); + + List geometries = Collections.emptyList(); + List tiepoints = Arrays.asList(tp1, tp2); + List measurements = Collections.emptyList(); + + CimrDescriptorSet set = new CimrDescriptorSet(measurements, geometries, tiepoints); + + CimrBandDescriptor result = set.getTpVariableByName("LON"); + + assertSame("Expected to get the matching geometry descriptor", tp2, result); + } + + @Test + public void getTPByName_returnsNull() { + CimrBandDescriptor tp1 = descriptor("LAT", CimrDescriptorKind.TIEPOINT_VARIABLE); + + List geometries = Collections.emptyList(); + List tiepoints = Arrays.asList(tp1); + List measurements = Collections.emptyList(); + + CimrDescriptorSet set = new CimrDescriptorSet(measurements, geometries, tiepoints); + + CimrBandDescriptor result = set.getTpVariableByName("LON"); + + assertNull(result); + } + - private static CimrBandDescriptor descriptor(String name) { + private static CimrBandDescriptor descriptor(String name, CimrDescriptorKind kind) { return new CimrBandDescriptor( name, "C_BAND_bt", CimrFrequencyBand.C_BAND, new String[] {"C_BAND_latitude", "C_BAND_longitude"}, + new String[] {""}, "/dummy/group", 0, - CimrDescriptorKind.GEOMETRY, + kind, new String[]{"n_scans", "n_samples_C_BAND"}, "double", "", diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrFootprintTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrFootprintTest.java new file mode 100644 index 000000000..ceea2d6d5 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrFootprintTest.java @@ -0,0 +1,62 @@ +package eu.esa.snap.cimr.cimr; + +import org.esa.snap.core.datamodel.GeoPos; +import org.junit.Test; + +import static org.junit.Assert.*; + + +public class CimrFootprintTest { + + private static final double doubleErr = 1e-9; + + @Test + public void testConstructorAndGetters() { + GeoPos geoPos = new GeoPos(24.5f, 280.1f); + double angle = 123.4; + double minor = 2500.0; + double major = 5000.0; + double value = 275.3; + + CimrFootprint fp = new CimrFootprint(geoPos, angle, minor, major, value); + + assertSame(geoPos, fp.getGeoPos()); + assertEquals(angle, fp.getAngle(), doubleErr); + assertEquals(value, fp.getValue(), doubleErr); + } + + @Test + public void testMinorAxisToDegree() { + GeoPos geoPos = new GeoPos(0.0f, 0.0f); + CimrFootprint fp = new CimrFootprint(geoPos, 0.0, 111_320.0, 0.0, 0.0); + + assertEquals(1.0, fp.getMinorAxisDegree(), doubleErr); + + CimrFootprint fpHalf = new CimrFootprint(geoPos, 0.0, 55_660.0, 0.0, 0.0); + assertEquals(0.5, fpHalf.getMinorAxisDegree(), doubleErr); + } + + @Test + public void testMajorAxisToDegreeAtEquator() { + GeoPos geoPos = new GeoPos(0.0f, 10.0f); + CimrFootprint fp = new CimrFootprint(geoPos, 0.0, 0.0, 111_320.0, 0.0); + + assertEquals(1.0, fp.getMajorAxisDegree(), doubleErr); + } + + @Test + public void testMajorAxisToDegreeAtMidLatitude() { + GeoPos geoPos = new GeoPos(60.0f, 10.0f); + CimrFootprint fp = new CimrFootprint(geoPos, 0.0, 0.0, 55_660.0, 0.0); + + assertEquals(1.0, fp.getMajorAxisDegree(), doubleErr); + } + + @Test + public void testMajorAxisToDegreeAtNegativeLatitude() { + GeoPos geoPos = new GeoPos(-60.0f, 10.0f); + CimrFootprint fp = new CimrFootprint(geoPos, 0.0, 0.0, 55_660.0, 0.0); + + assertEquals(1.0, fp.getMajorAxisDegree(), doubleErr); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridProductTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridProductTest.java index 908efcca5..e3561b3cb 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridProductTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridProductTest.java @@ -25,7 +25,7 @@ public void testAddAndGetBands() { CimrBandDescriptor band1 = new CimrBandDescriptor( "C_raw_bt_h_feed1", "raw_bt_h", CimrFrequencyBand.C_BAND, - new String[] {""}, + new String[] {""}, new String[] {""}, "/Data/Measurement_Data/C_BAND/", 1, CimrDescriptorKind.VARIABLE, new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, @@ -33,7 +33,7 @@ public void testAddAndGetBands() { ); CimrBandDescriptor band2 = new CimrBandDescriptor( "X_raw_bt_v_feed1", "raw_bt_h", CimrFrequencyBand.X_BAND, - new String[] {""}, + new String[] {""}, new String[] {""}, "/Data/Measurement_Data/C_BAND/", 2, CimrDescriptorKind.VARIABLE, new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, @@ -64,7 +64,7 @@ public void testBuildLazyCreatesBandsFromDescriptorSet() { CimrBandDescriptor tieDesc = new CimrBandDescriptor( "altitude", "altitude", CimrFrequencyBand.C_BAND, - new String[] {"lat", "lon"}, + new String[] {"lat", "lon"}, new String[] {""}, "/Geolocation/", 0, CimrDescriptorKind.TIEPOINT_VARIABLE, new String[] {"n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"}, @@ -72,7 +72,7 @@ public void testBuildLazyCreatesBandsFromDescriptorSet() { ); CimrBandDescriptor measDesc = new CimrBandDescriptor( "C_raw_bt_h_feed1", "raw_bt_h", CimrFrequencyBand.C_BAND, - new String[] {"lat", "lon"}, + new String[] {"lat", "lon"}, new String[] {""}, "/Data/Measurement_Data/C_BAND/", 0, CimrDescriptorKind.VARIABLE, new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, @@ -108,7 +108,7 @@ public void testGetBandsIsUnmodifiable() { CimrBandDescriptor band = new CimrBandDescriptor( "C_raw_bt_h_feed1", "raw_bt_h", CimrFrequencyBand.C_BAND, - new String[] {""}, + new String[] {""}, new String[] {""}, "/Data/Measurement_Data/C_BAND/", 1, CimrDescriptorKind.VARIABLE, new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilderTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilderTest.java index 6571ef1b2..31c3e30dc 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilderTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilderTest.java @@ -30,7 +30,7 @@ public void testBuildSnapProduct_createsBandsAndValues() throws Exception { CimrBandDescriptor bandDesc = new CimrBandDescriptor( "C_raw_bt_h_feed1", "raw_bt_h", CimrFrequencyBand.C_BAND, - new String[] {""}, + new String[] {""}, new String[] {""}, "/Data/Measurement_Data/C_BAND/", 1, CimrDescriptorKind.VARIABLE, new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, @@ -68,7 +68,7 @@ public void testBuildSnapProduct_setsMetadataAndAutoGrouping() throws Exception CimrBandDescriptor bandDesc = new CimrBandDescriptor( "C_raw_bt_h_feed1", "raw_bt_h", CimrFrequencyBand.C_BAND, - new String[] {""}, + new String[] {""}, new String[] {""}, "/Data/Measurement_Data/C_BAND/", 1, CimrDescriptorKind.VARIABLE, new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, @@ -110,7 +110,7 @@ public void testBuildSnapProduct_withMultipleBands_allPresentAndCorrect() throws CimrBandDescriptor band1 = new CimrBandDescriptor( "band1", "raw1", CimrFrequencyBand.C_BAND, - new String[] {""}, + new String[] {""}, new String[] {""}, "/Data/Measurement_Data/C_BAND/", 0, CimrDescriptorKind.VARIABLE, new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, @@ -118,7 +118,7 @@ public void testBuildSnapProduct_withMultipleBands_allPresentAndCorrect() throws ); CimrBandDescriptor band2 = new CimrBandDescriptor( "band2", "raw2", CimrFrequencyBand.X_BAND, - new String[] {""}, + new String[] {""}, new String[] {""}, "/Data/Measurement_Data/X_BAND/", 0, CimrDescriptorKind.VARIABLE, new String[] {"n_scans", "n_samples_X_BAND", "n_feeds_X_BAND"}, diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/config/CimrConfigLoaderTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/config/CimrConfigLoaderTest.java index dbbb616f7..354719686 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/config/CimrConfigLoaderTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/config/CimrConfigLoaderTest.java @@ -20,12 +20,12 @@ public void testLoadTestConfigJson() throws Exception { CimrDescriptorSet set = CimrConfigLoader.load("test-config.json"); assertNotNull(set); - List meas = set.getMeasurements(); - List tpVars = set.getTiepointVariables(); - List tpGeoms = set.getGeometries(); + List meas = set.getMeasurements(); + List tpVars = set.getTiepointVariables(); + List tpGeoms = set.getGeometries(); assertEquals(4, meas.size()); - assertEquals(1, tpVars.size()); + assertEquals(7, tpVars.size()); assertEquals(4, tpGeoms.size()); @@ -39,6 +39,7 @@ public void testLoadTestConfigJson() throws Exception { assertArrayEquals(new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, m0.getDimensions()); assertEquals("double", m0.getDataType()); assertArrayEquals(new String[]{"C_BAND_latitude_feed1", "C_BAND_longitude_feed1"}, m0.getGeometryNames()); + assertArrayEquals(new String[]{"C_BAND_footprint_minor_axis_feed1", "C_BAND_footprint_major_axis_feed1", "C_BAND_geometric_rot_angle_feed1"}, m0.getFootprintVars()); assertEquals("K", m0.getUnit()); assertEquals("Brightness temperature of the Earth, in H polarization, from raw counts (no RFI mitigation)", m0.getDescription()); @@ -53,6 +54,7 @@ public void testLoadTestConfigJson() throws Exception { assertArrayEquals(new String[]{"n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"}, tpVal0.getDimensions()); assertEquals("double", tpVal0.getDataType()); assertArrayEquals(new String[]{"C_BAND_latitude_feed1", "C_BAND_longitude_feed1"}, tpVal0.getGeometryNames()); + assertArrayEquals(new String[]{"C_BAND_footprint_minor_axis_feed1", "C_BAND_footprint_major_axis_feed1", "C_BAND_geometric_rot_angle_feed1"}, tpVal0.getFootprintVars()); assertEquals("m", tpVal0.getUnit()); assertEquals("Altitude for intersection of the LOS with the earth surface for the C band Earth views", tpVal0.getDescription()); diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyGridBandDataSourceTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyGridBandDataSourceTest.java index 9182314c2..0e937bf9e 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyGridBandDataSourceTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyGridBandDataSourceTest.java @@ -94,6 +94,7 @@ private static CimrBandDescriptor createDummyDescriptor(String name) { "valueVar", CimrFrequencyBand.C_BAND, new String[]{"lat", "lon"}, + new String[] {""}, "/dummy/path", 0, CimrDescriptorKind.VARIABLE, diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactoryTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactoryTest.java index 3bff54016..39f312536 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactoryTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactoryTest.java @@ -67,6 +67,7 @@ public void testCreateGeometryBand_NormalVariable() throws IOException, InvalidR "altitude", CimrFrequencyBand.C_BAND, new String[] {}, + new String[] {}, "/Data", 0, CimrDescriptorKind.VARIABLE, @@ -140,6 +141,7 @@ public void testCreateGeometryBand_TiepointVariableInterpolates() throws IOExcep "tie_var", CimrFrequencyBand.C_BAND, new String[]{}, + new String[] {}, "/Data", 0, CimrDescriptorKind.TIEPOINT_VARIABLE, @@ -204,6 +206,7 @@ public void testCreateGeometryBand_FailsForNon3DVariable() throws IOException, I "bad_var", CimrFrequencyBand.C_BAND, new String[]{}, + new String[] {}, "/Data", 0, CimrDescriptorKind.VARIABLE, diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrFootprintFactoryTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrFootprintFactoryTest.java new file mode 100644 index 000000000..bdd919c51 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrFootprintFactoryTest.java @@ -0,0 +1,110 @@ +package eu.esa.snap.cimr.netcdf; + +import eu.esa.snap.cimr.cimr.CimrFootprint; +import eu.esa.snap.cimr.grid.CimrGeometryBand; +import org.esa.snap.core.datamodel.GeoPos; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +public class NetcdfCimrFootprintFactoryTest { + + private static final double EPS = 1e-9; + + @Test + public void testCreateFootprints_populatesAllScanSampleCombinations() { + CimrGeometryBand geometryBand = mock(CimrGeometryBand.class); + CimrGeometryBand minorAxisBand = mock(CimrGeometryBand.class); + CimrGeometryBand majorAxisBand = mock(CimrGeometryBand.class); + CimrGeometryBand angleBand = mock(CimrGeometryBand.class); + + int scans = 2; + int samples = 3; + + when(geometryBand.getScanCount()).thenReturn(scans); + when(geometryBand.getSampleCount()).thenReturn(samples); + + when(geometryBand.getGeoPos(anyInt(), anyInt())).thenAnswer(inv -> { + int s = inv.getArgument(0); + int t = inv.getArgument(1); + return new GeoPos((float) (10 + s), (float) (20 + t)); + }); + + when(geometryBand.getValue(anyInt(), anyInt())).thenAnswer(inv -> { + int s = inv.getArgument(0); + int t = inv.getArgument(1); + return 100.0 + 10.0 * s + t; + }); + + when(angleBand.getValue(anyInt(), anyInt())).thenAnswer(inv -> { + int s = inv.getArgument(0); + int t = inv.getArgument(1); + return 1.0 * s + 0.1 * t; + }); + + when(minorAxisBand.getValue(anyInt(), anyInt())).thenAnswer(inv -> { + int s = inv.getArgument(0); + int t = inv.getArgument(1); + return 1000.0 + s + t; + }); + + when(majorAxisBand.getValue(anyInt(), anyInt())).thenAnswer(inv -> { + int s = inv.getArgument(0); + int t = inv.getArgument(1); + return 2000.0 + 2.0 * s + 3.0 * t; + }); + + NetcdfCimrFootprintFactory factory = new NetcdfCimrFootprintFactory(); + + + List footprints = factory.createFootprints( + geometryBand, minorAxisBand, majorAxisBand, angleBand); + + + assertEquals(scans * samples, footprints.size()); + + int idx = 0; + for (int s = 0; s < scans; s++) { + for (int t = 0; t < samples; t++) { + CimrFootprint fp = footprints.get(idx++); + + assertEquals(10.0 + s, fp.getGeoPos().getLat(), EPS); + assertEquals(20.0 + t, fp.getGeoPos().getLon(), EPS); + + double expectedAngle = 1.0 * s + 0.1 * t; + double expectedMinorAxis = 1000.0 + s + t; + double expectedValue = 100.0 + 10.0 * s + t; + + assertEquals(expectedAngle, fp.getAngle(), EPS); + assertEquals(expectedMinorAxis, fp.getMinorAxisDegree() * 111320.0, 1e-6 * 111320.0); + assertEquals(expectedValue, fp.getValue(), EPS); + } + } + } + + + @Test + public void testCreateFootprints_emptyGeometryBandReturnsEmptyList() { + CimrGeometryBand geometryBand = mock(CimrGeometryBand.class); + CimrGeometryBand minorAxisBand = mock(CimrGeometryBand.class); + CimrGeometryBand majorAxisBand = mock(CimrGeometryBand.class); + CimrGeometryBand angleBand = mock(CimrGeometryBand.class); + + when(geometryBand.getScanCount()).thenReturn(0); + when(geometryBand.getSampleCount()).thenReturn(0); + + NetcdfCimrFootprintFactory factory = new NetcdfCimrFootprintFactory(); + + List footprints = factory.createFootprints( + geometryBand, minorAxisBand, majorAxisBand, angleBand); + + assertNotNull(footprints); + assertTrue(footprints.isEmpty()); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactoryTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactoryTest.java index 608bf65e1..05ab68d51 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactoryTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactoryTest.java @@ -80,7 +80,7 @@ public void testGetOrCreateGeometry_BuildsGeometryAndCaches() throws IOException CimrBandDescriptor latDesc = new CimrBandDescriptor( "lat_c", "lat_c", CimrFrequencyBand.C_BAND, - new String[]{}, + new String[]{}, new String[] {""}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, @@ -88,7 +88,7 @@ public void testGetOrCreateGeometry_BuildsGeometryAndCaches() throws IOException ); CimrBandDescriptor lonDesc = new CimrBandDescriptor( "lon_c", "lon_c", CimrFrequencyBand.C_BAND, - new String[]{}, + new String[]{}, new String[] {""}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, @@ -97,7 +97,7 @@ public void testGetOrCreateGeometry_BuildsGeometryAndCaches() throws IOException CimrBandDescriptor varDesc = new CimrBandDescriptor( "altitude", "altitude", CimrFrequencyBand.C_BAND, - new String[]{"lat_c", "lon_c"}, + new String[]{"lat_c", "lon_c"}, new String[] {""}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, @@ -178,14 +178,14 @@ public void testClearCacheCreatesNewInstance() throws IOException, InvalidRangeE CimrBandDescriptor latDesc = new CimrBandDescriptor( "lat_c", "lat_c", CimrFrequencyBand.C_BAND, - new String[]{}, + new String[]{}, new String[] {}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, "double", "", "" ); CimrBandDescriptor lonDesc = new CimrBandDescriptor( "lon_c", "lon_c", CimrFrequencyBand.C_BAND, - new String[]{}, + new String[]{}, new String[] {}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, "double", "", "" @@ -193,7 +193,7 @@ public void testClearCacheCreatesNewInstance() throws IOException, InvalidRangeE CimrBandDescriptor varDesc = new CimrBandDescriptor( "altitude", "altitude", CimrFrequencyBand.C_BAND, - new String[]{"lat_c", "lon_c"}, + new String[]{"lat_c", "lon_c"}, new String[] {}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, "double", "", "" @@ -225,7 +225,7 @@ public void testGetOrCreateGeometry_FailsForInvalidGeometryNames() throws IOExce CimrBandDescriptor varDesc = new CimrBandDescriptor( "altitude", "altitude", CimrFrequencyBand.C_BAND, - null, + null, new String[] {}, "root", 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, "double", "", "" @@ -281,7 +281,7 @@ public void testGetOrCreateGeometry_FailsWhenGeometryDescriptorsMissing() throws CimrBandDescriptor latDesc = new CimrBandDescriptor( "lat_c", "lat_c", CimrFrequencyBand.C_BAND, - new String[]{}, + new String[]{}, new String[] {}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, "double", "", "" @@ -289,7 +289,7 @@ public void testGetOrCreateGeometry_FailsWhenGeometryDescriptorsMissing() throws CimrBandDescriptor varDesc = new CimrBandDescriptor( "altitude", "altitude", CimrFrequencyBand.C_BAND, - new String[]{"lat_c", "lon_c"}, + new String[]{"lat_c", "lon_c"}, new String[] {}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, "double", "", "" @@ -349,14 +349,14 @@ public void testGetOrCreateGeometry_UsesVariableFeedIndex() throws IOException, CimrBandDescriptor latDesc = new CimrBandDescriptor( "lat_c", "lat_c", CimrFrequencyBand.C_BAND, - new String[]{}, + new String[]{}, new String[] {}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, "double", "", "" ); CimrBandDescriptor lonDesc = new CimrBandDescriptor( "lon_c", "lon_c", CimrFrequencyBand.C_BAND, - new String[]{}, + new String[]{}, new String[] {}, rootPath, 0, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, "double", "", "" @@ -364,7 +364,7 @@ public void testGetOrCreateGeometry_UsesVariableFeedIndex() throws IOException, CimrBandDescriptor varDesc = new CimrBandDescriptor( "altitude", "altitude", CimrFrequencyBand.C_BAND, - new String[]{"lat_c", "lon_c"}, + new String[]{"lat_c", "lon_c"}, new String[] {}, rootPath, 1, CimrDescriptorKind.VARIABLE, new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, "double", "", "" diff --git a/pom.xml b/pom.xml index 65d530465..d54f47af5 100644 --- a/pom.xml +++ b/pom.xml @@ -85,6 +85,7 @@ cimr-reader + cimr-reader-ui jlinda rstb sar-cloud From 6baa00b789ac65d923b0646a1edaa0ab5240637b Mon Sep 17 00:00:00 2001 From: Benjamin Lutz Date: Thu, 4 Dec 2025 13:20:56 +0100 Subject: [PATCH 06/11] mapping geometry longitudes to symmetrical value range --- .../eu/esa/snap/cimr/grid/GlobalGridFactory.java | 2 +- .../snap/cimr/netcdf/NetcdfCimrGeometryFactory.java | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridFactory.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridFactory.java index bcb93efd3..cbebb4a3c 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridFactory.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridFactory.java @@ -7,7 +7,7 @@ public static GlobalGrid createGlobalPlateCarree(double cellSizeDeg) { int width = (int) Math.round(360.0 / cellSizeDeg); int height = (int) Math.round(180.0 / cellSizeDeg); - double lonMin = 0.0; + double lonMin = -180.0; double latMax = 90.0; PlateCarreeProjection proj = new PlateCarreeProjection( diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactory.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactory.java index 8276d6e94..bcfa0fa0d 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactory.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactory.java @@ -82,9 +82,9 @@ public CimrGeometry getOrCreateGeometry(CimrBandDescriptor variableDesc) throws for (int s = 0; s < nScans; s++) { for (int tp = 0; tp < nTiePoints; tp++) { idx.set(s, tp, 0); - float lat = (float) latData.getDouble(idx); - float lon = (float) lonData.getDouble(idx); - tiePoints[s][tp][0] = new GeoPos(lat, lon); + double lat = latData.getDouble(idx); + double lon = lonData.getDouble(idx); + tiePoints[s][tp][0] = new GeoPos(lat, ensureLongitude(lon)); } } @@ -107,4 +107,10 @@ private int getSampleCount(CimrBandDescriptor d) { public void clearCache() { this.cache.clear(); } + + private double ensureLongitude(double lon) { + while (lon > 180.0) lon -= 360.0; + while (lon <= -180.0) lon += 360.0; + return lon; + } } From 865d968397987b573a0c9ad4958efca6a943534b Mon Sep 17 00:00:00 2001 From: Benjamin Lutz Date: Thu, 4 Dec 2025 13:22:49 +0100 Subject: [PATCH 07/11] added footprint overlay and worldmap layer --- cimr-reader-ui/pom.xml | 80 +++++++++++++ .../snap/cimr/ui/CimrFootprintOverlay.java | 56 ++++++++++ .../ui/CimrSceneViewSelectionService.java | 57 ++++++++++ .../eu/esa/snap/cimr/ui/CimrUIManager.java | 105 ++++++++++++++++++ cimr-reader-ui/src/main/nbm/manifest.mf | 7 ++ .../cimr/ui/CimrFootprintOverlayTest.java | 45 ++++++++ .../esa/snap/cimr/ui/CimrUIManagerTest.java | 51 +++++++++ 7 files changed, 401 insertions(+) create mode 100644 cimr-reader-ui/pom.xml create mode 100644 cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrFootprintOverlay.java create mode 100644 cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrSceneViewSelectionService.java create mode 100644 cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrUIManager.java create mode 100644 cimr-reader-ui/src/main/nbm/manifest.mf create mode 100644 cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrFootprintOverlayTest.java create mode 100644 cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrUIManagerTest.java diff --git a/cimr-reader-ui/pom.xml b/cimr-reader-ui/pom.xml new file mode 100644 index 000000000..41a3d07f4 --- /dev/null +++ b/cimr-reader-ui/pom.xml @@ -0,0 +1,80 @@ + + 4.0.0 + + eu.esa.microwavetbx + microwave-toolbox + 14.0.0-SNAPSHOT + + + eu.esa.snap.cimr.ui + cimr-reader-ui + CIMR Reader UI + + nbm + + + + org.esa.snap + snap-core + + + org.esa.snap + ceres-glayer + + + org.esa.snap + snap-ui + + + org.esa.snap + ceres-binding + + + org.esa.snap + snap-rcp + + + eu.esa.snap.cimr + cimr-reader + ${project.version} + + + eu.esa.snap.netbeans + snap-gui-lib + 1.2.0 + + + org.mockito + mockito-core + test + + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + + + eu.esa.snap.cimr.ui.* + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + + diff --git a/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrFootprintOverlay.java b/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrFootprintOverlay.java new file mode 100644 index 000000000..b458c977e --- /dev/null +++ b/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrFootprintOverlay.java @@ -0,0 +1,56 @@ +package eu.esa.snap.cimr.ui; + +import com.bc.ceres.glayer.swing.LayerCanvas; +import com.bc.ceres.grender.Rendering; +import com.bc.ceres.grender.Viewport; +import eu.esa.snap.cimr.cimr.CimrFootprint; + +import java.awt.*; +import java.awt.geom.*; +import java.util.List; + + +public class CimrFootprintOverlay implements LayerCanvas.Overlay { + + public static final CimrFootprintOverlay INSTANCE = new CimrFootprintOverlay(); + + private List footprints; + + private CimrFootprintOverlay() {} + + public void setFootprints(List footprints) { + this.footprints = footprints; + } + + @Override + public void paintOverlay(LayerCanvas canvas, Rendering rendering) { + Graphics2D g = rendering.getGraphics(); + + Color oldColor = g.getColor(); + java.awt.Stroke oldStroke = g.getStroke(); + + Viewport vp = canvas.getViewport(); + AffineTransform m2vBase = vp.getModelToViewTransform(); + + for (CimrFootprint fp : footprints) { + double cx = fp.getGeoPos().getLon(); + double cy = fp.getGeoPos().getLat(); + double rx = fp.getMajorAxisDegree(); + double ry = fp.getMinorAxisDegree(); + + Ellipse2D modelEllipse = new Ellipse2D.Double(cx - rx, cy - ry, 2 * rx, 2 * ry); + double angleRad = Math.toRadians(fp.getAngle()); + + AffineTransform rotModel = AffineTransform.getRotateInstance(angleRad, cx, cy); + Shape rotatedModelShape = rotModel.createTransformedShape(modelEllipse); + Shape viewEllipse = m2vBase.createTransformedShape(rotatedModelShape); + + g.setColor(new Color(255, 255, 255, 255)); + g.fill(viewEllipse); + } + + g.setStroke(oldStroke); + g.setColor(oldColor); + } + +} diff --git a/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrSceneViewSelectionService.java b/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrSceneViewSelectionService.java new file mode 100644 index 000000000..c2acf1078 --- /dev/null +++ b/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrSceneViewSelectionService.java @@ -0,0 +1,57 @@ +package eu.esa.snap.cimr.ui; + +import org.esa.snap.core.datamodel.Product; +import org.esa.snap.rcp.windows.ToolTopComponent; +import org.esa.snap.ui.product.ProductSceneView; +import org.jspecify.annotations.NonNull; + +import java.util.ArrayList; +import java.util.List; + + +public class CimrSceneViewSelectionService extends ToolTopComponent { + + private final List selectionListeners = new ArrayList<>(); + private ProductSceneView selectedSceneView; + + + @Override + protected void productSceneViewSelected(@NonNull ProductSceneView view) { + setSelectedSceneView(view); + } + + @Override + protected void productSceneViewDeselected(@NonNull ProductSceneView view) { + setSelectedSceneView(null); + } + + private void setSelectedSceneView(ProductSceneView newView) { + ProductSceneView oldView = selectedSceneView; + if (oldView == newView) { + return; + } + if (newView != null) { + Product p = newView.getProduct(); + if (p == null) { + return; + } + } + selectedSceneView = newView; + fireSelectionChange(oldView, newView); + } + + public synchronized void addSceneViewSelectionListener(SelectionListener l) { + selectionListeners.add(l); + } + + private void fireSelectionChange(ProductSceneView oldView, ProductSceneView newView) { + for (SelectionListener listener : new ArrayList<>(selectionListeners)) { + listener.handleSceneViewSelectionChanged(oldView, newView); + } + } + + + public interface SelectionListener { + void handleSceneViewSelectionChanged(ProductSceneView oldView, ProductSceneView newView); + } +} diff --git a/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrUIManager.java b/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrUIManager.java new file mode 100644 index 000000000..da7b9d14e --- /dev/null +++ b/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrUIManager.java @@ -0,0 +1,105 @@ +package eu.esa.snap.cimr.ui; + +import com.bc.ceres.binding.PropertySet; +import com.bc.ceres.glayer.Layer; +import com.bc.ceres.glayer.LayerType; +import com.bc.ceres.glayer.LayerTypeRegistry; +import com.bc.ceres.glayer.support.ImageLayer; +import com.bc.ceres.glayer.support.LayerUtils; +import eu.esa.snap.cimr.CimrL1BProductReader; +import eu.esa.snap.cimr.cimr.CimrFootprint; +import org.esa.snap.core.dataio.ProductReader; +import org.esa.snap.core.datamodel.RasterDataNode; +import org.esa.snap.core.layer.WorldMapLayerType; +import org.esa.snap.rcp.SnapApp; +import org.esa.snap.ui.product.ProductSceneView; +import org.openide.modules.OnStart; +import org.openide.windows.OnShowing; + +import java.util.List; +import java.util.logging.Logger; + + +public class CimrUIManager { + + private static final String WORLDMAP_TYPE_PROPERTY_NAME = "worldmap.type"; + private static final String BLUE_MARBLE_LAYER_TYPE = "BlueMarbleLayerType"; + + private static final Logger LOG = Logger.getLogger(CimrUIManager.class.getName()); + private static volatile CimrSceneViewSelectionService sceneViewSelectionService; + + @OnStart + public static class StartOp implements Runnable { + @Override + public void run() { + LOG.info("Starting CIMR UI"); + sceneViewSelectionService = new CimrSceneViewSelectionService(); + } + } + + @OnShowing + public static class ShowingOp implements Runnable { + @Override + public void run() { + LOG.info("CIMR UI showing – installing footprint overlay listener"); + sceneViewSelectionService.addSceneViewSelectionListener(CimrUIManager::handleSceneViewChange); + } + } + + private static void handleSceneViewChange(ProductSceneView oldView, ProductSceneView newView) { + if (oldView != null) { + oldView.getLayerCanvas().removeOverlay(CimrFootprintOverlay.INSTANCE); + } + if (newView != null) { + // add worldmap layer + Layer worldMap = findWorldMapLayer(newView); + if (worldMap == null) { + worldMap = createWorldMapLayer(); + final Layer rootLayer = newView.getRootLayer(); + rootLayer.getChildren().add(worldMap); + } + worldMap.setVisible(true); + + // add footprints + CimrL1BProductReader cimrReader = getCimrReader(newView); + if (cimrReader != null) { + String band = newView.getRaster().getName(); + List fps = cimrReader.getFootprints(band); + if (!fps.isEmpty()) { + CimrFootprintOverlay.INSTANCE.setFootprints(fps); + newView.getLayerCanvas().addOverlay(CimrFootprintOverlay.INSTANCE); + } + } + } + } + + // TODO BL package private for testing, 12/25 + static CimrL1BProductReader getCimrReader(ProductSceneView view) { + RasterDataNode raster = view.getRaster(); + if (raster == null) { + return null; + } + ProductReader reader = raster.getProductReader(); + if (reader instanceof CimrL1BProductReader) { + return (CimrL1BProductReader) reader; + } + return null; + } + + private static Layer findWorldMapLayer(ProductSceneView view) { + return LayerUtils.getChildLayer(view.getRootLayer(), LayerUtils.SearchMode.DEEP, + layer -> layer.getLayerType() instanceof WorldMapLayerType); + } + + private static Layer createWorldMapLayer() { + final LayerType layerType = getWorldMapLayerType(); + final PropertySet template = layerType.createLayerConfig(null); + template.setValue(ImageLayer.PROPERTY_NAME_PIXEL_BORDER_SHOWN, false); + return layerType.createLayer(null, template); + } + + private static LayerType getWorldMapLayerType() { + String layerTypeClassName = SnapApp.getDefault().getPreferences().get(WORLDMAP_TYPE_PROPERTY_NAME, BLUE_MARBLE_LAYER_TYPE); + return LayerTypeRegistry.getLayerType(layerTypeClassName); + } +} diff --git a/cimr-reader-ui/src/main/nbm/manifest.mf b/cimr-reader-ui/src/main/nbm/manifest.mf new file mode 100644 index 000000000..ac61e99d3 --- /dev/null +++ b/cimr-reader-ui/src/main/nbm/manifest.mf @@ -0,0 +1,7 @@ +Manifest-Version: 1.0 +OpenIDE-Module-Specification-Version: ${microwavetbx.nbmSpecVersion} +OpenIDE-Module-Implementation-Version: ${microwavetbx.nbmImplVersion} +AutoUpdate-Show-In-Client: false +AutoUpdate-Essential-Module: false +OpenIDE-Module-Java-Dependencies: Java > 11 +OpenIDE-Module-Display-Category: SNAP Toolboxes diff --git a/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrFootprintOverlayTest.java b/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrFootprintOverlayTest.java new file mode 100644 index 000000000..85bb4e6d5 --- /dev/null +++ b/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrFootprintOverlayTest.java @@ -0,0 +1,45 @@ +package eu.esa.snap.cimr.ui; + +import com.bc.ceres.glayer.swing.LayerCanvas; +import com.bc.ceres.grender.Rendering; +import com.bc.ceres.grender.Viewport; +import eu.esa.snap.cimr.cimr.CimrFootprint; +import org.esa.snap.core.datamodel.GeoPos; +import org.junit.Test; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.util.Collections; + +import static org.mockito.Mockito.*; + + +public class CimrFootprintOverlayTest { + + + @Test + public void testPaintOverlay_doesNotThrow() { + CimrFootprintOverlay overlay = CimrFootprintOverlay.INSTANCE; + + CimrFootprint fp = new CimrFootprint( + new GeoPos(10f, 20f), 30.0, 1000.0, 2000.0, 300.0 + ); + overlay.setFootprints(Collections.singletonList(fp)); + + BufferedImage img = new BufferedImage(200, 200, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = img.createGraphics(); + + LayerCanvas canvas = mock(LayerCanvas.class); + Rendering rendering = mock(Rendering.class); + Viewport vp = mock(Viewport.class); + + when(canvas.getViewport()).thenReturn(vp); + when(vp.getModelToViewTransform()).thenReturn(new AffineTransform()); + when(rendering.getGraphics()).thenReturn(g2d); + + // should not throw exception + overlay.paintOverlay(canvas, rendering); + } + +} \ No newline at end of file diff --git a/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrUIManagerTest.java b/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrUIManagerTest.java new file mode 100644 index 000000000..3ddb3fb60 --- /dev/null +++ b/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrUIManagerTest.java @@ -0,0 +1,51 @@ +package eu.esa.snap.cimr.ui; + +import eu.esa.snap.cimr.CimrL1BProductReader; +import org.esa.snap.core.dataio.dimap.DimapProductReader; +import org.esa.snap.core.datamodel.RasterDataNode; +import org.esa.snap.ui.product.ProductSceneView; +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + + +public class CimrUIManagerTest { + + + @Test + public void testGetCimrReader_returnsReaderOnlyForCimr() { + ProductSceneView view = mock(ProductSceneView.class); + RasterDataNode raster = mock(RasterDataNode.class); + CimrL1BProductReader cimrReader = mock(CimrL1BProductReader.class); + when(view.getRaster()).thenReturn(raster); + when(raster.getProductReader()).thenReturn(cimrReader); + + CimrL1BProductReader result = CimrUIManager.getCimrReader(view); + + assertSame(cimrReader, result); + } + + @Test + public void testGetCimrReader_returnsNullNoRaster() { + ProductSceneView view = mock(ProductSceneView.class); + when(view.getRaster()).thenReturn(null); + + CimrL1BProductReader result = CimrUIManager.getCimrReader(view); + + assertNull(result); + } + + @Test + public void testGetCimrReader_returnsNullNotCimrReader() { + ProductSceneView view = mock(ProductSceneView.class); + RasterDataNode raster = mock(RasterDataNode.class); + DimapProductReader reader = mock(DimapProductReader.class); + when(view.getRaster()).thenReturn(null); + when(raster.getProductReader()).thenReturn(reader); + + CimrL1BProductReader result = CimrUIManager.getCimrReader(view); + + assertNull(result); + } +} \ No newline at end of file From af542744457ac684b616bee81d97418744efcaff Mon Sep 17 00:00:00 2001 From: Benjamin Lutz Date: Thu, 4 Dec 2025 15:43:25 +0100 Subject: [PATCH 08/11] make ellipses overlay represent color palette --- .../snap/cimr/ui/CimrFootprintOverlay.java | 42 ++++++++++++++++++- .../eu/esa/snap/cimr/ui/CimrUIManager.java | 4 +- .../cimr/ui/CimrFootprintOverlayTest.java | 4 ++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrFootprintOverlay.java b/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrFootprintOverlay.java index b458c977e..0a42313d1 100644 --- a/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrFootprintOverlay.java +++ b/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrFootprintOverlay.java @@ -4,6 +4,10 @@ import com.bc.ceres.grender.Rendering; import com.bc.ceres.grender.Viewport; import eu.esa.snap.cimr.cimr.CimrFootprint; +import org.esa.snap.core.datamodel.ColorPaletteDef; +import org.esa.snap.core.datamodel.ImageInfo; +import org.esa.snap.core.datamodel.RasterDataNode; +import org.esa.snap.core.image.ImageManager; import java.awt.*; import java.awt.geom.*; @@ -15,6 +19,7 @@ public class CimrFootprintOverlay implements LayerCanvas.Overlay { public static final CimrFootprintOverlay INSTANCE = new CimrFootprintOverlay(); private List footprints; + private RasterDataNode raster; private CimrFootprintOverlay() {} @@ -22,8 +27,16 @@ public void setFootprints(List footprints) { this.footprints = footprints; } + public void setRaster(RasterDataNode raster) { + this.raster = raster; + } + @Override public void paintOverlay(LayerCanvas canvas, Rendering rendering) { + if (footprints == null || footprints.isEmpty()) { + return; + } + Graphics2D g = rendering.getGraphics(); Color oldColor = g.getColor(); @@ -32,6 +45,16 @@ public void paintOverlay(LayerCanvas canvas, Rendering rendering) { Viewport vp = canvas.getViewport(); AffineTransform m2vBase = vp.getModelToViewTransform(); + ImageInfo imageInfo = raster.getImageInfo(); + Color baseColor = Color.WHITE; + Color[] fullPalette = null; + ColorPaletteDef cpd = null; + + if (imageInfo != null) { + cpd = imageInfo.getColorPaletteDef(); + fullPalette = ImageManager.createColorPalette(imageInfo); + } + for (CimrFootprint fp : footprints) { double cx = fp.getGeoPos().getLon(); double cy = fp.getGeoPos().getLat(); @@ -45,7 +68,11 @@ public void paintOverlay(LayerCanvas canvas, Rendering rendering) { Shape rotatedModelShape = rotModel.createTransformedShape(modelEllipse); Shape viewEllipse = m2vBase.createTransformedShape(rotatedModelShape); - g.setColor(new Color(255, 255, 255, 255)); + if (imageInfo != null && cpd != null) { + baseColor = getColorForValue(cpd, fullPalette, fp.getValue()); + } + + g.setColor(baseColor); g.fill(viewEllipse); } @@ -53,4 +80,17 @@ public void paintOverlay(LayerCanvas canvas, Rendering rendering) { g.setColor(oldColor); } + private Color getColorForValue(ColorPaletteDef cpd, Color[] fullPalette, double value) { + int numColors = cpd.getNumColors(); + double min = cpd.getMinDisplaySample(); + double max = cpd.getMaxDisplaySample(); + + double v = Math.max(min, Math.min(max, value)); + double f = (v - min) / (max - min); + + int idx = (int) Math.round(f * (numColors - 1)); + idx = Math.max(0, Math.min(idx, numColors)); + + return fullPalette[idx]; + } } diff --git a/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrUIManager.java b/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrUIManager.java index da7b9d14e..01843f3c6 100644 --- a/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrUIManager.java +++ b/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrUIManager.java @@ -63,10 +63,12 @@ private static void handleSceneViewChange(ProductSceneView oldView, ProductScene // add footprints CimrL1BProductReader cimrReader = getCimrReader(newView); if (cimrReader != null) { - String band = newView.getRaster().getName(); + RasterDataNode raster = newView.getRaster(); + String band = raster.getName(); List fps = cimrReader.getFootprints(band); if (!fps.isEmpty()) { CimrFootprintOverlay.INSTANCE.setFootprints(fps); + CimrFootprintOverlay.INSTANCE.setRaster(raster); newView.getLayerCanvas().addOverlay(CimrFootprintOverlay.INSTANCE); } } diff --git a/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrFootprintOverlayTest.java b/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrFootprintOverlayTest.java index 85bb4e6d5..d7a127afe 100644 --- a/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrFootprintOverlayTest.java +++ b/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrFootprintOverlayTest.java @@ -5,6 +5,7 @@ import com.bc.ceres.grender.Viewport; import eu.esa.snap.cimr.cimr.CimrFootprint; import org.esa.snap.core.datamodel.GeoPos; +import org.esa.snap.core.datamodel.RasterDataNode; import org.junit.Test; import java.awt.*; @@ -33,10 +34,13 @@ public void testPaintOverlay_doesNotThrow() { LayerCanvas canvas = mock(LayerCanvas.class); Rendering rendering = mock(Rendering.class); Viewport vp = mock(Viewport.class); + RasterDataNode raster = mock(RasterDataNode.class); + CimrFootprintOverlay.INSTANCE.setRaster(raster); when(canvas.getViewport()).thenReturn(vp); when(vp.getModelToViewTransform()).thenReturn(new AffineTransform()); when(rendering.getGraphics()).thenReturn(g2d); + when(raster.getImageInfo()).thenReturn(null); // should not throw exception overlay.paintOverlay(canvas, rendering); From c8167b1d080c1287360e9a9e4f8ab367d2d4bd6f Mon Sep 17 00:00:00 2001 From: Benjamin Lutz Date: Fri, 5 Dec 2025 15:38:15 +0100 Subject: [PATCH 09/11] read bounding box for grid definition --- .../esa/snap/cimr/CimrL1BProductReader.java | 16 ++-- .../eu/esa/snap/cimr/CimrReaderContext.java | 12 +-- .../esa/snap/cimr/cimr/CimrGridBuilder.java | 8 +- .../cimr/cimr/CimrGridMultiLevelSource.java | 4 +- .../esa/snap/cimr/cimr/CimrGridProduct.java | 16 ++-- .../cimr/cimr/CimrSnapProductBuilder.java | 8 +- .../esa/snap/cimr/grid/CimrBoundingBox.java | 78 ++++++++++++++++++ .../grid/{GlobalGrid.java => CimrGrid.java} | 8 +- ...ource.java => CimrGridBandDataSource.java} | 8 +- .../esa/snap/cimr/grid/CimrGridFactory.java | 38 +++++++++ .../cimr/grid/GeometryBandToGridMapper.java | 4 +- .../esa/snap/cimr/grid/GlobalGridFactory.java | 18 ---- .../eu/esa/snap/cimr/grid/GridProjection.java | 2 +- .../esa/snap/cimr/grid/LazyCrsGeoCoding.java | 4 +- .../snap/cimr/grid/PlateCarreeProjection.java | 2 +- .../cimr/netcdf/NetcdfCimrBandFactory.java | 1 + .../netcdf/NetcdfCimrGeometryFactory.java | 6 ++ .../esa/snap/cimr/config/cimr-l1b-config.json | 6 +- .../eu/esa/snap/cimr/config/test-config.json | 6 +- .../cimr/CimrReaderContextFootprintTest.java | 6 +- .../esa/snap/cimr/CimrReaderContextTest.java | 16 ++-- .../snap/cimr/cimr/CimrGridBuilderTest.java | 14 ++-- .../cimr/CimrGridMultiLevelSourceTest.java | 6 +- .../snap/cimr/cimr/CimrGridProductTest.java | 12 +-- .../cimr/cimr/CimrSnapProductBuilderTest.java | 28 +++---- .../snap/cimr/grid/CimrBoundingBoxTest.java | 36 ++++++++ ...t.java => CimrGridBandDataSourceTest.java} | 14 ++-- .../snap/cimr/grid/CimrGridBuilderTest.java | 8 +- .../snap/cimr/grid/CimrGridFactoryTest.java | 41 ++++++++++ ...{GlobalGridTest.java => CimrGridTest.java} | 6 +- .../grid/GeometryBandToGridMapperTest.java | 8 +- .../snap/cimr/grid/LazyCrsGeoCodingTest.java | 8 +- .../cimr/grid/LazyGridBandDataSourceTest.java | 10 +-- .../netcdf/NetcdfCimrGeometryFactoryTest.java | 82 +++++++++++++++++++ 34 files changed, 405 insertions(+), 135 deletions(-) create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrBoundingBox.java rename cimr-reader/src/main/java/eu/esa/snap/cimr/grid/{GlobalGrid.java => CimrGrid.java} (80%) rename cimr-reader/src/main/java/eu/esa/snap/cimr/grid/{GlobalGridBandDataSource.java => CimrGridBandDataSource.java} (79%) create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGridFactory.java delete mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridFactory.java create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrBoundingBoxTest.java rename cimr-reader/src/test/java/eu/esa/snap/cimr/grid/{GlobalGridBandDataSourceTest.java => CimrGridBandDataSourceTest.java} (72%) create mode 100644 cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridFactoryTest.java rename cimr-reader/src/test/java/eu/esa/snap/cimr/grid/{GlobalGridTest.java => CimrGridTest.java} (90%) diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java index f13b3b8b7..997d86584 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java @@ -3,8 +3,9 @@ import com.bc.ceres.core.ProgressMonitor; import eu.esa.snap.cimr.cimr.*; import eu.esa.snap.cimr.config.CimrConfigLoader; -import eu.esa.snap.cimr.grid.GlobalGrid; -import eu.esa.snap.cimr.grid.GlobalGridFactory; +import eu.esa.snap.cimr.grid.CimrBoundingBox; +import eu.esa.snap.cimr.grid.CimrGrid; +import eu.esa.snap.cimr.grid.CimrGridFactory; import eu.esa.snap.cimr.netcdf.NetcdfCimrGeometryFactory; import eu.esa.snap.cimr.netcdf.NetcdfCimrBandFactory; import org.esa.snap.core.dataio.AbstractProductReader; @@ -13,6 +14,7 @@ import org.esa.snap.core.datamodel.Product; import org.esa.snap.core.datamodel.ProductData; import org.esa.snap.dataio.netcdf.util.NetcdfFileOpener; +import ucar.ma2.InvalidRangeException; import ucar.nc2.NetcdfFile; import java.awt.*; @@ -57,6 +59,7 @@ protected Product readProductNodesImpl() throws IOException { @Override protected void readBandRasterDataImpl(int sourceOffsetX, int sourceOffsetY, int sourceWidth, int sourceHeight, int sourceStepX, int sourceStepY, Band destBand, int destOffsetX, int destOffsetY, int destWidth, int destHeight, ProductData destBuffer, ProgressMonitor pm) throws IOException { + // TODO BL handle destination offsets final RenderedImage image = destBand.getSourceImage(); final Raster data = image.getData(new Rectangle(destOffsetX, destOffsetY, destWidth, destHeight)); data.getDataElements(destOffsetX, destOffsetY, destWidth, destHeight, destBuffer.getElems()); @@ -99,14 +102,17 @@ private String getInputPath() { return (String) input; } - private CimrReaderContext initContext(NetcdfFile ncFile) throws IOException { + private CimrReaderContext initContext(NetcdfFile ncFile) throws IOException, InvalidRangeException { CimrDescriptorSet descriptorSet = CimrConfigLoader.load("cimr-l1b-config.json"); CimrDimensions dimensions = CimrDimensions.from(ncFile); - GlobalGrid globalGrid = GlobalGridFactory.createGlobalPlateCarree(0.1); + CimrBandDescriptor bbDescriptor = descriptorSet.getMeasurements().getFirst(); NetcdfCimrGeometryFactory geometryFactory = new NetcdfCimrGeometryFactory(ncFile, descriptorSet.getGeometries(), dimensions); + CimrBoundingBox bBox = geometryFactory.getBoundingBox(bbDescriptor, CimrGridFactory.DEFAULT_CELL_SIZE_DEG); + + CimrGrid cimrGrid = CimrGridFactory.createPlateCarreeFromBoundingBox(bBox); NetcdfCimrBandFactory bandFactory = new NetcdfCimrBandFactory(ncFile, dimensions); - return new CimrReaderContext(ncFile, descriptorSet, globalGrid, geometryFactory, bandFactory); + return new CimrReaderContext(ncFile, descriptorSet, cimrGrid, geometryFactory, bandFactory); } } diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrReaderContext.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrReaderContext.java index 2482bf7a4..5f15313ab 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrReaderContext.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrReaderContext.java @@ -21,7 +21,7 @@ public class CimrReaderContext { private final NetcdfFile ncFile; private final CimrDescriptorSet descriptorSet; - private final GlobalGrid globalGrid; + private final CimrGrid cimrGrid; private final GeometryBandToGridMapper mapper; private final NetcdfCimrGeometryFactory geometryFactory; private final NetcdfCimrBandFactory bandFactory; @@ -33,12 +33,12 @@ public class CimrReaderContext { public CimrReaderContext(NetcdfFile ncFile, CimrDescriptorSet descriptorSet, - GlobalGrid globalGrid, + CimrGrid cimrGrid, NetcdfCimrGeometryFactory geomFactory, NetcdfCimrBandFactory bandFactory) { this.ncFile = ncFile; this.descriptorSet = descriptorSet; - this.globalGrid = globalGrid; + this.cimrGrid = cimrGrid; this.mapper = new GeometryBandToGridMapper(); this.geometryFactory = geomFactory; this.bandFactory = bandFactory; @@ -46,8 +46,8 @@ public CimrReaderContext(NetcdfFile ncFile, } - public GlobalGrid getGlobalGrid() { - return this.globalGrid; + public CimrGrid getGlobalGrid() { + return this.cimrGrid; } public CimrDescriptorSet getDescriptorSet() { @@ -57,7 +57,7 @@ public CimrDescriptorSet getDescriptorSet() { public GridBandDataSource getOrCreateGridForVariable(CimrBandDescriptor varDesc, boolean useAverage) { CimrGeometryBand geometryBand = getOrCreateGeometryBand(varDesc); CimrGridBuilder gridBuilder = new CimrGridBuilder(this.mapper); - return gridBuilder.build(geometryBand, this.globalGrid, useAverage); + return gridBuilder.build(geometryBand, this.cimrGrid, useAverage); } public CimrGeometry getOrCreateGeometry(CimrBandDescriptor varDesc) { diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridBuilder.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridBuilder.java index 1130b38fa..bcdbc2534 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridBuilder.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridBuilder.java @@ -1,8 +1,8 @@ package eu.esa.snap.cimr.cimr; import eu.esa.snap.cimr.grid.CimrBand; -import eu.esa.snap.cimr.grid.GlobalGrid; -import eu.esa.snap.cimr.grid.GlobalGridBandDataSource; +import eu.esa.snap.cimr.grid.CimrGrid; +import eu.esa.snap.cimr.grid.CimrGridBandDataSource; import eu.esa.snap.cimr.grid.GeometryBandToGridMapper; @@ -15,8 +15,8 @@ public CimrGridBuilder(GeometryBandToGridMapper mapper) { this.mapper = mapper; } - public GlobalGridBandDataSource build(CimrBand band, GlobalGrid grid, boolean useAverage) { - GlobalGridBandDataSource target = GlobalGridBandDataSource.createEmpty(grid.getWidth(), grid.getHeight()); + public CimrGridBandDataSource build(CimrBand band, CimrGrid grid, boolean useAverage) { + CimrGridBandDataSource target = CimrGridBandDataSource.createEmpty(grid.getWidth(), grid.getHeight()); if (useAverage) { mapper.mapAverage(band, grid, target); } else { diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSource.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSource.java index 213c9e91f..66fe34c1f 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSource.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSource.java @@ -5,7 +5,7 @@ import com.bc.ceres.multilevel.support.AbstractMultiLevelSource; import com.bc.ceres.multilevel.support.DefaultMultiLevelImage; import com.bc.ceres.multilevel.support.DefaultMultiLevelModel; -import eu.esa.snap.cimr.grid.GlobalGrid; +import eu.esa.snap.cimr.grid.CimrGrid; import eu.esa.snap.cimr.grid.GridBandDataSource; import org.esa.snap.core.datamodel.Band; import org.esa.snap.core.image.ResolutionLevel; @@ -35,7 +35,7 @@ protected RenderedImage createImage(int level) { return new CimrGridOpImage(targetBand, resLevel, gridDataSource); } - public static void attachToBand(Band band, GridBandDataSource gridDataSource, GlobalGrid grid) { + public static void attachToBand(Band band, GridBandDataSource gridDataSource, CimrGrid grid) { AffineTransform imageToModel = grid.getProjection().getAffineTransform(grid); MultiLevelModel model = new DefaultMultiLevelModel(MLM_LEVEL_COUNT, imageToModel, grid.getWidth(), grid.getHeight()); diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridProduct.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridProduct.java index c52a19f82..d990505f4 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridProduct.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrGridProduct.java @@ -1,7 +1,7 @@ package eu.esa.snap.cimr.cimr; import eu.esa.snap.cimr.CimrReaderContext; -import eu.esa.snap.cimr.grid.GlobalGrid; +import eu.esa.snap.cimr.grid.CimrGrid; import eu.esa.snap.cimr.grid.GridBandDataSource; import eu.esa.snap.cimr.grid.LazyGridBandDataSource; @@ -12,17 +12,17 @@ public class CimrGridProduct { - private final GlobalGrid globalGrid; + private final CimrGrid cimrGrid; private final Map bands = new LinkedHashMap<>(); - public CimrGridProduct(GlobalGrid globalGrid) { - this.globalGrid = globalGrid; + public CimrGridProduct(CimrGrid cimrGrid) { + this.cimrGrid = cimrGrid; } - public GlobalGrid getGlobalGrid() { - return globalGrid; + public CimrGrid getGlobalGrid() { + return cimrGrid; } public void addBand(CimrBandDescriptor descriptor, GridBandDataSource dataSource) { @@ -43,10 +43,10 @@ public int getBandCount() { public static CimrGridProduct buildLazy(CimrReaderContext context, boolean useAverage) { - GlobalGrid globalGrid = context.getGlobalGrid(); + CimrGrid cimrGrid = context.getGlobalGrid(); CimrDescriptorSet descriptorSet = context.getDescriptorSet(); - CimrGridProduct product = new CimrGridProduct(globalGrid); + CimrGridProduct product = new CimrGridProduct(cimrGrid); for (CimrBandDescriptor desc : descriptorSet.getTiepointVariables()) { GridBandDataSource dataSource = new LazyGridBandDataSource(context, desc, useAverage); diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilder.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilder.java index f23681004..e3cf682b1 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilder.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilder.java @@ -1,6 +1,6 @@ package eu.esa.snap.cimr.cimr; -import eu.esa.snap.cimr.grid.GlobalGrid; +import eu.esa.snap.cimr.grid.CimrGrid; import eu.esa.snap.cimr.grid.GridBandDataSource; import eu.esa.snap.cimr.grid.LazyCrsGeoCoding; import org.esa.snap.core.datamodel.Band; @@ -18,7 +18,7 @@ public class CimrSnapProductBuilder { public static Product buildProduct(String productName, String productType, CimrGridProduct cimrProduct, String path) throws Exception { - GlobalGrid grid = cimrProduct.getGlobalGrid(); + CimrGrid grid = cimrProduct.getGlobalGrid(); Product product = new Product(productName, productType, grid.getWidth(), grid.getHeight()); addGeoCoding(grid, product); @@ -30,14 +30,14 @@ public static Product buildProduct(String productName, String productType, CimrG return product; } - private static void addGeoCoding(GlobalGrid grid, Product product) { + private static void addGeoCoding(CimrGrid grid, Product product) { GeoCoding geoCoding = new LazyCrsGeoCoding(grid); product.setSceneGeoCoding(geoCoding); } private static void addBands(CimrGridProduct cimrProduct, Product product) { - GlobalGrid grid = cimrProduct.getGlobalGrid(); + CimrGrid grid = cimrProduct.getGlobalGrid(); for (Map.Entry e : cimrProduct.getBands().entrySet()) { CimrBandDescriptor desc = e.getKey(); diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrBoundingBox.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrBoundingBox.java new file mode 100644 index 000000000..0eb2b37af --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrBoundingBox.java @@ -0,0 +1,78 @@ +package eu.esa.snap.cimr.grid; + + +public class CimrBoundingBox { + + private static final double DEFAULT_BOUNDING_BOX_OFFSET_DEG = .5; + + double lonMin; + double lonMax; + double latMin; + double latMax; + + + public CimrBoundingBox(double lonMin, double lonMax, double latMin, double latMax) { + this.lonMin = lonMin; + this.lonMax = lonMax; + this.latMin = latMin; + this.latMax = latMax; + } + + public double getLonMin() { + return lonMin; + } + + public double getLonMax() { + return lonMax; + } + + public double getLatMin() { + return latMin; + } + + public double getLatMax() { + return latMax; + } + + public double getWidth() { + return lonMax - lonMin; + } + + public double getHeight() { + return latMax - latMin; + } + + public static CimrBoundingBox create(CimrGeometry geometry, double cellSizeDeg) { + double lonMin = Double.POSITIVE_INFINITY; + double lonMax = Double.NEGATIVE_INFINITY; + double latMin = Double.POSITIVE_INFINITY; + double latMax = Double.NEGATIVE_INFINITY; + + for (int ii = 0; ii < geometry.getScanCount(); ii++) { + for(int jj = 0; jj < geometry.getSampleCount(); jj++) { + double lon = geometry.getGeoPos(ii, jj, 0).getLon(); + double lat = geometry.getGeoPos(ii, jj, 0).getLat(); + + if (lon < lonMin) lonMin = lon; + if (lon > lonMax) lonMax = lon; + if (lat < latMin) latMin = lat; + if (lat > latMax) latMax = lat; + } + } + + lonMin = snapToGrid(lonMin - DEFAULT_BOUNDING_BOX_OFFSET_DEG, true, cellSizeDeg); + lonMax = snapToGrid(lonMax + DEFAULT_BOUNDING_BOX_OFFSET_DEG, false, cellSizeDeg); + latMin = snapToGrid(latMin - DEFAULT_BOUNDING_BOX_OFFSET_DEG, true, cellSizeDeg); + latMax = snapToGrid(latMax + DEFAULT_BOUNDING_BOX_OFFSET_DEG, false, cellSizeDeg); + + return new CimrBoundingBox(lonMin, lonMax, latMin, latMax); + } + + private static double snapToGrid(double val, boolean isMin, double cellSizeDeg) { + if (isMin) { + return Math.floor(val / cellSizeDeg) * cellSizeDeg; + } else { + return Math.ceil(val / cellSizeDeg) * cellSizeDeg; + } + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGrid.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGrid.java similarity index 80% rename from cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGrid.java rename to cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGrid.java index 7cf3d0f05..6c307ca53 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGrid.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGrid.java @@ -5,14 +5,14 @@ import java.awt.*; -public class GlobalGrid { +public class CimrGrid { - private int width; - private int height; + private final int width; + private final int height; private final GridProjection projection; - public GlobalGrid(GridProjection projection, int width, int height) { + public CimrGrid(GridProjection projection, int width, int height) { this.projection = projection; this.height = height; this.width = width; diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridBandDataSource.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGridBandDataSource.java similarity index 79% rename from cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridBandDataSource.java rename to cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGridBandDataSource.java index a1d0c3978..8bc79adfe 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridBandDataSource.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGridBandDataSource.java @@ -3,14 +3,14 @@ import java.util.Arrays; -public class GlobalGridBandDataSource implements GridBandDataSource { +public class CimrGridBandDataSource implements GridBandDataSource { private final int width; private final int height; private final double[] data; - public GlobalGridBandDataSource(int width, int height, double[] data) { + public CimrGridBandDataSource(int width, int height, double[] data) { if (data.length != width * height) { throw new IllegalArgumentException("data length must be width * height"); } @@ -20,10 +20,10 @@ public GlobalGridBandDataSource(int width, int height, double[] data) { } - public static GlobalGridBandDataSource createEmpty(int width, int height) { + public static CimrGridBandDataSource createEmpty(int width, int height) { double[] data = new double[width * height]; Arrays.fill(data, Double.NaN); - return new GlobalGridBandDataSource(width, height, data); + return new CimrGridBandDataSource(width, height, data); } public int getWidth() { diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGridFactory.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGridFactory.java new file mode 100644 index 000000000..2219a5afa --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/CimrGridFactory.java @@ -0,0 +1,38 @@ +package eu.esa.snap.cimr.grid; + + +public class CimrGridFactory { + + public static final double DEFAULT_CELL_SIZE_DEG = .02; + + + public static CimrGrid createGlobalPlateCarree(double cellSizeDeg) { + int width = (int) Math.round(360.0 / cellSizeDeg); + int height = (int) Math.round(180.0 / cellSizeDeg); + + double lonMin = -180.0; + double latMax = 90.0; + + PlateCarreeProjection proj = new PlateCarreeProjection( + width, height, lonMin, latMax, cellSizeDeg, cellSizeDeg + ); + return new CimrGrid(proj, width, height); + } + + public static CimrGrid createPlateCarreeFromBoundingBox(CimrBoundingBox bBox) { + return createPlateCarreeFromBoundingBox(bBox, DEFAULT_CELL_SIZE_DEG); + } + + public static CimrGrid createPlateCarreeFromBoundingBox(CimrBoundingBox bBox, double cellSizeDeg) { + int width = (int) Math.round(bBox.getWidth() / cellSizeDeg); + int height = (int) Math.round(bBox.getHeight() / cellSizeDeg); + + double lonMin = bBox.lonMin; + double latMax = bBox.latMax; + + PlateCarreeProjection proj = new PlateCarreeProjection( + width, height, lonMin, latMax, cellSizeDeg, cellSizeDeg + ); + return new CimrGrid(proj, width, height); + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GeometryBandToGridMapper.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GeometryBandToGridMapper.java index c634def7f..b35d4033f 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GeometryBandToGridMapper.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GeometryBandToGridMapper.java @@ -9,7 +9,7 @@ public class GeometryBandToGridMapper { - public void mapNearest(CimrBand band, GlobalGrid grid, GridBandDataSource target) { + public void mapNearest(CimrBand band, CimrGrid grid, GridBandDataSource target) { Point gridPoint = new Point(); for (int ss = 0; ss < band.getScanCount(); ss++) { @@ -28,7 +28,7 @@ public void mapNearest(CimrBand band, GlobalGrid grid, GridBandDataSource target } } - public void mapAverage(CimrBand band, GlobalGrid grid, GridBandDataSource target) { + public void mapAverage(CimrBand band, CimrGrid grid, GridBandDataSource target) { int width = grid.getWidth(); int height = grid.getHeight(); diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridFactory.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridFactory.java deleted file mode 100644 index cbebb4a3c..000000000 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GlobalGridFactory.java +++ /dev/null @@ -1,18 +0,0 @@ -package eu.esa.snap.cimr.grid; - - -public class GlobalGridFactory { - - public static GlobalGrid createGlobalPlateCarree(double cellSizeDeg) { - int width = (int) Math.round(360.0 / cellSizeDeg); - int height = (int) Math.round(180.0 / cellSizeDeg); - - double lonMin = -180.0; - double latMax = 90.0; - - PlateCarreeProjection proj = new PlateCarreeProjection( - width, height, lonMin, latMax, cellSizeDeg, cellSizeDeg - ); - return new GlobalGrid(proj, width, height); - } -} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GridProjection.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GridProjection.java index 7448c35f4..056e53540 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GridProjection.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/GridProjection.java @@ -14,7 +14,7 @@ public interface GridProjection { boolean geoPosToGrid(GeoPos lat, Point out); CoordinateReferenceSystem getCrs() throws FactoryException; - AffineTransform getAffineTransform(GlobalGrid grid); + AffineTransform getAffineTransform(CimrGrid grid); double getLonMin(); double getLatMax(); diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/LazyCrsGeoCoding.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/LazyCrsGeoCoding.java index e267a4cbf..a61472601 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/LazyCrsGeoCoding.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/LazyCrsGeoCoding.java @@ -11,10 +11,10 @@ public class LazyCrsGeoCoding implements GeoCoding { - private final GlobalGrid grid; + private final CimrGrid grid; private GeoCoding delegate; - public LazyCrsGeoCoding(GlobalGrid grid) { + public LazyCrsGeoCoding(CimrGrid grid) { this.grid = grid; } diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/PlateCarreeProjection.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/PlateCarreeProjection.java index 14d0b540b..49937cb5a 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/PlateCarreeProjection.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/grid/PlateCarreeProjection.java @@ -86,7 +86,7 @@ public CoordinateReferenceSystem getCrs() throws FactoryException { } @Override - public AffineTransform getAffineTransform(GlobalGrid grid) { + public AffineTransform getAffineTransform(CimrGrid grid) { double lonMin = grid.getProjection().getLonMin(); double latMax = grid.getProjection().getLatMax(); double deltaLon = grid.getProjection().getDeltaLon(); diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactory.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactory.java index e31ee9c52..30af11907 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactory.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrBandFactory.java @@ -56,6 +56,7 @@ public CimrGeometryBand createGeometryBand(CimrBandDescriptor desc, CimrGeometry int sampleCount = getSampleCount(desc); values = new double[nScans][sampleCount]; + // TODO extract Tiepoint interpolation for (int s = 0; s < nScans; s++) { for (int smp = 0; smp < sampleCount; smp++) { double t = (double) smp * (nSamples - 1) / (double) (sampleCount - 1); diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactory.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactory.java index bcfa0fa0d..df0f67d5c 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactory.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactory.java @@ -2,6 +2,7 @@ import eu.esa.snap.cimr.cimr.CimrBandDescriptor; import eu.esa.snap.cimr.cimr.CimrDimensions; +import eu.esa.snap.cimr.grid.CimrBoundingBox; import eu.esa.snap.cimr.grid.CimrTiepointGeometry; import eu.esa.snap.cimr.grid.CimrGeometry; import org.esa.snap.core.datamodel.GeoPos; @@ -113,4 +114,9 @@ private double ensureLongitude(double lon) { while (lon <= -180.0) lon += 360.0; return lon; } + + public CimrBoundingBox getBoundingBox(CimrBandDescriptor variableDesc, double cellSizeDeg) throws InvalidRangeException, IOException { + CimrGeometry bbGeometry = getOrCreateGeometry(variableDesc); + return CimrBoundingBox.create(bbGeometry, cellSizeDeg); + } } diff --git a/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/cimr-l1b-config.json b/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/cimr-l1b-config.json index d63b88f43..fcff04e0a 100644 --- a/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/cimr-l1b-config.json +++ b/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/cimr-l1b-config.json @@ -113,7 +113,7 @@ "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], "footprintVars" : ["C_BAND_footprint_minor_axis_feed2", "C_BAND_footprint_major_axis_feed2", "C_BAND_geometric_rot_angle_feed2"], "groupPath": "/Data/Measurement_Data/C_BAND/", - "feedIndex": 0, + "feedIndex": 1, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], "dataType": "double", "unit": "m", @@ -126,7 +126,7 @@ "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], "footprintVars" : ["C_BAND_footprint_minor_axis_feed2", "C_BAND_footprint_major_axis_feed2", "C_BAND_geometric_rot_angle_feed2"], "groupPath": "/Data/Measurement_Data/C_BAND/", - "feedIndex": 0, + "feedIndex": 1, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], "dataType": "double", "unit": "m", @@ -139,7 +139,7 @@ "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], "footprintVars" : ["C_BAND_footprint_minor_axis_feed2", "C_BAND_footprint_major_axis_feed2", "C_BAND_geometric_rot_angle_feed2"], "groupPath": "/Data/Measurement_Data/C_BAND/", - "feedIndex": 0, + "feedIndex": 1, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], "dataType": "double", "unit": "deg", diff --git a/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/test-config.json b/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/test-config.json index d63b88f43..fcff04e0a 100644 --- a/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/test-config.json +++ b/cimr-reader/src/main/resources/eu/esa/snap/cimr/config/test-config.json @@ -113,7 +113,7 @@ "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], "footprintVars" : ["C_BAND_footprint_minor_axis_feed2", "C_BAND_footprint_major_axis_feed2", "C_BAND_geometric_rot_angle_feed2"], "groupPath": "/Data/Measurement_Data/C_BAND/", - "feedIndex": 0, + "feedIndex": 1, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], "dataType": "double", "unit": "m", @@ -126,7 +126,7 @@ "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], "footprintVars" : ["C_BAND_footprint_minor_axis_feed2", "C_BAND_footprint_major_axis_feed2", "C_BAND_geometric_rot_angle_feed2"], "groupPath": "/Data/Measurement_Data/C_BAND/", - "feedIndex": 0, + "feedIndex": 1, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], "dataType": "double", "unit": "m", @@ -139,7 +139,7 @@ "geometryNames": ["C_BAND_latitude_feed2", "C_BAND_longitude_feed2"], "footprintVars" : ["C_BAND_footprint_minor_axis_feed2", "C_BAND_footprint_major_axis_feed2", "C_BAND_geometric_rot_angle_feed2"], "groupPath": "/Data/Measurement_Data/C_BAND/", - "feedIndex": 0, + "feedIndex": 1, "dimensions": ["n_scans", "n_tie_points_C_BAND", "n_feeds_C_BAND"], "dataType": "double", "unit": "deg", diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextFootprintTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextFootprintTest.java index 2fc2f8b68..a27aa8b9f 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextFootprintTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextFootprintTest.java @@ -6,7 +6,7 @@ import eu.esa.snap.cimr.cimr.CimrFrequencyBand; import eu.esa.snap.cimr.grid.CimrGeometry; import eu.esa.snap.cimr.grid.CimrGeometryBand; -import eu.esa.snap.cimr.grid.GlobalGrid; +import eu.esa.snap.cimr.grid.CimrGrid; import eu.esa.snap.cimr.netcdf.NetcdfCimrBandFactory; import eu.esa.snap.cimr.netcdf.NetcdfCimrFootprintFactory; import eu.esa.snap.cimr.netcdf.NetcdfCimrGeometryFactory; @@ -39,7 +39,7 @@ public class CimrReaderContextFootprintTest { private CimrDescriptorSet descriptorSet; @Mock - private GlobalGrid globalGrid; + private CimrGrid cimrGrid; @Mock private NetcdfCimrGeometryFactory geometryFactory; @@ -90,7 +90,7 @@ public class CimrReaderContextFootprintTest { @Before public void setUp() throws Exception { - context = new CimrReaderContext(ncFile, descriptorSet, globalGrid, geometryFactory, bandFactory); + context = new CimrReaderContext(ncFile, descriptorSet, cimrGrid, geometryFactory, bandFactory); Field ff = CimrReaderContext.class.getDeclaredField("footprintFactory"); ff.setAccessible(true); diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextTest.java index 9fbd4315b..babec2ecb 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextTest.java @@ -22,7 +22,7 @@ public class CimrReaderContextTest { @Test public void testConstructorAndGetters() { - GlobalGrid grid = createTestGrid(); + CimrGrid grid = createTestGrid(); CimrDescriptorSet descriptorSet = createEmptyDescriptorSet(); NetcdfCimrGeometryFactory geomFactory = new NetcdfCimrGeometryFactory(null, Collections.emptyList(), null); @@ -42,7 +42,7 @@ public void testConstructorAndGetters() { @Test public void testGetOrCreateGeometry_DelegatesAndWrapsCheckedException() { - GlobalGrid grid = createTestGrid(); + CimrGrid grid = createTestGrid(); CimrDescriptorSet descriptorSet = createEmptyDescriptorSet(); CimrBandDescriptor desc = createTestDescriptor(); @@ -75,7 +75,7 @@ public CimrGeometry getOrCreateGeometry(CimrBandDescriptor variableDesc) @Test public void testGetOrCreateGridForVariable() { - GlobalGrid grid = createTestGrid(); + CimrGrid grid = createTestGrid(); CimrDescriptorSet descriptorSet = createEmptyDescriptorSet(); CimrBandDescriptor desc = createTestDescriptor(); @@ -122,7 +122,7 @@ public CimrGeometryBand createGeometryBand(CimrBandDescriptor desc, CimrGeometry @Test public void testGetOrCreateGridForVariable_WrapsBandFactoryCheckedException() { - GlobalGrid grid = createTestGrid(); + CimrGrid grid = createTestGrid(); CimrDescriptorSet descriptorSet = createEmptyDescriptorSet(); CimrBandDescriptor desc = createTestDescriptor(); @@ -163,7 +163,7 @@ public CimrGeometryBand createGeometryBand(CimrBandDescriptor desc, CimrGeometry @Test public void testGetOrCreateGridForVariable_PropagatesGeometryRuntimeException() { - GlobalGrid grid = createTestGrid(); + CimrGrid grid = createTestGrid(); CimrDescriptorSet descriptorSet = createEmptyDescriptorSet(); CimrBandDescriptor desc = createTestDescriptor(); @@ -211,7 +211,7 @@ public void testClearCache_clearsBandCacheAndGeometryCache() { 0.0, 0.0, 1.0, 1.0 ); - GlobalGrid grid = new GlobalGrid(proj, 2, 1); + CimrGrid grid = new CimrGrid(proj, 2, 1); CimrBandDescriptor varDesc = new CimrBandDescriptor( "testVar", "v", CimrFrequencyBand.C_BAND, @@ -287,13 +287,13 @@ public CimrGeometryBand createGeometryBand(CimrBandDescriptor desc, CimrGeometry - private GlobalGrid createTestGrid() { + private CimrGrid createTestGrid() { PlateCarreeProjection proj = new PlateCarreeProjection( 1, 1, -0.5, 0.5, 1.0, 1.0 ); - return new GlobalGrid(proj, 1, 1); + return new CimrGrid(proj, 1, 1); } private CimrBandDescriptor createTestDescriptor() { diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridBuilderTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridBuilderTest.java index f40e4a17f..c4eddec5a 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridBuilderTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridBuilderTest.java @@ -13,9 +13,9 @@ public class CimrGridBuilderTest { public void build_whenUseAverageTrue_usesMapAverage() { RecordingMapper mapper = new RecordingMapper(); CimrGridBuilder builder = new CimrGridBuilder(mapper); - GlobalGrid grid = GlobalGridFactory.createGlobalPlateCarree(10.0); + CimrGrid grid = CimrGridFactory.createGlobalPlateCarree(10.0); - GlobalGridBandDataSource result = builder.build(null, grid, true); + CimrGridBandDataSource result = builder.build(null, grid, true); assertTrue(mapper.mapAverageCalled); assertFalse(mapper.mapNearestCalled); @@ -28,9 +28,9 @@ public void build_whenUseAverageTrue_usesMapAverage() { public void build_whenUseAverageFalse_usesMapNearest() { RecordingMapper mapper = new RecordingMapper(); CimrGridBuilder builder = new CimrGridBuilder(mapper); - GlobalGrid grid = GlobalGridFactory.createGlobalPlateCarree(10.0); + CimrGrid grid = CimrGridFactory.createGlobalPlateCarree(10.0); - GlobalGridBandDataSource result = builder.build(null, grid, false); + CimrGridBandDataSource result = builder.build(null, grid, false); assertFalse(mapper.mapAverageCalled); assertTrue(mapper.mapNearestCalled); @@ -43,11 +43,11 @@ private static class RecordingMapper extends GeometryBandToGridMapper { boolean mapAverageCalled; boolean mapNearestCalled; CimrBand lastBand; - GlobalGrid lastGrid; + CimrGrid lastGrid; GridBandDataSource lastTarget; @Override - public void mapAverage(CimrBand band, GlobalGrid grid, GridBandDataSource target) { + public void mapAverage(CimrBand band, CimrGrid grid, GridBandDataSource target) { mapAverageCalled = true; lastBand = band; lastGrid = grid; @@ -55,7 +55,7 @@ public void mapAverage(CimrBand band, GlobalGrid grid, GridBandDataSource target } @Override - public void mapNearest(CimrBand band, GlobalGrid grid, GridBandDataSource target) { + public void mapNearest(CimrBand band, CimrGrid grid, GridBandDataSource target) { mapNearestCalled = true; lastBand = band; lastGrid = grid; diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSourceTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSourceTest.java index c8f7bdfcb..4faa5518a 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSourceTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridMultiLevelSourceTest.java @@ -4,7 +4,7 @@ import com.bc.ceres.multilevel.MultiLevelModel; import com.bc.ceres.multilevel.support.DefaultMultiLevelImage; import com.bc.ceres.multilevel.support.DefaultMultiLevelModel; -import eu.esa.snap.cimr.grid.GlobalGrid; +import eu.esa.snap.cimr.grid.CimrGrid; import eu.esa.snap.cimr.grid.GridBandDataSource; import eu.esa.snap.cimr.grid.PlateCarreeProjection; import org.esa.snap.core.datamodel.Band; @@ -79,9 +79,9 @@ public void setSample(int x, int y, double value) { -180.0, 90.0, 360.0 / width, 180.0 / height ); - GlobalGrid globalGrid = new GlobalGrid(projection, width, height); + CimrGrid cimrGrid = new CimrGrid(projection, width, height); - CimrGridMultiLevelSource.attachToBand(band, dataSource, globalGrid); + CimrGridMultiLevelSource.attachToBand(band, dataSource, cimrGrid); assertNotNull(band.getSourceImage()); assertTrue(band.getSourceImage() instanceof DefaultMultiLevelImage); diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridProductTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridProductTest.java index e3561b3cb..89d729255 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridProductTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrGridProductTest.java @@ -19,7 +19,7 @@ public void testAddAndGetBands() { 0.0, 1.0, 1.0, 1.0 ); - GlobalGrid grid = new GlobalGrid(proj, 2,1); + CimrGrid grid = new CimrGrid(proj, 2,1); CimrGridProduct product = new CimrGridProduct(grid); @@ -42,8 +42,8 @@ public void testAddAndGetBands() { double[] data1 = {1.0, 2.0}; double[] data2 = {10.0, 20.0}; - GridBandDataSource ds1 = new GlobalGridBandDataSource(2, 1, data1); - GridBandDataSource ds2 = new GlobalGridBandDataSource(2, 1, data2); + GridBandDataSource ds1 = new CimrGridBandDataSource(2, 1, data1); + GridBandDataSource ds2 = new CimrGridBandDataSource(2, 1, data2); product.addBand(band1, ds1); product.addBand(band2, ds2); @@ -60,7 +60,7 @@ public void testAddAndGetBands() { @Test public void testBuildLazyCreatesBandsFromDescriptorSet() { PlateCarreeProjection proj = new PlateCarreeProjection(2, 1, 0.0, 1.0, 1.0, 1.0); - GlobalGrid grid = new GlobalGrid(proj, 2, 1); + CimrGrid grid = new CimrGrid(proj, 2, 1); CimrBandDescriptor tieDesc = new CimrBandDescriptor( "altitude", "altitude", CimrFrequencyBand.C_BAND, @@ -103,7 +103,7 @@ public void testBuildLazyCreatesBandsFromDescriptorSet() { @Test(expected = UnsupportedOperationException.class) public void testGetBandsIsUnmodifiable() { PlateCarreeProjection proj = new PlateCarreeProjection(2, 1, 0.0, 1.0, 1.0, 1.0); - GlobalGrid grid = new GlobalGrid(proj, 2, 1); + CimrGrid grid = new CimrGrid(proj, 2, 1); CimrGridProduct product = new CimrGridProduct(grid); CimrBandDescriptor band = new CimrBandDescriptor( @@ -114,7 +114,7 @@ public void testGetBandsIsUnmodifiable() { new String[] {"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, "double", "", "" ); - GridBandDataSource ds = new GlobalGridBandDataSource(2, 1, new double[]{1.0, 2.0}); + GridBandDataSource ds = new CimrGridBandDataSource(2, 1, new double[]{1.0, 2.0}); product.addBand(band, ds); product.getBands().put(band, ds); diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilderTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilderTest.java index 31c3e30dc..de1b6905f 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilderTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrSnapProductBuilderTest.java @@ -1,7 +1,7 @@ package eu.esa.snap.cimr.cimr; -import eu.esa.snap.cimr.grid.GlobalGridBandDataSource; -import eu.esa.snap.cimr.grid.GlobalGrid; +import eu.esa.snap.cimr.grid.CimrGridBandDataSource; +import eu.esa.snap.cimr.grid.CimrGrid; import eu.esa.snap.cimr.grid.GridBandDataSource; import eu.esa.snap.cimr.grid.PlateCarreeProjection; import org.esa.snap.core.datamodel.Band; @@ -24,9 +24,9 @@ public void testBuildSnapProduct_createsBandsAndValues() throws Exception { 0.0, 1.0, 1.0, 1.0 ); - GlobalGrid globalGrid = new GlobalGrid(proj, 2, 1); + CimrGrid cimrGrid = new CimrGrid(proj, 2, 1); - CimrGridProduct gridProduct = new CimrGridProduct(globalGrid); + CimrGridProduct gridProduct = new CimrGridProduct(cimrGrid); CimrBandDescriptor bandDesc = new CimrBandDescriptor( "C_raw_bt_h_feed1", "raw_bt_h", CimrFrequencyBand.C_BAND, @@ -38,7 +38,7 @@ public void testBuildSnapProduct_createsBandsAndValues() throws Exception { ); double[] data = {1.0, 2.0}; - GridBandDataSource ds = new GlobalGridBandDataSource(2, 1, data); + GridBandDataSource ds = new CimrGridBandDataSource(2, 1, data); gridProduct.addBand(bandDesc, ds); Product product = CimrSnapProductBuilder.buildProduct("TEST", "CIMR_GRID", gridProduct, "path"); @@ -62,9 +62,9 @@ public void testBuildSnapProduct_setsMetadataAndAutoGrouping() throws Exception 0.0, 1.0, 1.0, 1.0 ); - GlobalGrid globalGrid = new GlobalGrid(proj, 2, 1); + CimrGrid cimrGrid = new CimrGrid(proj, 2, 1); - CimrGridProduct gridProduct = new CimrGridProduct(globalGrid); + CimrGridProduct gridProduct = new CimrGridProduct(cimrGrid); CimrBandDescriptor bandDesc = new CimrBandDescriptor( "C_raw_bt_h_feed1", "raw_bt_h", CimrFrequencyBand.C_BAND, @@ -77,7 +77,7 @@ public void testBuildSnapProduct_setsMetadataAndAutoGrouping() throws Exception ); double[] data = {1.0, 2.0}; - GridBandDataSource ds = new GlobalGridBandDataSource(2, 1, data); + GridBandDataSource ds = new CimrGridBandDataSource(2, 1, data); gridProduct.addBand(bandDesc, ds); String path = "some\\path\\file.nc"; @@ -104,9 +104,9 @@ public void testBuildSnapProduct_withMultipleBands_allPresentAndCorrect() throws 0.0, 1.0, 1.0, 1.0 ); - GlobalGrid globalGrid = new GlobalGrid(proj, 2, 1); + CimrGrid cimrGrid = new CimrGrid(proj, 2, 1); - CimrGridProduct gridProduct = new CimrGridProduct(globalGrid); + CimrGridProduct gridProduct = new CimrGridProduct(cimrGrid); CimrBandDescriptor band1 = new CimrBandDescriptor( "band1", "raw1", CimrFrequencyBand.C_BAND, @@ -125,8 +125,8 @@ public void testBuildSnapProduct_withMultipleBands_allPresentAndCorrect() throws "double", "", "" ); - GridBandDataSource ds1 = new GlobalGridBandDataSource(2, 1, new double[]{1.0, 2.0}); - GridBandDataSource ds2 = new GlobalGridBandDataSource(2, 1, new double[]{10.0, 20.0}); + GridBandDataSource ds1 = new CimrGridBandDataSource(2, 1, new double[]{1.0, 2.0}); + GridBandDataSource ds2 = new CimrGridBandDataSource(2, 1, new double[]{10.0, 20.0}); gridProduct.addBand(band1, ds1); gridProduct.addBand(band2, ds2); @@ -153,9 +153,9 @@ public void testBuildSnapProduct_withNoBands_createsEmptyProductWithGeoCoding() 0.0, 1.0, 1.0, 1.0 ); - GlobalGrid globalGrid = new GlobalGrid(proj, 4, 2); + CimrGrid cimrGrid = new CimrGrid(proj, 4, 2); - CimrGridProduct gridProduct = new CimrGridProduct(globalGrid); + CimrGridProduct gridProduct = new CimrGridProduct(cimrGrid); Product product = CimrSnapProductBuilder.buildProduct("EMPTY", "CIMR_GRID", gridProduct, "path"); diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrBoundingBoxTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrBoundingBoxTest.java new file mode 100644 index 000000000..f383c750c --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrBoundingBoxTest.java @@ -0,0 +1,36 @@ +package eu.esa.snap.cimr.grid; + +import org.esa.snap.core.datamodel.GeoPos; +import org.junit.Test; + +import static org.junit.Assert.*; + + +public class CimrBoundingBoxTest { + + private static final double doubleErr = 0.00001; + + @Test + public void testCreateBoundingBox() { + int scanCount = 5; + int tpCount = 4; + int sampleCount = 8; + GeoPos[][][] tiePoints = new GeoPos[scanCount][tpCount][1]; + + for (int s = 0; s < scanCount; s++) { + for (int tp = 0; tp < tpCount; tp++) { + double lat = 50.0234 + s; + double lon = 10.542 + tp * 2; + tiePoints[s][tp][0] = new GeoPos(lat, lon); + } + } + + CimrGeometry geometry = new CimrTiepointGeometry(tiePoints, sampleCount); + CimrBoundingBox bBox = CimrBoundingBox.create(geometry, 0.02); + + assertEquals(49.52, bBox.getLatMin(), doubleErr); + assertEquals(10.04, bBox.getLonMin(), doubleErr); + assertEquals(54.54, bBox.getLatMax(), doubleErr); + assertEquals(17.06, bBox.getLonMax(), doubleErr); + } +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GlobalGridBandDataSourceTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridBandDataSourceTest.java similarity index 72% rename from cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GlobalGridBandDataSourceTest.java rename to cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridBandDataSourceTest.java index e4383c678..96e3f1774 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GlobalGridBandDataSourceTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridBandDataSourceTest.java @@ -5,7 +5,7 @@ import static org.junit.Assert.*; -public class GlobalGridBandDataSourceTest { +public class CimrGridBandDataSourceTest { @Test public void testGetSample_basicLayout() { @@ -15,7 +15,7 @@ public void testGetSample_basicLayout() { 1.0, 2.0, 3.0, 4.0 }; - GlobalGridBandDataSource ds = new GlobalGridBandDataSource(width, height, data); + CimrGridBandDataSource ds = new CimrGridBandDataSource(width, height, data); assertEquals(1.0, ds.getSample(0, 0), 1e-12); assertEquals(2.0, ds.getSample(1, 0), 1e-12); @@ -25,7 +25,7 @@ public void testGetSample_basicLayout() { @Test public void testCreateEmpty_initialNaN() { - GlobalGridBandDataSource ds = GlobalGridBandDataSource.createEmpty(2, 2); + CimrGridBandDataSource ds = CimrGridBandDataSource.createEmpty(2, 2); assertTrue(Double.isNaN(ds.getSample(0, 0))); assertTrue(Double.isNaN(ds.getSample(1, 0))); @@ -35,7 +35,7 @@ public void testCreateEmpty_initialNaN() { @Test public void testSetSample() { - GlobalGridBandDataSource ds = GlobalGridBandDataSource.createEmpty(2, 1); + CimrGridBandDataSource ds = CimrGridBandDataSource.createEmpty(2, 1); ds.setSample(0, 0, 42.0); ds.setSample(1, 0, 7.0); @@ -46,18 +46,18 @@ public void testSetSample() { @Test(expected = IllegalArgumentException.class) public void testConstructor_invalidLength_throws() { - new GlobalGridBandDataSource(2, 2, new double[]{1.0, 2.0, 3.0}); + new CimrGridBandDataSource(2, 2, new double[]{1.0, 2.0, 3.0}); } @Test(expected = IllegalArgumentException.class) public void testGetSample_outOfBounds_throws() { - GlobalGridBandDataSource ds = GlobalGridBandDataSource.createEmpty(2, 2); + CimrGridBandDataSource ds = CimrGridBandDataSource.createEmpty(2, 2); ds.getSample(2, 0); } @Test(expected = IllegalArgumentException.class) public void testSetSample_outOfBounds_throws() { - GlobalGridBandDataSource ds = GlobalGridBandDataSource.createEmpty(2, 2); + CimrGridBandDataSource ds = CimrGridBandDataSource.createEmpty(2, 2); ds.setSample(-1, 0, 5.0); } } \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridBuilderTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridBuilderTest.java index 05f8d6101..d49558311 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridBuilderTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridBuilderTest.java @@ -23,9 +23,9 @@ public void testBuild_usesAverageWhenTrue() { 0.0, 1.0, 1.0, 1.0 ); - GlobalGrid grid = new GlobalGrid(proj, 1, 1); + CimrGrid grid = new CimrGrid(proj, 1, 1); - GlobalGridBandDataSource target = builder.build(swath, grid, true); + CimrGridBandDataSource target = builder.build(swath, grid, true); assertEquals(41.0, target.getSample(0, 0), doubleErr); assertEquals(1, target.getWidth()); @@ -43,9 +43,9 @@ public void testBuild_usesNearestWhenFalse() { 0.0, 1.0, 1.0, 1.0 ); - GlobalGrid grid = new GlobalGrid(proj, 1, 1); + CimrGrid grid = new CimrGrid(proj, 1, 1); - GlobalGridBandDataSource target = builder.build(swath, grid, false); + CimrGridBandDataSource target = builder.build(swath, grid, false); assertEquals(40.0, target.getSample(0, 0), doubleErr); assertEquals(1, target.getWidth()); diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridFactoryTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridFactoryTest.java new file mode 100644 index 000000000..b6fbb3c59 --- /dev/null +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridFactoryTest.java @@ -0,0 +1,41 @@ +package eu.esa.snap.cimr.grid; + +import org.esa.snap.core.datamodel.GeoPos; +import org.junit.Test; + +import static org.junit.Assert.*; + + +public class CimrGridFactoryTest { + + private static final double doubleErr = 0.00001; + + @Test + public void testCreatePlateCarreeFromBoundingBox() { + int scanCount = 5; + int tpCount = 4; + int sampleCount = 8; + GeoPos[][][] tiePoints = new GeoPos[scanCount][tpCount][1]; + + for (int s = 0; s < scanCount; s++) { + for (int tp = 0; tp < tpCount; tp++) { + double lat = 50.0234 + s; + double lon = 10.542 + tp * 2; + tiePoints[s][tp][0] = new GeoPos(lat, lon); + } + } + + CimrGeometry geometry = new CimrTiepointGeometry(tiePoints, sampleCount); + CimrBoundingBox bBox = CimrBoundingBox.create(geometry, CimrGridFactory.DEFAULT_CELL_SIZE_DEG); + + CimrGrid grid = CimrGridFactory.createPlateCarreeFromBoundingBox(bBox); + + assertEquals(351.0, grid.getWidth(), doubleErr); + assertEquals(251.0, grid.getHeight(), doubleErr); + assertEquals(CimrGridFactory.DEFAULT_CELL_SIZE_DEG, grid.getProjection().getDeltaLat(), doubleErr); + assertEquals(CimrGridFactory.DEFAULT_CELL_SIZE_DEG, grid.getProjection().getDeltaLon(), doubleErr); + assertEquals(10.04, grid.getProjection().getLonMin(), doubleErr); + assertEquals(54.54, grid.getProjection().getLatMax(), doubleErr); + } + +} \ No newline at end of file diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GlobalGridTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridTest.java similarity index 90% rename from cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GlobalGridTest.java rename to cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridTest.java index 73ec87a86..3733fc834 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GlobalGridTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/CimrGridTest.java @@ -9,9 +9,9 @@ import static org.junit.Assert.*; -public class GlobalGridTest { +public class CimrGridTest { - GlobalGrid grid; + CimrGrid grid; @Before public void setUp() { @@ -20,7 +20,7 @@ public void setUp() { -180.0, 90.0, 1.0, 1.0 ); - grid = new GlobalGrid(proj, 180, 360); + grid = new CimrGrid(proj, 180, 360); } @Test diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GeometryBandToGridMapperTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GeometryBandToGridMapperTest.java index 6e84ba654..3d85c3aa7 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GeometryBandToGridMapperTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/GeometryBandToGridMapperTest.java @@ -17,10 +17,10 @@ public void testMap_simpleCase_mapNearest() { -10.0, 81.0, 1.0, 1.0 ); - GlobalGrid grid = new GlobalGrid(projection, 4, 2); + CimrGrid grid = new CimrGrid(projection, 4, 2); CimrBand swath = new DummyCimrBand(); - GlobalGridBandDataSource target = GlobalGridBandDataSource.createEmpty(4, 2); + CimrGridBandDataSource target = CimrGridBandDataSource.createEmpty(4, 2); GeometryBandToGridMapper mapper = new GeometryBandToGridMapper(); mapper.mapNearest(swath, grid, target); @@ -40,10 +40,10 @@ public void testMap_simpleCase_mapNearest() { @Test public void testMap_simpleCase_mapAverage() { PlateCarreeProjection proj = new PlateCarreeProjection(1, 1, -180, 90, 360, 180); - GlobalGrid grid = new GlobalGrid(proj, 1, 1); + CimrGrid grid = new CimrGrid(proj, 1, 1); CimrBand swath = new DummyCimrBand(); - GlobalGridBandDataSource target = GlobalGridBandDataSource.createEmpty(1, 1); + CimrGridBandDataSource target = CimrGridBandDataSource.createEmpty(1, 1); GeometryBandToGridMapper mapper = new GeometryBandToGridMapper(); mapper.mapAverage(swath, grid, target); diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyCrsGeoCodingTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyCrsGeoCodingTest.java index 36e847ee2..9443d551f 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyCrsGeoCodingTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyCrsGeoCodingTest.java @@ -14,7 +14,7 @@ public class LazyCrsGeoCodingTest { @Test public void test_canGetFlagsAndIsGlobalDoNotInitDelegate() throws Exception { - GlobalGrid grid = mock(GlobalGrid.class); + CimrGrid grid = mock(CimrGrid.class); LazyCrsGeoCoding gc = new LazyCrsGeoCoding(grid); @@ -28,7 +28,7 @@ public void test_canGetFlagsAndIsGlobalDoNotInitDelegate() throws Exception { @Test public void test_delegateIsCreatedLazilyAndReusedForAllDelegatingMethods() throws Exception { - GlobalGrid grid = GlobalGridFactory.createGlobalPlateCarree(1.0); // ggf. Aufruf anpassen + CimrGrid grid = CimrGridFactory.createGlobalPlateCarree(1.0); LazyCrsGeoCoding gc = new LazyCrsGeoCoding(grid); assertNull(getField(gc, "delegate")); @@ -58,7 +58,7 @@ public void test_delegateIsCreatedLazilyAndReusedForAllDelegatingMethods() throw @Test public void wrapsDelegateCreationFailuresInRuntimeException() { - GlobalGrid badGrid = mock(GlobalGrid.class); + CimrGrid badGrid = mock(CimrGrid.class); when(badGrid.getWidth()).thenReturn(10); when(badGrid.getHeight()).thenReturn(10); when(badGrid.getProjection()).thenThrow(new RuntimeException("boom")); @@ -77,7 +77,7 @@ public void wrapsDelegateCreationFailuresInRuntimeException() { @Test(expected = IllegalStateException.class) public void cloneThrowsIllegalStateException() { - GlobalGrid grid = GlobalGridFactory.createGlobalPlateCarree(1.0); + CimrGrid grid = CimrGridFactory.createGlobalPlateCarree(1.0); LazyCrsGeoCoding gc = new LazyCrsGeoCoding(grid); gc.clone(); diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyGridBandDataSourceTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyGridBandDataSourceTest.java index 0e937bf9e..be7f42634 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyGridBandDataSourceTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/grid/LazyGridBandDataSourceTest.java @@ -23,7 +23,7 @@ public void testLazyInitializationAndGetSampleDelegation() { 1.0, 2.0, 3.0, 4.0 }; - GlobalGridBandDataSource delegate = new GlobalGridBandDataSource(2, 2, data); + CimrGridBandDataSource delegate = new CimrGridBandDataSource(2, 2, data); TestReaderContext context = new TestReaderContext(delegate); CimrBandDescriptor desc = createDummyDescriptor("test_band"); @@ -47,7 +47,7 @@ public void testSetSampleDelegationAndUseAverageFalse() { 0.0, 0.0, 0.0, 0.0 }; - GlobalGridBandDataSource delegate = new GlobalGridBandDataSource(2, 2, data); + CimrGridBandDataSource delegate = new CimrGridBandDataSource(2, 2, data); TestReaderContext context = new TestReaderContext(delegate); CimrBandDescriptor desc = createDummyDescriptor("test_band_2"); @@ -70,9 +70,9 @@ private static class TestReaderContext extends CimrReaderContext { int callCount = 0; CimrBandDescriptor lastDescriptor; boolean lastUseAverage; - private final GlobalGridBandDataSource delegate; + private final CimrGridBandDataSource delegate; - TestReaderContext(GlobalGridBandDataSource delegate) { + TestReaderContext(CimrGridBandDataSource delegate) { super((NetcdfFile) null, new CimrDescriptorSet(null, null, null), null, null, null); @@ -80,7 +80,7 @@ private static class TestReaderContext extends CimrReaderContext { } @Override - public GlobalGridBandDataSource getOrCreateGridForVariable(CimrBandDescriptor descriptor, boolean useAverage) { + public CimrGridBandDataSource getOrCreateGridForVariable(CimrBandDescriptor descriptor, boolean useAverage) { callCount++; lastDescriptor = descriptor; lastUseAverage = useAverage; diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactoryTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactoryTest.java index 05ab68d51..cb5eb765b 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactoryTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrGeometryFactoryTest.java @@ -5,6 +5,7 @@ import eu.esa.snap.cimr.cimr.CimrDimensions; import eu.esa.snap.cimr.cimr.CimrFrequencyBand; import eu.esa.snap.cimr.cimr.CimrDescriptorKind; +import eu.esa.snap.cimr.grid.CimrBoundingBox; import eu.esa.snap.cimr.grid.CimrGeometry; import org.esa.snap.core.datamodel.GeoPos; import org.junit.Test; @@ -377,4 +378,85 @@ public void testGetOrCreateGeometry_UsesVariableFeedIndex() throws IOException, assertEquals(10.0, p.getLat(), doubleErr); assertEquals(20.0, p.getLon(), doubleErr); } + + + @Test + public void testGetBoundingBox() throws IOException, InvalidRangeException { + int nScans = 2; + int nTiepoints = 2; + int nFeeds = 2; + int nSamplesOut = 4; + + Dimension scanDim = new Dimension("n_scans", nScans); + Dimension tpDim = new Dimension("n_tiepoints_C_BAND", nTiepoints); + Dimension feedDim = new Dimension("n_feeds_C_BAND", nFeeds); + Dimension samplesDim = new Dimension("n_samples_C_BAND", nSamplesOut); + + Group.Builder rootBuilder = Group.builder(null).setName("root"); + rootBuilder.addDimension(scanDim) + .addDimension(tpDim) + .addDimension(feedDim) + .addDimension(samplesDim); + + ArrayDouble.D3 latData = new ArrayDouble.D3(nScans, nTiepoints, nFeeds); + ArrayDouble.D3 lonData = new ArrayDouble.D3(nScans, nTiepoints, nFeeds); + latData.set(0, 0, 0, 1.0); + lonData.set(0, 0, 0, 2.0); + latData.set(0, 0, 1, 10.0); + lonData.set(0, 0, 1, 20.0); + + Variable.Builder latBuilder = Variable.builder() + .setName("lat_c") + .setDataType(DataType.DOUBLE) + .setDimensionsByName("n_scans n_tiepoints_C_BAND n_feeds_C_BAND") + .setCachedData(latData, false); + Variable.Builder lonBuilder = Variable.builder() + .setName("lon_c") + .setDataType(DataType.DOUBLE) + .setDimensionsByName("n_scans n_tiepoints_C_BAND n_feeds_C_BAND") + .setCachedData(lonData, false); + + rootBuilder.addVariable(latBuilder); + rootBuilder.addVariable(lonBuilder); + + NetcdfFile ncFile = NetcdfFile.builder() + .setLocation("test") + .setRootGroup(rootBuilder) + .build(); + + CimrDimensions dims = CimrDimensions.from(ncFile); + String rootPath = "/"; + + CimrBandDescriptor latDesc = new CimrBandDescriptor( + "lat_c", "lat_c", CimrFrequencyBand.C_BAND, + new String[]{}, new String[] {}, + rootPath, 0, CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, + "double", "", "" + ); + CimrBandDescriptor lonDesc = new CimrBandDescriptor( + "lon_c", "lon_c", CimrFrequencyBand.C_BAND, + new String[]{}, new String[] {}, + rootPath, 0, CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_tiepoints_C_BAND", "n_feeds_C_BAND"}, + "double", "", "" + ); + + CimrBandDescriptor varDesc = new CimrBandDescriptor( + "altitude", "altitude", CimrFrequencyBand.C_BAND, + new String[]{"lat_c", "lon_c"}, new String[] {}, + rootPath, 1, CimrDescriptorKind.VARIABLE, + new String[]{"n_scans", "n_samples_C_BAND", "n_feeds_C_BAND"}, + "double", "", "" + ); + + NetcdfCimrGeometryFactory factory = new NetcdfCimrGeometryFactory(ncFile, Arrays.asList(latDesc, lonDesc), dims); + + CimrBoundingBox bBox = factory.getBoundingBox(varDesc, 0.02); + + assertEquals(-0.5, bBox.getLatMin(), doubleErr); + assertEquals(-0.5, bBox.getLonMin(), doubleErr); + assertEquals(10.5, bBox.getLatMax(), doubleErr); + assertEquals(20.5, bBox.getLonMax(), doubleErr); + } } \ No newline at end of file From 63347bc4ab6cd6c59413f1673cb452e63d630269 Mon Sep 17 00:00:00 2001 From: Benjamin Lutz Date: Fri, 5 Dec 2025 17:05:11 +0100 Subject: [PATCH 10/11] fixed issues with footprint caching --- .../snap/cimr/ui/CimrFootprintOverlay.java | 34 ++++++++----- .../eu/esa/snap/cimr/ui/CimrUIManager.java | 12 ++--- .../cimr/ui/CimrFootprintOverlayTest.java | 15 +++--- .../esa/snap/cimr/ui/CimrUIManagerTest.java | 51 ------------------- .../esa/snap/cimr/CimrL1BProductReader.java | 4 +- .../eu/esa/snap/cimr/CimrReaderContext.java | 19 ++++--- ...Footprint.java => CimrFootprintShape.java} | 10 +--- .../eu/esa/snap/cimr/cimr/CimrFootprints.java | 22 ++++++++ .../netcdf/NetcdfCimrFootprintFactory.java | 23 +++++++-- .../cimr/CimrReaderContextFootprintTest.java | 39 +++++++------- ...tTest.java => CimrFootprintShapeTest.java} | 16 +++--- .../NetcdfCimrFootprintFactoryTest.java | 43 +++++++++++++--- 12 files changed, 155 insertions(+), 133 deletions(-) delete mode 100644 cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrUIManagerTest.java rename cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/{CimrFootprint.java => CimrFootprintShape.java} (78%) create mode 100644 cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFootprints.java rename cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/{CimrFootprintTest.java => CimrFootprintShapeTest.java} (68%) diff --git a/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrFootprintOverlay.java b/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrFootprintOverlay.java index 0a42313d1..b1b5aeb21 100644 --- a/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrFootprintOverlay.java +++ b/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrFootprintOverlay.java @@ -3,7 +3,8 @@ import com.bc.ceres.glayer.swing.LayerCanvas; import com.bc.ceres.grender.Rendering; import com.bc.ceres.grender.Viewport; -import eu.esa.snap.cimr.cimr.CimrFootprint; +import eu.esa.snap.cimr.cimr.CimrFootprints; +import eu.esa.snap.cimr.cimr.CimrFootprintShape; import org.esa.snap.core.datamodel.ColorPaletteDef; import org.esa.snap.core.datamodel.ImageInfo; import org.esa.snap.core.datamodel.RasterDataNode; @@ -18,12 +19,12 @@ public class CimrFootprintOverlay implements LayerCanvas.Overlay { public static final CimrFootprintOverlay INSTANCE = new CimrFootprintOverlay(); - private List footprints; + private CimrFootprints footprints; private RasterDataNode raster; private CimrFootprintOverlay() {} - public void setFootprints(List footprints) { + public void setFootprints(CimrFootprints footprints) { this.footprints = footprints; } @@ -33,7 +34,7 @@ public void setRaster(RasterDataNode raster) { @Override public void paintOverlay(LayerCanvas canvas, Rendering rendering) { - if (footprints == null || footprints.isEmpty()) { + if (footprints == null || footprints.getShapes().isEmpty()) { return; } @@ -55,21 +56,25 @@ public void paintOverlay(LayerCanvas canvas, Rendering rendering) { fullPalette = ImageManager.createColorPalette(imageInfo); } - for (CimrFootprint fp : footprints) { - double cx = fp.getGeoPos().getLon(); - double cy = fp.getGeoPos().getLat(); - double rx = fp.getMajorAxisDegree(); - double ry = fp.getMinorAxisDegree(); + List shapes = footprints.getShapes(); + List values = footprints.getValues(); + + for (int ii = 0; ii < shapes.size(); ii++) { + CimrFootprintShape shape = shapes.get(ii); + double cx = shape.getGeoPos().getLon(); + double cy = shape.getGeoPos().getLat(); + double rx = shape.getMajorAxisDegree(); + double ry = shape.getMinorAxisDegree(); Ellipse2D modelEllipse = new Ellipse2D.Double(cx - rx, cy - ry, 2 * rx, 2 * ry); - double angleRad = Math.toRadians(fp.getAngle()); + double angleRad = Math.toRadians(shape.getAngle()); AffineTransform rotModel = AffineTransform.getRotateInstance(angleRad, cx, cy); Shape rotatedModelShape = rotModel.createTransformedShape(modelEllipse); Shape viewEllipse = m2vBase.createTransformedShape(rotatedModelShape); if (imageInfo != null && cpd != null) { - baseColor = getColorForValue(cpd, fullPalette, fp.getValue()); + baseColor = getColorForValue(cpd, fullPalette, values.get(ii)); } g.setColor(baseColor); @@ -85,11 +90,16 @@ private Color getColorForValue(ColorPaletteDef cpd, Color[] fullPalette, double double min = cpd.getMinDisplaySample(); double max = cpd.getMaxDisplaySample(); + if (Double.compare(min, max) == 0) { + Color c = cpd.getLastPoint().getColor(); + return new Color(c.getRed(), c.getGreen(), c.getBlue()); + } + double v = Math.max(min, Math.min(max, value)); double f = (v - min) / (max - min); int idx = (int) Math.round(f * (numColors - 1)); - idx = Math.max(0, Math.min(idx, numColors)); + idx = Math.max(0, Math.min(idx, numColors - 1)); return fullPalette[idx]; } diff --git a/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrUIManager.java b/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrUIManager.java index 01843f3c6..0bf84211f 100644 --- a/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrUIManager.java +++ b/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrUIManager.java @@ -7,7 +7,7 @@ import com.bc.ceres.glayer.support.ImageLayer; import com.bc.ceres.glayer.support.LayerUtils; import eu.esa.snap.cimr.CimrL1BProductReader; -import eu.esa.snap.cimr.cimr.CimrFootprint; +import eu.esa.snap.cimr.cimr.CimrFootprints; import org.esa.snap.core.dataio.ProductReader; import org.esa.snap.core.datamodel.RasterDataNode; import org.esa.snap.core.layer.WorldMapLayerType; @@ -16,7 +16,6 @@ import org.openide.modules.OnStart; import org.openide.windows.OnShowing; -import java.util.List; import java.util.logging.Logger; @@ -65,8 +64,8 @@ private static void handleSceneViewChange(ProductSceneView oldView, ProductScene if (cimrReader != null) { RasterDataNode raster = newView.getRaster(); String band = raster.getName(); - List fps = cimrReader.getFootprints(band); - if (!fps.isEmpty()) { + CimrFootprints fps = cimrReader.getFootprints(band); + if (!fps.getShapes().isEmpty()) { CimrFootprintOverlay.INSTANCE.setFootprints(fps); CimrFootprintOverlay.INSTANCE.setRaster(raster); newView.getLayerCanvas().addOverlay(CimrFootprintOverlay.INSTANCE); @@ -74,9 +73,8 @@ private static void handleSceneViewChange(ProductSceneView oldView, ProductScene } } } - - // TODO BL package private for testing, 12/25 - static CimrL1BProductReader getCimrReader(ProductSceneView view) { + + private static CimrL1BProductReader getCimrReader(ProductSceneView view) { RasterDataNode raster = view.getRaster(); if (raster == null) { return null; diff --git a/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrFootprintOverlayTest.java b/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrFootprintOverlayTest.java index d7a127afe..ba8b65b06 100644 --- a/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrFootprintOverlayTest.java +++ b/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrFootprintOverlayTest.java @@ -3,7 +3,8 @@ import com.bc.ceres.glayer.swing.LayerCanvas; import com.bc.ceres.grender.Rendering; import com.bc.ceres.grender.Viewport; -import eu.esa.snap.cimr.cimr.CimrFootprint; +import eu.esa.snap.cimr.cimr.CimrFootprintShape; +import eu.esa.snap.cimr.cimr.CimrFootprints; import org.esa.snap.core.datamodel.GeoPos; import org.esa.snap.core.datamodel.RasterDataNode; import org.junit.Test; @@ -11,7 +12,8 @@ import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; -import java.util.Collections; +import java.util.ArrayList; +import java.util.List; import static org.mockito.Mockito.*; @@ -23,10 +25,11 @@ public class CimrFootprintOverlayTest { public void testPaintOverlay_doesNotThrow() { CimrFootprintOverlay overlay = CimrFootprintOverlay.INSTANCE; - CimrFootprint fp = new CimrFootprint( - new GeoPos(10f, 20f), 30.0, 1000.0, 2000.0, 300.0 - ); - overlay.setFootprints(Collections.singletonList(fp)); + CimrFootprintShape shape = new CimrFootprintShape(new GeoPos(10f, 20f), 30.0, 1000.0, 2000.0); + ArrayList values = new ArrayList<>(); + values.add(1.0); + + overlay.setFootprints(new CimrFootprints(List.of(shape), values)); BufferedImage img = new BufferedImage(200, 200, BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = img.createGraphics(); diff --git a/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrUIManagerTest.java b/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrUIManagerTest.java deleted file mode 100644 index 3ddb3fb60..000000000 --- a/cimr-reader-ui/src/test/java/eu/esa/snap/cimr/ui/CimrUIManagerTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package eu.esa.snap.cimr.ui; - -import eu.esa.snap.cimr.CimrL1BProductReader; -import org.esa.snap.core.dataio.dimap.DimapProductReader; -import org.esa.snap.core.datamodel.RasterDataNode; -import org.esa.snap.ui.product.ProductSceneView; -import org.junit.Test; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - - -public class CimrUIManagerTest { - - - @Test - public void testGetCimrReader_returnsReaderOnlyForCimr() { - ProductSceneView view = mock(ProductSceneView.class); - RasterDataNode raster = mock(RasterDataNode.class); - CimrL1BProductReader cimrReader = mock(CimrL1BProductReader.class); - when(view.getRaster()).thenReturn(raster); - when(raster.getProductReader()).thenReturn(cimrReader); - - CimrL1BProductReader result = CimrUIManager.getCimrReader(view); - - assertSame(cimrReader, result); - } - - @Test - public void testGetCimrReader_returnsNullNoRaster() { - ProductSceneView view = mock(ProductSceneView.class); - when(view.getRaster()).thenReturn(null); - - CimrL1BProductReader result = CimrUIManager.getCimrReader(view); - - assertNull(result); - } - - @Test - public void testGetCimrReader_returnsNullNotCimrReader() { - ProductSceneView view = mock(ProductSceneView.class); - RasterDataNode raster = mock(RasterDataNode.class); - DimapProductReader reader = mock(DimapProductReader.class); - when(view.getRaster()).thenReturn(null); - when(raster.getProductReader()).thenReturn(reader); - - CimrL1BProductReader result = CimrUIManager.getCimrReader(view); - - assertNull(result); - } -} \ No newline at end of file diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java index 997d86584..7e5df7b2d 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrL1BProductReader.java @@ -78,13 +78,13 @@ public void close() throws IOException { super.close(); } - public List getFootprints(String name) { + public CimrFootprints getFootprints(String name) { CimrBandDescriptor desc = this.readerContext.getDescriptorSet().getMeasurementByName(name); if (desc == null) { desc = this.readerContext.getDescriptorSet().getTpVariableByName(name); } if (desc == null) { - return List.of(); + return new CimrFootprints( List.of(), List.of()); } return this.readerContext.getOrCreateFootprints(desc); } diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrReaderContext.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrReaderContext.java index 5f15313ab..795e12e84 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrReaderContext.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/CimrReaderContext.java @@ -1,9 +1,6 @@ package eu.esa.snap.cimr; -import eu.esa.snap.cimr.cimr.CimrBandDescriptor; -import eu.esa.snap.cimr.cimr.CimrDescriptorSet; -import eu.esa.snap.cimr.cimr.CimrFootprint; -import eu.esa.snap.cimr.cimr.CimrGridBuilder; +import eu.esa.snap.cimr.cimr.*; import eu.esa.snap.cimr.grid.*; import eu.esa.snap.cimr.netcdf.NetcdfCimrFootprintFactory; import eu.esa.snap.cimr.netcdf.NetcdfCimrGeometryFactory; @@ -28,7 +25,7 @@ public class CimrReaderContext { private final NetcdfCimrFootprintFactory footprintFactory; private final Map geometryBandCache = new ConcurrentHashMap<>(); - private final Map> footprintCache = new ConcurrentHashMap<>(); + private final Map> footprintCache = new ConcurrentHashMap<>(); public CimrReaderContext(NetcdfFile ncFile, @@ -84,9 +81,10 @@ private String getGeometryBandKey(CimrBandDescriptor varDesc) { return varDesc.getBand().name() + ":" + varDesc.getValueVarName() + ":" + varDesc.getFeedIndex(); } - public List getOrCreateFootprints(CimrBandDescriptor varDesc) { + public CimrFootprints getOrCreateFootprints(CimrBandDescriptor varDesc) { String key = getFootprintKey(varDesc); - return this.footprintCache.computeIfAbsent(key, d -> { + + List shapes = this.footprintCache.computeIfAbsent(key, d -> { CimrBandDescriptor minorAxisDesc = this.descriptorSet.getTpVariableByName(varDesc.getFootprintVars()[0]); CimrBandDescriptor majorAxisDesc = this.descriptorSet.getTpVariableByName(varDesc.getFootprintVars()[1]); CimrBandDescriptor angleDesc = this.descriptorSet.getTpVariableByName(varDesc.getFootprintVars()[2]); @@ -96,8 +94,13 @@ public List getOrCreateFootprints(CimrBandDescriptor varDesc) { CimrGeometryBand majorAxisBand = getOrCreateGeometryBand(majorAxisDesc); CimrGeometryBand angleBand = getOrCreateGeometryBand(angleDesc); - return footprintFactory.createFootprints(geometryBand, minorAxisBand, majorAxisBand, angleBand); + return footprintFactory.createFootprintShapes(geometryBand, minorAxisBand, majorAxisBand, angleBand); }); + + CimrGeometryBand geometryBand = getOrCreateGeometryBand(varDesc); + List values = this.footprintFactory.getFootprintValues(geometryBand); + + return new CimrFootprints(shapes, values); } private String getFootprintKey(CimrBandDescriptor varDesc) { diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFootprint.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFootprintShape.java similarity index 78% rename from cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFootprint.java rename to cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFootprintShape.java index 770a0c018..d5e4e2757 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFootprint.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFootprintShape.java @@ -2,20 +2,18 @@ import org.esa.snap.core.datamodel.GeoPos; -public class CimrFootprint { +public class CimrFootprintShape { GeoPos geoPos; double angle; // degree double minor_axis; double major_axis; - double value; - public CimrFootprint(GeoPos geoPos, double angle, double minor_axis, double major_axis, double value) { + public CimrFootprintShape(GeoPos geoPos, double angle, double minor_axis, double major_axis) { this.geoPos = geoPos; this.angle = angle; this.minor_axis = minor_axis; this.major_axis = major_axis; - this.value = value; } public GeoPos getGeoPos() { @@ -26,10 +24,6 @@ public double getAngle() { return angle; } - public double getValue() { - return value; - } - public double getMinorAxisDegree() { return metersToLatDeg(minor_axis); } diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFootprints.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFootprints.java new file mode 100644 index 000000000..0fca2444b --- /dev/null +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/cimr/CimrFootprints.java @@ -0,0 +1,22 @@ +package eu.esa.snap.cimr.cimr; + +import java.util.List; + +public class CimrFootprints { + + private final List shapes; + private final List values; + + public CimrFootprints(List shapes, List values) { + this.shapes = shapes; + this.values = values; + } + + public List getShapes() { + return shapes; + } + + public List getValues() { + return values; + } +} diff --git a/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrFootprintFactory.java b/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrFootprintFactory.java index ed48bddc7..89ffdc2e7 100644 --- a/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrFootprintFactory.java +++ b/cimr-reader/src/main/java/eu/esa/snap/cimr/netcdf/NetcdfCimrFootprintFactory.java @@ -1,6 +1,6 @@ package eu.esa.snap.cimr.netcdf; -import eu.esa.snap.cimr.cimr.CimrFootprint; +import eu.esa.snap.cimr.cimr.CimrFootprintShape; import eu.esa.snap.cimr.grid.CimrGeometryBand; import org.esa.snap.core.datamodel.GeoPos; @@ -11,8 +11,8 @@ public class NetcdfCimrFootprintFactory { - public List createFootprints(CimrGeometryBand geometryBand, CimrGeometryBand minorAxisBand, CimrGeometryBand majorAxisBand, CimrGeometryBand angleBand) { - List footprints = new ArrayList<>(); + public List createFootprintShapes(CimrGeometryBand geometryBand, CimrGeometryBand minorAxisBand, CimrGeometryBand majorAxisBand, CimrGeometryBand angleBand) { + List footprints = new ArrayList<>(); int scans = geometryBand.getScanCount(); int samples = geometryBand.getSampleCount(); @@ -22,11 +22,24 @@ public List createFootprints(CimrGeometryBand geometryBand, CimrG final double angle = angleBand.getValue(scanIndex, sampleIndex); final double minorAxis = minorAxisBand.getValue(scanIndex, sampleIndex); final double majorAxis = majorAxisBand.getValue(scanIndex, sampleIndex); - final double value = geometryBand.getValue(scanIndex, sampleIndex); - footprints.add( new CimrFootprint(pos, angle, minorAxis, majorAxis, value)); + footprints.add( new CimrFootprintShape(pos, angle, minorAxis, majorAxis)); } } return footprints; } + + public List getFootprintValues(CimrGeometryBand geometryBand) { + List values = new ArrayList<>(); + int scans = geometryBand.getScanCount(); + int samples = geometryBand.getSampleCount(); + + for (int scanIndex = 0; scanIndex < scans; scanIndex++) { + for (int sampleIndex = 0; sampleIndex < samples; sampleIndex++) { + values.add(geometryBand.getValue(scanIndex, sampleIndex)); + } + } + + return values; + } } diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextFootprintTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextFootprintTest.java index a27aa8b9f..ec39ea4cc 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextFootprintTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/CimrReaderContextFootprintTest.java @@ -1,9 +1,6 @@ package eu.esa.snap.cimr; -import eu.esa.snap.cimr.cimr.CimrBandDescriptor; -import eu.esa.snap.cimr.cimr.CimrDescriptorSet; -import eu.esa.snap.cimr.cimr.CimrFootprint; -import eu.esa.snap.cimr.cimr.CimrFrequencyBand; +import eu.esa.snap.cimr.cimr.*; import eu.esa.snap.cimr.grid.CimrGeometry; import eu.esa.snap.cimr.grid.CimrGeometryBand; import eu.esa.snap.cimr.grid.CimrGrid; @@ -133,15 +130,15 @@ public void setUp() throws Exception { @Test public void testGetOrCreateFootprints_createsFromDependenciesAndCaches() throws InvalidRangeException, IOException { - CimrFootprint dummyFp = new CimrFootprint(new GeoPos(10.f, 20.f), 45.0, 1000.0, 2000.0, 300.0); - List expectedList = Collections.singletonList(dummyFp); + CimrFootprintShape dummyFp = new CimrFootprintShape(new GeoPos(10.f, 20.f), 45.0, 1000.0, 2000.0); + List expectedList = Collections.singletonList(dummyFp); - when(footprintFactory.createFootprints(mainGeomBand, minorGeomBand, majorGeomBand, angleGeomBand)) + when(footprintFactory.createFootprintShapes(mainGeomBand, minorGeomBand, majorGeomBand, angleGeomBand)) .thenReturn(expectedList); - List first = context.getOrCreateFootprints(mainDesc); + CimrFootprints first = context.getOrCreateFootprints(mainDesc); - assertSame(expectedList, first); + assertSame(expectedList, first.getShapes()); verify(descriptorSet).getTpVariableByName("FOOT_MINOR"); verify(descriptorSet).getTpVariableByName("FOOT_MAJOR"); @@ -158,12 +155,16 @@ public void testGetOrCreateFootprints_createsFromDependenciesAndCaches() throws verify(bandFactory).createGeometryBand(angleDesc, angleGeom); verify(footprintFactory, times(1)) - .createFootprints(mainGeomBand, minorGeomBand, majorGeomBand, angleGeomBand); + .createFootprintShapes(mainGeomBand, minorGeomBand, majorGeomBand, angleGeomBand); - List second = context.getOrCreateFootprints(mainDesc); + CimrFootprints second = context.getOrCreateFootprints(mainDesc); - assertSame(first, second); - verify(footprintFactory, times(1)).createFootprints(mainGeomBand, minorGeomBand, majorGeomBand, angleGeomBand); + assertSame(first.getShapes(), second.getShapes()); + assertNotSame(first, second); + verify(footprintFactory, times(1)).createFootprintShapes(mainGeomBand, minorGeomBand, majorGeomBand, angleGeomBand); + verify(footprintFactory, times(2)).getFootprintValues(mainGeomBand); + + assertNotSame(first.getValues(), second.getValues()); } @Test @@ -173,18 +174,18 @@ public void testGetOrCreateFootprints_usesFootprintKeyBasedOnBandAndFeed() { when(otherDesc.getBand()).thenReturn(CimrFrequencyBand.C_BAND); when(otherDesc.getFeedIndex()).thenReturn(0); - CimrFootprint fp = new CimrFootprint(new GeoPos(0.f, 0.f), 0.0, 500.0, 1000.0, 250.0); - List expected = Collections.singletonList(fp); + CimrFootprintShape fp = new CimrFootprintShape(new GeoPos(0.f, 0.f), 0.0, 500.0, 1000.0); + List expected = Collections.singletonList(fp); - when(footprintFactory.createFootprints( + when(footprintFactory.createFootprintShapes( mainGeomBand, minorGeomBand, majorGeomBand, angleGeomBand)) .thenReturn(expected); - List list1 = context.getOrCreateFootprints(mainDesc); - List list2 = context.getOrCreateFootprints(otherDesc); + List list1 = context.getOrCreateFootprints(mainDesc).getShapes(); + List list2 = context.getOrCreateFootprints(otherDesc).getShapes(); assertSame(list1, list2); - verify(footprintFactory, times(1)).createFootprints(mainGeomBand, minorGeomBand, majorGeomBand, angleGeomBand); + verify(footprintFactory, times(1)).createFootprintShapes(mainGeomBand, minorGeomBand, majorGeomBand, angleGeomBand); } } diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrFootprintTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrFootprintShapeTest.java similarity index 68% rename from cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrFootprintTest.java rename to cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrFootprintShapeTest.java index ceea2d6d5..e89096e23 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrFootprintTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/cimr/CimrFootprintShapeTest.java @@ -6,7 +6,7 @@ import static org.junit.Assert.*; -public class CimrFootprintTest { +public class CimrFootprintShapeTest { private static final double doubleErr = 1e-9; @@ -16,30 +16,28 @@ public void testConstructorAndGetters() { double angle = 123.4; double minor = 2500.0; double major = 5000.0; - double value = 275.3; - CimrFootprint fp = new CimrFootprint(geoPos, angle, minor, major, value); + CimrFootprintShape fp = new CimrFootprintShape(geoPos, angle, minor, major); assertSame(geoPos, fp.getGeoPos()); assertEquals(angle, fp.getAngle(), doubleErr); - assertEquals(value, fp.getValue(), doubleErr); } @Test public void testMinorAxisToDegree() { GeoPos geoPos = new GeoPos(0.0f, 0.0f); - CimrFootprint fp = new CimrFootprint(geoPos, 0.0, 111_320.0, 0.0, 0.0); + CimrFootprintShape fp = new CimrFootprintShape(geoPos, 0.0, 111_320.0, 0.0); assertEquals(1.0, fp.getMinorAxisDegree(), doubleErr); - CimrFootprint fpHalf = new CimrFootprint(geoPos, 0.0, 55_660.0, 0.0, 0.0); + CimrFootprintShape fpHalf = new CimrFootprintShape(geoPos, 0.0, 55_660.0, 0.0); assertEquals(0.5, fpHalf.getMinorAxisDegree(), doubleErr); } @Test public void testMajorAxisToDegreeAtEquator() { GeoPos geoPos = new GeoPos(0.0f, 10.0f); - CimrFootprint fp = new CimrFootprint(geoPos, 0.0, 0.0, 111_320.0, 0.0); + CimrFootprintShape fp = new CimrFootprintShape(geoPos, 0.0, 0.0, 111_320.0); assertEquals(1.0, fp.getMajorAxisDegree(), doubleErr); } @@ -47,7 +45,7 @@ public void testMajorAxisToDegreeAtEquator() { @Test public void testMajorAxisToDegreeAtMidLatitude() { GeoPos geoPos = new GeoPos(60.0f, 10.0f); - CimrFootprint fp = new CimrFootprint(geoPos, 0.0, 0.0, 55_660.0, 0.0); + CimrFootprintShape fp = new CimrFootprintShape(geoPos, 0.0, 0.0, 55_660.0); assertEquals(1.0, fp.getMajorAxisDegree(), doubleErr); } @@ -55,7 +53,7 @@ public void testMajorAxisToDegreeAtMidLatitude() { @Test public void testMajorAxisToDegreeAtNegativeLatitude() { GeoPos geoPos = new GeoPos(-60.0f, 10.0f); - CimrFootprint fp = new CimrFootprint(geoPos, 0.0, 0.0, 55_660.0, 0.0); + CimrFootprintShape fp = new CimrFootprintShape(geoPos, 0.0, 0.0, 55_660.0); assertEquals(1.0, fp.getMajorAxisDegree(), doubleErr); } diff --git a/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrFootprintFactoryTest.java b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrFootprintFactoryTest.java index bdd919c51..e71a81fc4 100644 --- a/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrFootprintFactoryTest.java +++ b/cimr-reader/src/test/java/eu/esa/snap/cimr/netcdf/NetcdfCimrFootprintFactoryTest.java @@ -1,6 +1,6 @@ package eu.esa.snap.cimr.netcdf; -import eu.esa.snap.cimr.cimr.CimrFootprint; +import eu.esa.snap.cimr.cimr.CimrFootprintShape; import eu.esa.snap.cimr.grid.CimrGeometryBand; import org.esa.snap.core.datamodel.GeoPos; import org.junit.Test; @@ -63,7 +63,7 @@ public void testCreateFootprints_populatesAllScanSampleCombinations() { NetcdfCimrFootprintFactory factory = new NetcdfCimrFootprintFactory(); - List footprints = factory.createFootprints( + List footprints = factory.createFootprintShapes( geometryBand, minorAxisBand, majorAxisBand, angleBand); @@ -72,18 +72,16 @@ public void testCreateFootprints_populatesAllScanSampleCombinations() { int idx = 0; for (int s = 0; s < scans; s++) { for (int t = 0; t < samples; t++) { - CimrFootprint fp = footprints.get(idx++); + CimrFootprintShape fp = footprints.get(idx++); assertEquals(10.0 + s, fp.getGeoPos().getLat(), EPS); assertEquals(20.0 + t, fp.getGeoPos().getLon(), EPS); double expectedAngle = 1.0 * s + 0.1 * t; double expectedMinorAxis = 1000.0 + s + t; - double expectedValue = 100.0 + 10.0 * s + t; assertEquals(expectedAngle, fp.getAngle(), EPS); assertEquals(expectedMinorAxis, fp.getMinorAxisDegree() * 111320.0, 1e-6 * 111320.0); - assertEquals(expectedValue, fp.getValue(), EPS); } } } @@ -101,10 +99,43 @@ public void testCreateFootprints_emptyGeometryBandReturnsEmptyList() { NetcdfCimrFootprintFactory factory = new NetcdfCimrFootprintFactory(); - List footprints = factory.createFootprints( + List footprints = factory.createFootprintShapes( geometryBand, minorAxisBand, majorAxisBand, angleBand); assertNotNull(footprints); assertTrue(footprints.isEmpty()); } + + @Test + public void testgetFootprintValues() { + CimrGeometryBand geometryBand = mock(CimrGeometryBand.class); + + int scans = 2; + int samples = 3; + + when(geometryBand.getScanCount()).thenReturn(scans); + when(geometryBand.getSampleCount()).thenReturn(samples); + + when(geometryBand.getValue(anyInt(), anyInt())).thenAnswer(inv -> { + int s = inv.getArgument(0); + int t = inv.getArgument(1); + return 100.0 + 10.0 * s + t; + }); + + NetcdfCimrFootprintFactory factory = new NetcdfCimrFootprintFactory(); + + List values = factory.getFootprintValues(geometryBand); + + assertEquals(scans * samples, values.size()); + + int idx = 0; + for (int s = 0; s < scans; s++) { + for (int t = 0; t < samples; t++) { + double value = values.get(idx++); + + double expectedValue = 100.0 + 10.0 * s + t; + assertEquals(expectedValue, value, EPS); + } + } + } } \ No newline at end of file From b464605560503c0ffab53695a45c08730dd72b2d Mon Sep 17 00:00:00 2001 From: Benjamin Lutz Date: Fri, 5 Dec 2025 17:12:44 +0100 Subject: [PATCH 11/11] remove ellipses temporarily --- .../eu/esa/snap/cimr/ui/CimrUIManager.java | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrUIManager.java b/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrUIManager.java index 0bf84211f..8bbd4d45c 100644 --- a/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrUIManager.java +++ b/cimr-reader-ui/src/main/java/eu/esa/snap/cimr/ui/CimrUIManager.java @@ -46,9 +46,9 @@ public void run() { } private static void handleSceneViewChange(ProductSceneView oldView, ProductSceneView newView) { - if (oldView != null) { - oldView.getLayerCanvas().removeOverlay(CimrFootprintOverlay.INSTANCE); - } +// if (oldView != null) { +// oldView.getLayerCanvas().removeOverlay(CimrFootprintOverlay.INSTANCE); +// } if (newView != null) { // add worldmap layer Layer worldMap = findWorldMapLayer(newView); @@ -59,33 +59,33 @@ private static void handleSceneViewChange(ProductSceneView oldView, ProductScene } worldMap.setVisible(true); - // add footprints - CimrL1BProductReader cimrReader = getCimrReader(newView); - if (cimrReader != null) { - RasterDataNode raster = newView.getRaster(); - String band = raster.getName(); - CimrFootprints fps = cimrReader.getFootprints(band); - if (!fps.getShapes().isEmpty()) { - CimrFootprintOverlay.INSTANCE.setFootprints(fps); - CimrFootprintOverlay.INSTANCE.setRaster(raster); - newView.getLayerCanvas().addOverlay(CimrFootprintOverlay.INSTANCE); - } - } - } - } - - private static CimrL1BProductReader getCimrReader(ProductSceneView view) { - RasterDataNode raster = view.getRaster(); - if (raster == null) { - return null; - } - ProductReader reader = raster.getProductReader(); - if (reader instanceof CimrL1BProductReader) { - return (CimrL1BProductReader) reader; +// // add footprints +// CimrL1BProductReader cimrReader = getCimrReader(newView); +// if (cimrReader != null) { +// RasterDataNode raster = newView.getRaster(); +// String band = raster.getName(); +// CimrFootprints fps = cimrReader.getFootprints(band); +// if (!fps.getShapes().isEmpty()) { +// CimrFootprintOverlay.INSTANCE.setFootprints(fps); +// CimrFootprintOverlay.INSTANCE.setRaster(raster); +// newView.getLayerCanvas().addOverlay(CimrFootprintOverlay.INSTANCE); +// } +// } } - return null; } +// private static CimrL1BProductReader getCimrReader(ProductSceneView view) { +// RasterDataNode raster = view.getRaster(); +// if (raster == null) { +// return null; +// } +// ProductReader reader = raster.getProductReader(); +// if (reader instanceof CimrL1BProductReader) { +// return (CimrL1BProductReader) reader; +// } +// return null; +// } + private static Layer findWorldMapLayer(ProductSceneView view) { return LayerUtils.getChildLayer(view.getRootLayer(), LayerUtils.SearchMode.DEEP, layer -> layer.getLayerType() instanceof WorldMapLayerType);