Skip to content

Commit 8c2a51c

Browse files
committed
feat: standardize NODATA values to Float.NaN
1 parent 46dc465 commit 8c2a51c

3 files changed

Lines changed: 73 additions & 8 deletions

File tree

mago-terrainer/src/main/java/com/gaia3d/terrain/tile/raster/TerrainRasterFormat.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public final class TerrainRasterFormat {
1212
public static final int VERSION = 1;
1313
public static final int HEADER_SIZE_BYTES = 64;
1414
public static final int FLOAT_BYTES = Float.BYTES;
15+
public static final float NO_DATA_VALUE = Float.NaN;
1516
public static final ByteOrder BYTE_ORDER = ByteOrder.LITTLE_ENDIAN;
1617

1718
}

mago-terrainer/src/main/java/com/gaia3d/terrain/tile/raster/TerrainRasterWriter.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,19 @@ public void write(Path path, GridCoverage2D coverage) throws IOException {
3131
Raster raster = coverage.getRenderedImage().getData();
3232
int width = raster.getWidth();
3333
int height = raster.getHeight();
34-
float sourceNoDataValue = resolveNoDataValue(coverage);
35-
float noDataValue = (float) GlobalOptions.getInstance().getNoDataValue();
34+
double sourceNoDataValue = resolveNoDataValue(coverage);
35+
double configuredNoDataValue = GlobalOptions.getInstance().getNoDataValue();
36+
float noDataValue = TerrainRasterFormat.NO_DATA_VALUE;
3637
float[] elevations = new float[Math.multiplyExact(width, height)];
3738
int index = 0;
3839
int maxX = raster.getMinX() + width;
3940
int maxY = raster.getMinY() + height;
4041
for (int y = raster.getMinY(); y < maxY; y++) {
4142
for (int x = raster.getMinX(); x < maxX; x++) {
4243
double sample = raster.getSampleDouble(x, y, 0);
43-
elevations[index++] = isNoData(sample, sourceNoDataValue) ? noDataValue : (float) sample;
44+
float elevation = (float) sample;
45+
elevations[index++] = isNoData(sample, sourceNoDataValue, configuredNoDataValue) || !Float.isFinite(elevation)
46+
? noDataValue : elevation;
4447
}
4548
}
4649

@@ -51,14 +54,19 @@ public void write(Path path, GridCoverage2D coverage) throws IOException {
5154
noDataValue, elevations));
5255
}
5356

