Skip to content

Commit 6b091b7

Browse files
WeatherGodtdrwenski
authored andcommitted
Simplify geotiff metadata writing
1 parent 38397c7 commit 6b091b7

3 files changed

Lines changed: 106 additions & 54 deletions

File tree

cdm/misc/src/main/java/ucar/nc2/geotiff/GeotiffWriter.java

Lines changed: 41 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -323,63 +323,50 @@ private void writeMetadata(boolean greyScale, double xStart, double yStart, doub
323323
geotiff.addTag(new IFDEntry(Tag.Software, FieldType.ASCII).setValue("nc2geotiff"));
324324
geotiff.addTag(new IFDEntry(Tag.PlanarConfiguration, FieldType.SHORT).setValue(1));
325325

326-
if (greyScale) {
327-
// standard tags for Greyscale images ( see TIFF spec, section 4)
328-
geotiff.addTag(new IFDEntry(Tag.BitsPerSample, FieldType.SHORT).setValue(8)); // 8 bits per sample
329-
geotiff.addTag(new IFDEntry(Tag.SamplesPerPixel, FieldType.SHORT).setValue(1));
330-
331-
geotiff.addTag(new IFDEntry(Tag.XResolution, FieldType.RATIONAL).setValue(1, 1));
332-
geotiff.addTag(new IFDEntry(Tag.YResolution, FieldType.RATIONAL).setValue(1, 1));
333-
geotiff.addTag(new IFDEntry(Tag.ResolutionUnit, FieldType.SHORT).setValue(1));
334-
// black is zero (value used in GeotiffWriter)
335-
geotiff.addTag(new IFDEntry(Tag.PhotometricInterpretation, FieldType.SHORT).setValue(1));
326+
// standard tags for Greyscale images ( see TIFF spec, section 4)
327+
geotiff.addTag(new IFDEntry(Tag.BitsPerSample, FieldType.SHORT).setValue(elemSize * 8));
328+
geotiff.addTag(new IFDEntry(Tag.SamplesPerPixel, FieldType.SHORT).setValue(1));
329+
330+
geotiff.addTag(new IFDEntry(Tag.XResolution, FieldType.RATIONAL).setValue(1, 1));
331+
geotiff.addTag(new IFDEntry(Tag.YResolution, FieldType.RATIONAL).setValue(1, 1));
332+
geotiff.addTag(new IFDEntry(Tag.ResolutionUnit, FieldType.SHORT).setValue(1));
333+
334+
if (colorTable != null && colorTable.length > 0) {
335+
// standard tags for Palette-color images ( see TIFF spec, section 5)
336+
geotiff.addTag(new IFDEntry(Tag.PhotometricInterpretation, FieldType.SHORT).setValue(3));
337+
geotiff.addTag(new IFDEntry(Tag.ColorMap, FieldType.SHORT, colorTable.length).setValue(colorTable));
336338
} else {
337-
if (colorTable != null && colorTable.length > 0) {
338-
// standard tags for Palette-color images ( see TIFF spec, section 5)
339-
geotiff.addTag(new IFDEntry(Tag.PhotometricInterpretation, FieldType.SHORT).setValue(3));
340-
geotiff.addTag(new IFDEntry(Tag.ColorMap, FieldType.SHORT, colorTable.length).setValue(colorTable));
339+
geotiff.addTag(new IFDEntry(Tag.PhotometricInterpretation, FieldType.SHORT).setValue(1)); // black is zero
340+
}
341+
342+
// standard tags for SampleFormat ( see TIFF spec, section 19)
343+
if (dtype.isIntegral() && !greyScale) {
344+
geotiff.addTag(new IFDEntry(Tag.SampleFormat, FieldType.SHORT).setValue(dtype.isUnsigned() ? 1 : 2)); // UINT or
345+
// INT
346+
int min = (int) (dataMinMax.min);
347+
int max = (int) (dataMinMax.max);
348+
FieldType ftype;
349+
DataType sdtype = dtype.withSignedness(DataType.Signedness.SIGNED);
350+
if (sdtype == DataType.BYTE) {
351+
ftype = dtype.isUnsigned() ? FieldType.BYTE : FieldType.SBYTE;
352+
} else if (sdtype == DataType.SHORT) {
353+
ftype = dtype.isUnsigned() ? FieldType.SHORT : FieldType.SSHORT;
354+
} else if (sdtype == DataType.INT) {
355+
// A geotiff LONG/SLONG is really a 4-byte regular integer
356+
ftype = dtype.isUnsigned() ? FieldType.LONG : FieldType.SLONG;
341357
} else {
342-
geotiff.addTag(new IFDEntry(Tag.PhotometricInterpretation, FieldType.SHORT).setValue(1)); // black is zero
343-
}
344-
345-
geotiff.addTag(new IFDEntry(Tag.BitsPerSample, FieldType.SHORT).setValue(elemSize * 8));
346-
347-
// standard tags for SampleFormat ( see TIFF spec, section 19)
348-
if (dtype.isIntegral()) {
349-
geotiff.addTag(new IFDEntry(Tag.SampleFormat, FieldType.SHORT).setValue(dtype.isUnsigned() ? 1 : 2)); // UINT or
350-
// INT
351-
} else if (dtype.isFloatingPoint()) {
352-
geotiff.addTag(new IFDEntry(Tag.SampleFormat, FieldType.SHORT).setValue(3)); // IEEE Floating Point Type
353-
} else {
354-
throw new IllegalArgumentException("Unsupported data type for geotiff: " + dtype);
355-
}
356-
geotiff.addTag(new IFDEntry(Tag.SamplesPerPixel, FieldType.SHORT).setValue(1));
357-
358-
if (dtype.isFloatingPoint()) {
359-
float min = (float) (dataMinMax.min);
360-
float max = (float) (dataMinMax.max);
361-
geotiff.addTag(new IFDEntry(Tag.SMinSampleValue, FieldType.FLOAT).setValue(min));
362-
geotiff.addTag(new IFDEntry(Tag.SMaxSampleValue, FieldType.FLOAT).setValue(max));
363-
geotiff.addTag(new IFDEntry(Tag.GDALNoData, FieldType.ASCII).setValue(String.valueOf(min - 1.f)));
364-
} else if (dtype.isIntegral()) {
365-
int min = (int) (dataMinMax.min);
366-
int max = (int) (dataMinMax.max);
367-
FieldType ftype;
368-
DataType sdtype = dtype.withSignedness(DataType.Signedness.SIGNED);
369-
if (sdtype == DataType.BYTE) {
370-
ftype = dtype.isUnsigned() ? FieldType.BYTE : FieldType.SBYTE;
371-
} else if (sdtype == DataType.SHORT) {
372-
ftype = dtype.isUnsigned() ? FieldType.SHORT : FieldType.SSHORT;
373-
} else if (sdtype == DataType.INT) {
374-
// A geotiff LONG/SLONG is really a 4-byte regular integer
375-
ftype = dtype.isUnsigned() ? FieldType.LONG : FieldType.SLONG;
376-
} else {
377-
throw new IllegalArgumentException("Unsupported dtype: " + dtype);
378-
}
379-
geotiff.addTag(new IFDEntry(Tag.SMinSampleValue, ftype).setValue(min));
380-
geotiff.addTag(new IFDEntry(Tag.SMaxSampleValue, ftype).setValue(max));
381-
// No GDALNoData tag is set as it is ambiguous what would be appropriate here.
358+
throw new IllegalArgumentException("Unsupported dtype: " + dtype);
382359
}
360+
geotiff.addTag(new IFDEntry(Tag.SMinSampleValue, ftype).setValue(min));
361+
geotiff.addTag(new IFDEntry(Tag.SMaxSampleValue, ftype).setValue(max));
362+
// No GDALNoData tag is set as it is ambiguous what would be appropriate here.
363+
} else if (dtype.isFloatingPoint()) {
364+
geotiff.addTag(new IFDEntry(Tag.SampleFormat, FieldType.SHORT).setValue(3)); // IEEE Floating Point Type
365+
float min = (float) (dataMinMax.min);
366+
float max = (float) (dataMinMax.max);
367+
geotiff.addTag(new IFDEntry(Tag.SMinSampleValue, FieldType.FLOAT).setValue(min));
368+
geotiff.addTag(new IFDEntry(Tag.SMaxSampleValue, FieldType.FLOAT).setValue(max));
369+
geotiff.addTag(new IFDEntry(Tag.GDALNoData, FieldType.ASCII).setValue(String.valueOf(min - 1.f)));
383370
}
384371

385372
/*
52 Bytes
Binary file not shown.

cdm/misc/src/test/java/ucar/nc2/geotiff/TestGeoTiffPalette.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,4 +200,69 @@ public void testWritePalette() throws IOException {
200200
Assert.assertTrue(FileUtils.contentEquals(file1, file2));
201201
}
202202
}
203+
204+
@Test
205+
public void testWritePaletteGreyscale() throws IOException {
206+
// The "greyscale" mode is a misnomer. It really just means that it performs an
207+
// certain normalization on the data that can be represented as 1-255, which is
208+
// typically take as a greyscale, but there is no reason why a color table can't
209+
// be applied to it as well. This test exercises this.
210+
// Note that this test does not check the data, only the tags.
211+
String gridOut = tempFolder.newFile().getAbsolutePath();
212+
String baseline = "src/test/data/ucar/nc2/geotiff/baseline_palette.tif";
213+
logger.info("****geotiff palette write {}", gridOut);
214+
215+
HashMap<Integer, Color> colorMap =
216+
GeotiffWriter.createColorMap(new int[] {1, 2, 3, 4}, new String[] {"#00AAff", "#151412", "#DE01aB", "#100ABB"});
217+
int[] colorTable;
218+
219+
Array dtArray;
220+
try (GridDataset dataset = GridDataset.open("src/test/data/ucar/nc2/geotiff/categorical.nc")) {
221+
final GeoGrid grid = dataset.findGridByName("drought");
222+
assert grid != null;
223+
final GridCoordSystem gcs = grid.getCoordinateSystem();
224+
assert gcs != null;
225+
int rtindex = -1;
226+
int tindex = -1;
227+
CoordinateAxis1D timeAxis = gcs.getTimeAxis1D();
228+
assert timeAxis != null;
229+
tindex = (int) timeAxis.getSize() - 1; // last one
230+
dtArray = grid.readDataSlice(rtindex, -1, tindex, 0, -1, -1);
231+
232+
try (GeotiffWriter writer = new GeotiffWriter(gridOut)) {
233+
writer.setColorTable(colorMap, Color.black);
234+
writer.writeGrid(dataset, grid, dtArray, true, DataType.UBYTE);
235+
colorTable = writer.getColorTable();
236+
}
237+
238+
// read it back in to check the tags
239+
try (GeoTiff geotiff = new GeoTiff(gridOut)) {
240+
geotiff.read();
241+
logger.debug("{}", geotiff.showInfo());
242+
243+
IFDEntry photoTag = geotiff.findTag(Tag.PhotometricInterpretation);
244+
Assert.assertNotNull(photoTag);
245+
Assert.assertEquals(1, photoTag.count);
246+
Assert.assertEquals(3, photoTag.value[0]);
247+
248+
IFDEntry colorTableTag = geotiff.findTag(Tag.ColorMap);
249+
Assert.assertNotNull(colorTableTag);
250+
Assert.assertEquals(3 * 256, colorTableTag.count);
251+
Assert.assertArrayEquals(colorTable, colorTableTag.value);
252+
253+
// For backwards-compatibility, the min/max/nodata tags are not recorded.
254+
// There is no reason why this has to be the case.
255+
IFDEntry sMinTag = geotiff.findTag(Tag.SMinSampleValue);
256+
Assert.assertNull(sMinTag);
257+
IFDEntry sMaxTag = geotiff.findTag(Tag.SMaxSampleValue);
258+
Assert.assertNull(sMaxTag);
259+
260+
// When doing a color paletted geotiff, no assumption is made
261+
// about the NoData value and is therefore not encoded.
262+
IFDEntry noDataTag = geotiff.findTag(Tag.GDALNoData);
263+
Assert.assertNull(noDataTag);
264+
}
265+
}
266+
}
267+
203268
}

0 commit comments

Comments
 (0)