Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 52 additions & 3 deletions cdm-test/src/test/java/ucar/nc2/dt/grid/TestCFWriter2.java
Original file line number Diff line number Diff line change
@@ -1,37 +1,41 @@
/*
* Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
* Copyright (c) 1998-2025 University Corporation for Atmospheric Research/Unidata
* See LICENSE for license information.
*/

package ucar.nc2.dt.grid;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.TemporaryFolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ucar.nc2.Attribute;
import ucar.nc2.NetcdfFile;
import ucar.nc2.NetcdfFileWriter;
import ucar.nc2.NetcdfFiles;
import ucar.nc2.Variable;
import ucar.nc2.constants.CF;
import ucar.nc2.grib.collection.Grib;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarDateRange;
import ucar.nc2.util.DebugFlagsImpl;
import ucar.unidata.geoloc.LatLonPoint;
import ucar.unidata.geoloc.LatLonRect;
import ucar.unidata.geoloc.ProjectionPoint;
import ucar.unidata.geoloc.ProjectionPointImpl;
import ucar.unidata.geoloc.ProjectionRect;
import ucar.unidata.util.test.category.NeedsCdmUnitTest;
import ucar.unidata.util.test.TestDir;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertTrue;

/**
* Test CFGridWriter2
Expand Down Expand Up @@ -256,6 +260,51 @@ private void testFileSize(String fileIn, String gridNames, String startDate, Str
}
}

@Test
@Category(NeedsCdmUnitTest.class)
public void testAxisProjUnitMismatch() throws Exception {
String fileOut = tempFolder.newFile().getAbsolutePath();
String varName = "HNW";

final double falseEastingMeters = 400000.0;
final double falseEastingKm = falseEastingMeters / 1000;

try (GridDataset gds = GridDataset.open(TestDir.cdmUnitTestDir + "ncss/test/falseEastingNorthingScaleReset.nc4")) {
Variable x = gds.getNetcdfFile().findVariable("x");
assertNotNull(x);
Attribute xUnits = x.findAttribute(CF.UNITS);
assertNotNull(xUnits);
assertThat(xUnits.getStringValue()).isEqualTo("m");

Variable proj = gds.getNetcdfFile().findVariable("lambert_conformal_conic");
assertNotNull(proj);
Attribute feAttr = proj.findAttribute(CF.FALSE_EASTING);
assertNotNull(feAttr);
// false_easting in meters
assertThat(feAttr.getNumericValue()).isEqualTo(falseEastingMeters);

// setup subset
List<String> gridList = new ArrayList<>();
gridList.add(varName);

NetcdfFileWriter writer = NetcdfFileWriter.createNew(NetcdfFileWriter.Version.netcdf3, fileOut);

// write subset
CFGridWriter2.writeFile(gds, gridList, null, null, 1, null, null, 1, true, writer);
}

try (NetcdfFile ncf = NetcdfFiles.open(fileOut)) {
Variable proj = ncf.findVariable("lambert_conformal_conic");
assertNotNull(proj);
Attribute feAttr = proj.findAttribute(CF.FALSE_EASTING);
assertNotNull(feAttr);
// GeoX in km
Variable x = ncf.findVariable("x");
Attribute xUnits = x.findAttribute(CF.UNITS);
assertNotNull(xUnits);
assertThat(xUnits.getStringValue()).isEqualTo("km");
// false_easting in km
assertThat(feAttr.getNumericValue()).isEqualTo(falseEastingKm);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/*
* 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 for license information.
*/

package ucar.nc2.ft2.coverage.writer;

import com.google.common.base.Preconditions;
Expand All @@ -13,13 +14,15 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import ucar.ma2.Array;
import ucar.ma2.DataType;
import ucar.ma2.InvalidRangeException;
import ucar.ma2.Section;
import ucar.nc2.Attribute;
import ucar.nc2.AttributeContainer;
import ucar.nc2.AttributeContainerMutable;
import ucar.nc2.Dimension;
import ucar.nc2.Group;
import ucar.nc2.Variable;
Expand All @@ -28,6 +31,7 @@
import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.nc2.constants._Coordinate;
import ucar.nc2.dataset.transform.AbstractTransformBuilder;
import ucar.nc2.ft2.coverage.Coverage;
import ucar.nc2.ft2.coverage.CoverageCollection;
import ucar.nc2.ft2.coverage.CoverageCoordAxis;
Expand Down Expand Up @@ -308,8 +312,53 @@ private void addCoordTransforms(CoverageCollection subsetDataset, Group.Builder
// scalar coordinate transform variable - container for transform info
Variable.Builder ctv = Variable.builder().setName(ct.getName()).setDataType(DataType.INT);
group.addVariable(ctv);
ctv.addAttributes(ct.attributes());
AttributeContainer ctAttrs = ct.attributes();

AttributeContainerMutable newAttrs = AttributeContainerMutable.copyFrom(ctAttrs);
// adjust false_easting/false_northing if needed
// possibly needed if the subset dataset includes GeoX or GeoY axes, so first find those
Map<AxisType, CoverageCoordAxis> mapCoordAxes = subsetDataset.getCoordAxes().stream()
.filter(ca -> ca.getAxisType() == AxisType.GeoX || ca.getAxisType() == AxisType.GeoY)
.collect(Collectors.toMap(CoverageCoordAxis::getAxisType, mca -> mca));
// if we found GeoX and/or GeoY axes, start checking
if (!mapCoordAxes.isEmpty()) {
boolean eastScaled = false;
if (mapCoordAxes.containsKey(AxisType.GeoX)) {
eastScaled = scaleFalseEastingNorthing(CF.FALSE_EASTING, ctAttrs, newAttrs,
mapCoordAxes.get(AxisType.GeoX).attributes().findAttributeString(CF.UNITS, null));
}
boolean northScaled = false;
if (mapCoordAxes.containsKey(AxisType.GeoY)) {
northScaled = scaleFalseEastingNorthing(CF.FALSE_NORTHING, ctAttrs, newAttrs,
mapCoordAxes.get(AxisType.GeoY).attributes().findAttributeString(CF.UNITS, null));
}
// do not propagate the unit attribute on the projection variable if we ensured the
// units match, as this is state matchs CF (likely this attribute was added when creating
// a coverage from the NetcdfDataset.
if (!ctAttrs.findAttributeString(CF.UNITS, "").isEmpty()) {
if (eastScaled || northScaled) {
newAttrs.removeAttribute(CF.UNITS);
}
}
}
ctv.addAttributes(newAttrs.toImmutable());
}
}

