diff --git a/cdm/core/src/main/java/ucar/nc2/dataset/VariableDS.java b/cdm/core/src/main/java/ucar/nc2/dataset/VariableDS.java index 05f1630dec..80ba1af23b 100644 --- a/cdm/core/src/main/java/ucar/nc2/dataset/VariableDS.java +++ b/cdm/core/src/main/java/ucar/nc2/dataset/VariableDS.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata + * Copyright (c) 1998-2025 John Caron and University Corporation for Atmospheric Research/Unidata * See LICENSE for license information. */ package ucar.nc2.dataset; @@ -494,9 +494,28 @@ protected Array _read(Section section) throws IOException, InvalidRangeException // do not call directly @Override public Array reallyRead(Variable client, CancelTask cancelTask) throws IOException { - if (orgVar == null) + if (orgVar == null) { + // possible aggregation + if (this.proxyReader != null && this.proxyReader instanceof ucar.nc2.ncml.Aggregation) { + return this.proxyReader.reallyRead(client, cancelTask); + } return getMissingDataArray(shape); + } + if (client instanceof CoordinateAxis) { + // If orgVar is a VariableDS (orgVar of a CoordinateAxis using the non-builder + // API, for example), call reallyRead on it, otherwise, enhancements will get + // applied twice in a second _read() call triggered by calling read() on the + // VariableDS. + if (orgVar instanceof VariableDS && !orgVar.hasCachedData()) { + return orgVar.reallyRead(client, cancelTask); + } + // Some aggregations use a DatasetProxyReader for reading a coordinate axis + // variable, so check to see if that's what we have and, if so, use it. + if (ucar.nc2.ncml.Aggregation.instanceOfDatasetProxyReader(this.proxyReader)) { + return this.proxyReader.reallyRead(client, cancelTask); + } + } return orgVar.read(); } @@ -508,9 +527,28 @@ public Array reallyRead(Variable client, Section section, CancelTask cancelTask) if ((null == section) || section.computeSize() == getSize()) return reallyRead(client, cancelTask); - if (orgVar == null) - return getMissingDataArray(section.getShape()); + if (orgVar == null) { + // possible aggregation + if (this.proxyReader != null && this.proxyReader instanceof ucar.nc2.ncml.Aggregation) { + return this.proxyReader.reallyRead(client, section, cancelTask); + } + return getMissingDataArray(shape); + } + if (client instanceof CoordinateAxis) { + // If orgVar is a VariableDS (orgVar of a CoordinateAxis using the non-builder + // API, for example), call reallyRead on it, otherwise, enhancements will get + // applied twice in a second _read() call triggered by calling read() on the + // VariableDS. + if (orgVar instanceof VariableDS && !orgVar.hasCachedData()) { + return orgVar.reallyRead(client, section, cancelTask); + } + // Some aggregations use a DatasetProxyReader for reading a coordinate axis + // variable, so check to see if that's what we have and, if so, use it. + if (ucar.nc2.ncml.Aggregation.instanceOfDatasetProxyReader(this.proxyReader)) { + return this.proxyReader.reallyRead(client, section, cancelTask); + } + } return orgVar.read(section); } diff --git a/cdm/core/src/main/java/ucar/nc2/ncml/Aggregation.java b/cdm/core/src/main/java/ucar/nc2/ncml/Aggregation.java index 27b3ddd815..0cd8ba2005 100644 --- a/cdm/core/src/main/java/ucar/nc2/ncml/Aggregation.java +++ b/cdm/core/src/main/java/ucar/nc2/ncml/Aggregation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2020 John Caron and University Corporation for Atmospheric Research/Unidata + * Copyright (c) 1998-2025 John Caron and University Corporation for Atmospheric Research/Unidata * See LICENSE.txt for license information. */ package ucar.nc2.ncml; @@ -777,6 +777,11 @@ protected void setDatasetAcquireProxy(DatasetProxyReader proxy, Group g) throws } } + public static boolean instanceOfDatasetProxyReader(Object obj) { + if (obj == null) + return false; + return obj instanceof DatasetProxyReader; + } protected class DatasetProxyReader implements ProxyReader { Dataset dataset; diff --git a/cdm/core/src/test/data/scaledCoordAxis1D.nc4 b/cdm/core/src/test/data/scaledCoordAxis1D.nc4 new file mode 100644 index 0000000000..6e881907d2 Binary files /dev/null and b/cdm/core/src/test/data/scaledCoordAxis1D.nc4 differ diff --git a/cdm/core/src/test/java/ucar/nc2/dataset/TestScaledCoordAxis1D.java b/cdm/core/src/test/java/ucar/nc2/dataset/TestScaledCoordAxis1D.java new file mode 100644 index 0000000000..3430b6fb75 --- /dev/null +++ b/cdm/core/src/test/java/ucar/nc2/dataset/TestScaledCoordAxis1D.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 University Corporation for Atmospheric Research/Unidata + * See LICENSE.txt for license information. + */ +package ucar.nc2.dataset; + +import java.io.IOException; +import java.util.Objects; +import org.junit.Test; +import ucar.ma2.Array; +import ucar.nc2.Attribute; +import ucar.nc2.NetcdfFile; +import ucar.nc2.NetcdfFiles; +import ucar.nc2.Variable; +import ucar.nc2.constants.AxisType; +import ucar.unidata.util.test.TestDir; +import static com.google.common.truth.Truth.assertThat; + +public class TestScaledCoordAxis1D { + + @Test + public void testEnhancedOnce() throws IOException { + String testfile = TestDir.cdmLocalTestDataDir + "scaledCoordAxis1D.nc4"; + try (NetcdfFile ncf = NetcdfFile.open(testfile, null); NetcdfDataset ncd = NetcdfDataset.openDataset(testfile)) { + checkAxis(ncf, ncd, "x", AxisType.GeoX); + checkAxis(ncf, ncd, "y", AxisType.GeoY); + } + } + + @Test + public void testEnhancedOnceBuilders() throws IOException { + String testfile = TestDir.cdmLocalTestDataDir + "scaledCoordAxis1D.nc4"; + try (NetcdfFile ncf = NetcdfFiles.open(testfile, null); NetcdfDataset ncd = NetcdfDatasets.openDataset(testfile)) { + checkAxis(ncf, ncd, "x", AxisType.GeoX); + checkAxis(ncf, ncd, "y", AxisType.GeoY); + } + } + + public void checkAxis(NetcdfFile ncf, NetcdfDataset ncd, String axisName, AxisType axisType) throws IOException { + Variable var = ncf.findVariable(axisName); + assertThat(var != null).isTrue(); + Array vals = var.read(); + Attribute scaleAttr = var.findAttribute("scale_factor"); + assertThat(scaleAttr).isNotNull(); + float scale = Objects.requireNonNull(scaleAttr.getNumericValue()).floatValue(); + Attribute offsetAttr = var.findAttribute("add_offset"); + assertThat(offsetAttr).isNotNull(); + float offset = Objects.requireNonNull(offsetAttr.getNumericValue()).floatValue(); + assertThat(vals.getShape().length).isEqualTo(1); + int len = vals.getShape()[0]; + final float[] valsEnhanced = new float[len]; + for (int i = 0; i < len; i++) { + valsEnhanced[i] = vals.getShort(i) * scale + offset; + } + // vals have a scale and offset. + assertThat(vals).isNotNull(); + CoordinateAxis coodAxis = ncd.findCoordinateAxis(axisType); + Array coordVals = coodAxis.read(); + for (int i = 0; i < len; i++) { + assertThat(coordVals.getFloat(i)).isWithin(1e-6f).of(valsEnhanced[i]); + } + } +}