54-
private float resolveNoDataValue(GridCoverage2D coverage) {
57+
private double resolveNoDataValue(GridCoverage2D coverage) {
5558
NoDataContainer noData = CoverageUtilities.getNoDataProperty(coverage);
56-
return noData == null ? Float.NaN : (float) noData.getAsSingleValue();
59+
return noData == null ? Double.NaN : noData.getAsSingleValue();
5760
}
5861

59-
private boolean isNoData(double sample, float sourceNoDataValue) {
62+
private boolean isNoData(double sample, double sourceNoDataValue, double configuredNoDataValue) {
6063
return !Double.isFinite(sample)
61-
|| (!Float.isNaN(sourceNoDataValue) && Double.compare(sample, sourceNoDataValue) == 0);
64+
|| matchesNoData(sample, sourceNoDataValue)
65+
|| matchesNoData(sample, configuredNoDataValue);
66+
}
67+
68+
private boolean matchesNoData(double sample, double noDataValue) {
69+
return !Double.isNaN(noDataValue) && Double.compare(sample, noDataValue) == 0;
6270
}
6371

6472
private ByteBuffer createHeader(TerrainRasterData data) {

mago-terrainer/src/test/java/com/gaia3d/terrain/tile/raster/TerrainRasterReaderWriterTest.java

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.gaia3d.terrain.tile.raster;
22

3+
import com.gaia3d.command.GlobalOptions;
34
import com.gaia3d.terrain.tile.core.*;
45
import com.gaia3d.terrain.tile.elevation.*;
56
import com.gaia3d.terrain.tile.generation.*;
@@ -8,16 +9,56 @@
89

910
import org.junit.jupiter.api.Tag;
1011
import org.junit.jupiter.api.Test;
11-
12+
import org.eclipse.imagen.PlanarImage;
13+
import org.eclipse.imagen.RasterFactory;
14+
import org.eclipse.imagen.TiledImage;
15+
import org.eclipse.imagen.media.range.NoDataContainer;
16+
import org.geotools.coverage.grid.GridCoverage2D;
17+
import org.geotools.coverage.grid.GridCoverageFactory;
18+
import org.geotools.coverage.util.CoverageUtilities;
19+
import org.geotools.geometry.jts.ReferencedEnvelope;
20+
import org.geotools.referencing.crs.DefaultGeographicCRS;
21+
22+
import java.awt.image.DataBuffer;
23+
import java.awt.image.WritableRaster;
1224
import java.io.IOException;
1325
import java.nio.ByteBuffer;
1426
import java.nio.file.Files;
1527
import java.nio.file.Path;
28+
import java.util.HashMap;
29+
import java.util.Map;
1630

1731
import static org.junit.jupiter.api.Assertions.*;
1832

1933
class TerrainRasterReaderWriterTest {
2034

35+
@Test
36+
@Tag("default")
37+
void coverageWriterPreservesDoubleNoDataMarkerForComparison() throws Exception {
38+
Path tempDir = Files.createTempDirectory("terrain-raster-double-nodata-");
39+
Path rasterPath = tempDir.resolve("tile" + TerrainRasterFormat.EXTENSION);
40+
GlobalOptions globalOptions = GlobalOptions.getInstance();
41+
double previousNoData = globalOptions.getNoDataValue();
42+
globalOptions.setNoDataValue(-9999.0);
43+
GridCoverage2D coverage = createDoubleCoverage(
44+
new double[]{-Double.MAX_VALUE, -9999.0, Double.NaN, 123.5}, -Double.MAX_VALUE);
45+
46+
try {
47+
new TerrainRasterWriter().write(rasterPath, coverage);
48+
TerrainRasterData read = new TerrainRasterReader().read(rasterPath);
49+
50+
assertTrue(Float.isNaN(read.noDataValue()));
51+
assertTrue(Float.isNaN(read.getElevation(0, 0)));
52+
assertTrue(Float.isNaN(read.getElevation(1, 0)));
53+
assertTrue(Float.isNaN(read.getElevation(2, 0)));
54+
assertEquals(123.5f, read.getElevation(3, 0));
55+
} finally {
56+
coverage.dispose(true);
57+
globalOptions.setNoDataValue(previousNoData);
58+
deleteRecursively(tempDir);
59+
}
60+
}
61+
2162
@Test
2263
@Tag("default")
2364
void writerAndReaderRoundTripFloat32TerrainRaster() throws Exception {
@@ -108,6 +149,21 @@ void publicRasterDataConstructorRetainsDefensiveCopy() {
108149
assertEquals(10.0f, data.getElevation(0, 0));
109150
}
110151

152+
private GridCoverage2D createDoubleCoverage(double[] values, double noDataValue) {
153+
WritableRaster raster = RasterFactory.createBandedRaster(DataBuffer.TYPE_DOUBLE, values.length, 1, 1, null);
154+
for (int x = 0; x < values.length; x++) {
155+
raster.setSample(x, 0, 0, values[x]);
156+
}
157+
158+
TiledImage image = new TiledImage(0, 0, values.length, 1, 0, 0,
159+
raster.getSampleModel(), PlanarImage.createColorModel(raster.getSampleModel()));
160+
image.setData(raster);
161+
Map<String, Object> properties = new HashMap<>();
162+
CoverageUtilities.setNoDataProperty(properties, new NoDataContainer(noDataValue));
163+
ReferencedEnvelope envelope = new ReferencedEnvelope(0.0, values.length, 0.0, 1.0, DefaultGeographicCRS.WGS84);
164+
return new GridCoverageFactory().create("double-dem", image, envelope, null, null, properties);
165+
}
166+
111167
private void deleteRecursively(Path path) throws IOException {
112168
if (!Files.exists(path)) {
113169
return;

0 commit comments

Comments
 (0)