private boolean scaleFalseEastingNorthing(String falseValueType, AttributeContainer cta,
AttributeContainerMutable newCta, String caUnits) {
double falseValue = cta.findAttributeDouble(falseValueType, Double.MIN_VALUE);
boolean scaled = false;
if (falseValue != Double.MIN_VALUE) {
double scalef = AbstractTransformBuilder.getFalseEastingScaleFactor(caUnits);
if (scalef != 1.0) {
scaled = true;
newCta.removeAttribute(falseValueType);
// here we divide by scalef, to undo the scalef applied when creating the Coverage
newCta.addAttribute(Attribute.builder(falseValueType).setNumericValue(falseValue / scalef, false).build());
}
}
return scaled;
}

private void addLatLon2D(CoverageCollection subsetDataset, Group.Builder group) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (c) 2025 University Corporation for Atmospheric Research/Unidata
* See LICENSE for license information.
*/

package ucar.nc2.ft2.coverage.writer;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertNotNull;

import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.TemporaryFolder;
import ucar.ma2.InvalidRangeException;
import ucar.nc2.Attribute;
import ucar.nc2.NetcdfFile;
import ucar.nc2.NetcdfFiles;
import ucar.nc2.Variable;
import ucar.nc2.constants.CF;
import ucar.nc2.constants.FeatureType;
import ucar.nc2.ft2.coverage.CoverageCollection;
import ucar.nc2.ft2.coverage.CoverageDatasetFactory;
import ucar.nc2.ft2.coverage.FeatureDatasetCoverage;
import ucar.nc2.write.NetcdfFileFormat;
import ucar.nc2.write.NetcdfFormatWriter;
import ucar.unidata.util.test.TestDir;
import ucar.unidata.util.test.category.NeedsCdmUnitTest;

public class TestCFGridCoverageWriter {

@Rule
public final TemporaryFolder tempFolder = new TemporaryFolder();

@Test
@Category(NeedsCdmUnitTest.class)
public void testCFGridCoverageWriterNonKmProjectionParams() throws IOException, InvalidRangeException {
String fileOut = tempFolder.newFile().getAbsolutePath();
String fileIn = TestDir.cdmUnitTestDir + "ncss/test/falseEastingNorthingScaleReset.nc4";

try (FeatureDatasetCoverage cc = CoverageDatasetFactory.open(fileIn)) {
CoverageCollection gcs = cc.findCoverageDataset(FeatureType.GRID);
assertNotNull(gcs);
NetcdfFormatWriter.Builder writerb =
NetcdfFormatWriter.builder().setNewFile(true).setFormat(NetcdfFileFormat.NETCDF3).setLocation(fileOut);

CFGridCoverageWriter.Result result = CFGridCoverageWriter.write(gcs, null, null, false, writerb, 0);
if (!result.wasWritten()) {
throw new InvalidRangeException("Error writing: " + result.getErrorMessage());
}
}

try (NetcdfFile ncf = NetcdfFiles.open(fileOut)) {
Variable proj = ncf.findVariable("lambert_conformal_conic");
assertNotNull(proj);
Attribute feAttr = proj.findAttribute(CF.FALSE_EASTING);
assertNotNull(feAttr);
Attribute fnAttr = proj.findAttribute(CF.FALSE_NORTHING);
assertNotNull(fnAttr);
// GeoX axis in meters
Variable x = ncf.findVariable("x");
assertNotNull(x);
assertThat(x.getUnitsString()).isEqualTo("m");
// false_easting in m
assertThat(feAttr.getNumericValue()).isEqualTo(400000.0);
assertThat(fnAttr.getNumericValue()).isEqualTo(400000.0);
// should not have a units attribute on the proj variable
Attribute unitsAttr = proj.findAttribute(CF.UNITS);
assertThat(unitsAttr).isNull();
}
}
}