1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.imaging.examples.tiff;
18
19 import java.awt.image.BufferedImage;
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.PrintStream;
23 import java.util.HashMap;
24 import java.util.List;
25
26 import javax.imageio.ImageIO;
27
28 import org.apache.commons.imaging.FormatCompliance;
29 import org.apache.commons.imaging.ImagingException;
30 import org.apache.commons.imaging.bytesource.ByteSource;
31 import org.apache.commons.imaging.formats.tiff.TiffContents;
32 import org.apache.commons.imaging.formats.tiff.TiffDirectory;
33 import org.apache.commons.imaging.formats.tiff.TiffField;
34 import org.apache.commons.imaging.formats.tiff.TiffImagingParameters;
35 import org.apache.commons.imaging.formats.tiff.TiffReader;
36 import org.apache.commons.imaging.formats.tiff.constants.GdalLibraryTagConstants;
37 import org.apache.commons.imaging.formats.tiff.constants.GeoTiffTagConstants;
38 import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
39 import org.apache.commons.lang3.StringUtils;
40
41
42
43
44
45 public class ReadTagsAndImages {
46
47
48
49
50
51 enum CoordinateTransformationCode {
52 TransverseMercator(1), TransvMercator_Modified_Alaska(2), ObliqueMercator(3), ObliqueMercator_Laborde(4), ObliqueMercator_Rosenmund(5),
53 ObliqueMercator_Spherical(6), Mercator(7), LambertConfConic_2SP(8), LambertConfConic_Helmert(9), LambertAzimEqualArea(10), AlbersEqualArea(11),
54 AzimuthalEquidistant(12), EquidistantConic(13), Stereographic(14), PolarStereographic(15), ObliqueStereographic(16), Equirectangular(17),
55 CassiniSoldner(18), Gnomonic(19), MillerCylindrical(20), Orthographic(21), Polyconic(22), Robinson(23), Sinusoidal(24), VanDerGrinten(25),
56 NewZealandMapGrid(26), TransvMercator_SouthOriented(27);
57
58
59
60
61
62
63
64 static CoordinateTransformationCode getValueForKey(final int key) {
65 for (final CoordinateTransformationCode v : values()) {
66 if (v.key == key) {
67 return v;
68 }
69 }
70 return null;
71 }
72
73 int key;
74
75 CoordinateTransformationCode(final int key) {
76 this.key = key;
77 }
78 }
79
80 enum GeoKey {
81
82 GTModelTypeGeoKey(1024),
83 GTRasterTypeGeoKey(1025),
84 GTCitationGeoKey(1026),
85
86
87 GeographicTypeGeoKey(2048),
88 GeogCitationGeoKey(2049),
89 GeogGeodeticDatumGeoKey(2050),
90 GeogPrimeMeridianGeoKey(2051),
91 GeogLinearUnitsGeoKey(2052),
92 GeogLinearUnitSizeGeoKey(2053),
93 GeogAngularUnitsGeoKey(2054),
94 GeogAngularUnitSizeGeoKey(2055),
95 GeogEllipsoidGeoKey(2056),
96 GeogSemiMajorAxisGeoKey(2057),
97 GeogSemiMinorAxisGeoKey(2058),
98 GeogInvFlatteningGeoKey(2059),
99 GeogAzimuthUnitsGeoKey(2060),
100 GeogPrimeMeridianLongGeoKey(2061),
101
102
103 ProjectedCRSGeoKey(3072),
104 PCSCitationGeoKey(3073),
105 ProjectionGeoKey(3074),
106 ProjCoordTransGeoKey(3075),
107 ProjLinearUnitsGeoKey(3076),
108 ProjLinearUnitSizeGeoKey(3077),
109 ProjStdParallel1GeoKey(3078),
110 ProjStdParallel2GeoKey(3079),
111 ProjNatOriginLongGeoKey(3080),
112 ProjNatOriginLatGeoKey(3081),
113 ProjFalseEastingGeoKey(3082),
114 ProjFalseNorthingGeoKey(3083),
115 ProjFalseOriginLongGeoKey(3084),
116 ProjFalseOriginLatGeoKey(3085),
117 ProjFalseOriginEastingGeoKey(3086),
118 ProjFalseOriginNorthingGeoKey(3087),
119 ProjCenterLongGeoKey(3088),
120 ProjCenterLatGeoKey(3089),
121 ProjCenterEastingGeoKey(3090),
122 ProjCenterNorthingGeoKey(3091),
123 ProjScaleAtNatOriginGeoKey(3092),
124 ProjScaleAtCenterGeoKey(3093),
125 ProjAzimuthAngleGeoKey(3094),
126 ProjStraightVertPoleLongGeoKey(3095),
127
128 VerticalCSTypeGeoKey(4096),
129 VerticalCitationGeoKey(4097),
130 VerticalDatumGeoKey(4098),
131 VerticalUnitsGeoKey(4099),
132
133
134 To_WGS84_GeoKey(2062);
135
136 int key;
137
138 GeoKey(final int key) {
139 this.key = key;
140 }
141 }
142
143
144
145
146
147
148
149
150
151
152 private static final String[] USAGE = { "Usage ReadTagsAndImages <input file> [output file]", " input file: mandatory file to be read",
153 " output file: optional root name and path for files to be written" };
154
155 private static HashMap<Integer, GeoKey> keyMap;
156 private static String nameFormat;
157
158
159
160
161
162
163
164
165
166
167 private static String extractAscii(final String asciiParameters, final int pos, final int len) {
168 if (asciiParameters != null && len > 0 && pos + len <= asciiParameters.length()) {
169 return asciiParameters.substring(pos, pos + len - 1);
170 }
171 return "~~~";
172 }
173
174
175
176
177
178
179
180
181
182
183
184 private static String extractDouble(final double[] doubleParameters, final int pos, final int len) {
185 if (doubleParameters != null && doubleParameters.length >= pos + len) {
186 final StringBuilder sb = new StringBuilder();
187 for (int i = 0; i < len && i < 3; i++) {
188 if (i > 0) {
189 sb.append(" | ");
190 }
191 sb.append(Double.toString(doubleParameters[pos + i]));
192 }
193 if (len > 3) {
194 sb.append(" | ...");
195 }
196 return sb.toString();
197 }
198 return "~~~";
199 }
200
201
202
203
204
205
206
207
208
209
210
211
212
213 private static String interpretElements(final GeoKey geoKey, final int ref, final int len, final int valueOrPosition, final double[] doubleParameters,
214 final String asciiParameters) {
215 switch (geoKey) {
216 case GTModelTypeGeoKey:
217 switch (valueOrPosition) {
218 case 1:
219 return "Projected Coordinate System";
220 case 2:
221 return "Geographic Coordinate System";
222 case 3:
223
224 return "Geocentric Coordinate System";
225 default:
226 break;
227 }
228 break;
229 case GTRasterTypeGeoKey:
230 switch (valueOrPosition) {
231 case 1:
232 return "RasterPixelIsArea";
233 case 2:
234 return "RasterPixelIsPoint";
235 default:
236 return "User Defined";
237 }
238 case GeographicTypeGeoKey:
239 switch (valueOrPosition) {
240 case 4269:
241 return "North American Datum 1983";
242 case 4030:
243 return "World Geodetic Survey 1984";
244 case 4326:
245 return "EPSG 4326, Geographic 2D WGS 84";
246 default:
247 break;
248 }
249 break;
250 case GTCitationGeoKey:
251 return extractAscii(asciiParameters, valueOrPosition, len);
252 case GeogCitationGeoKey:
253 return extractAscii(asciiParameters, valueOrPosition, len);
254 case GeogAngularUnitsGeoKey:
255 switch (valueOrPosition) {
256 case 9101:
257 return "Radians";
258 case 9102:
259 return "Degrees";
260 default:
261 break;
262 }
263 break;
264 case GeogSemiMajorAxisGeoKey:
265 return extractDouble(doubleParameters, valueOrPosition, len);
266 case GeogInvFlatteningGeoKey:
267 return extractDouble(doubleParameters, valueOrPosition, len);
268 case To_WGS84_GeoKey:
269 return extractDouble(doubleParameters, valueOrPosition, len);
270 case ProjectedCRSGeoKey:
271
272 if (0 <= valueOrPosition && valueOrPosition <= 1023) {
273 return "Reserved";
274 }
275 if (1024 <= valueOrPosition && valueOrPosition <= 32766) {
276 return "EPSG Code #" + valueOrPosition;
277 }
278 if (valueOrPosition == 32767) {
279 return "User-Defined Projection";
280 }
281 break;
282 case ProjectionGeoKey:
283 if (valueOrPosition == 32767) {
284 return "User-Defined";
285 }
286 break;
287 case ProjCoordTransGeoKey:
288 final CoordinateTransformationCode code = CoordinateTransformationCode.getValueForKey(valueOrPosition);
289 if (code != null) {
290 return code.name();
291 }
292 break;
293 case ProjLinearUnitsGeoKey:
294 switch (valueOrPosition) {
295 case 9001:
296 return "Meter";
297 case 9002:
298 return "Foot";
299 case 9003:
300 return "Survey Foot";
301 default:
302 break;
303 }
304 break;
305 case ProjStdParallel1GeoKey:
306 case ProjStdParallel2GeoKey:
307 case ProjNatOriginLongGeoKey:
308 case ProjFalseEastingGeoKey:
309 case ProjFalseNorthingGeoKey:
310 case ProjFalseOriginLongGeoKey:
311 case ProjFalseOriginLatGeoKey:
312 case ProjFalseOriginEastingGeoKey:
313 case ProjFalseOriginNorthingGeoKey:
314 case ProjCenterLongGeoKey:
315 case ProjCenterLatGeoKey:
316 return String.format("%13.4f", doubleParameters[valueOrPosition]);
317
318 default:
319 break;
320 }
321 return "See GeoTIFF specification";
322 }
323
324
325
326
327
328
329
330
331 public static void main(final String[] args) throws ImagingException, IOException {
332 if (args.length == 0) {
333
334 for (final String s : USAGE) {
335 System.err.println(s);
336 }
337 System.exit(0);
338 }
339
340
341
342
343 final PrintStream ps = System.out;
344
345 final File target = new File(args[0]);
346 String rootName = null;
347 if (args.length == 2) {
348 rootName = args[1];
349 }
350 final boolean optionalImageReadingEnabled = StringUtils.isNotEmpty(rootName);
351
352 final ByteSource byteSource = ByteSource.file(target);
353 final TiffImagingParameters params = new TiffImagingParameters();
354
355
356
357
358
359
360 final TiffReader tiffReader = new TiffReader(true);
361 final TiffContents contents = tiffReader.readDirectories(byteSource, optionalImageReadingEnabled,
362 FormatCompliance.getDefault());
363
364
365
366 int iDirectory = 0;
367 for (final TiffDirectory directory : contents.directories) {
368
369 final boolean hasTiffImageData = directory.hasTiffImageData();
370 if (iDirectory > 0) {
371 ps.println("\n-----------------------------------------------------\n");
372 }
373
374 String contentType = "";
375 if (directory.hasTiffRasterData()) {
376 contentType = "Numeric raster data";
377 } else if (directory.hasTiffImageData()) {
378 contentType = "Image data";
379 }
380 ps.format("Directory %2d %s, description: %s%n", iDirectory, contentType, directory.description());
381
382 final List<TiffField> fieldList = directory.getDirectoryEntries();
383 for (final TiffField tiffField : fieldList) {
384 String s = tiffField.toString();
385 if (s.length() > 90) {
386 s = s.substring(0, 90);
387 }
388
389
390
391
392 if (tiffField.getTag() == 0x144 || tiffField.getTag() == 0x145) {
393 final int i = s.indexOf(')');
394 final int[] a = tiffField.getIntArrayValue();
395 s = s.substring(0, i + 2) + " [" + a.length + " entries]";
396 }
397 ps.println(" " + s);
398 }
399
400 summarizeGeoTiffTags(ps, directory);
401
402 if (optionalImageReadingEnabled && hasTiffImageData) {
403 final File output = new File(rootName + "_" + iDirectory + ".jpg");
404 ps.println("Writing image to " + output.getPath());
405 final BufferedImage bImage = directory.getTiffImage(params);
406 ImageIO.write(bImage, "JPEG", output);
407 }
408 ps.println("");
409 iDirectory++;
410 }
411 }
412
413
414
415
416
417
418
419
420 private static void summarizeGeoTiffTags(final PrintStream ps, final TiffDirectory directory) throws ImagingException {
421
422 if (keyMap == null) {
423 final GeoKey[] values = GeoKey.values();
424 int maxNameLength = 0;
425 keyMap = new HashMap<>();
426 for (final GeoKey g : values) {
427 final String name = g.name();
428 if (name.length() > maxNameLength) {
429 maxNameLength = name.length();
430 }
431 keyMap.put(g.key, g);
432 }
433
434
435 nameFormat = String.format(" %%-%ds", maxNameLength);
436 }
437
438
439 final short[] geoKeyDirectory = directory.getFieldValue(GeoTiffTagConstants.EXIF_TAG_GEO_KEY_DIRECTORY_TAG, false);
440 if (geoKeyDirectory == null || geoKeyDirectory.length < 4) {
441
442 return;
443 }
444 ps.println("");
445 ps.println("Summary of GeoTIFF Elements ----------------------------");
446
447 final short[] bitsPerSample = directory.getFieldValue(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE, false);
448 final short[] sampleFormat = directory.getFieldValue(TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, false);
449 String contentTypeString = null;
450 if (bitsPerSample != null && sampleFormat != null) {
451 if (bitsPerSample[0] == 16 && sampleFormat[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER) {
452 contentTypeString = "Numeric, Short Integer";
453 } else if ((bitsPerSample[0] == 32 || bitsPerSample[0] == 64) && sampleFormat[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) {
454 contentTypeString = "Numeric, Floating Point (" + bitsPerSample[0] + "-bit samples)";
455 }
456 }
457 if (contentTypeString != null) {
458 ps.format("%nContent Type: %s", contentTypeString);
459 final String[] gdalNoDataString = directory.getFieldValue(GdalLibraryTagConstants.EXIF_TAG_GDAL_NO_DATA, false);
460 if (gdalNoDataString != null && gdalNoDataString.length > 0) {
461 ps.format(" GDAL No-Data value: %s", gdalNoDataString[0]);
462 }
463 ps.format("%n");
464 }
465
466
467
468
469
470
471 final int[] elements = new int[geoKeyDirectory.length];
472 for (int i = 0; i < geoKeyDirectory.length; i++) {
473 elements[i] = geoKeyDirectory[i] & 0xffff;
474 }
475
476
477
478
479
480
481 final TiffField doubleParametersField = directory.findField(GeoTiffTagConstants.EXIF_TAG_GEO_DOUBLE_PARAMS_TAG);
482 double[] doubleParameters = null;
483 if (doubleParametersField != null) {
484 doubleParameters = doubleParametersField.getDoubleArrayValue();
485 }
486
487
488
489 final TiffField asciiParametersField = directory.findField(GeoTiffTagConstants.EXIF_TAG_GEO_ASCII_PARAMS_TAG);
490 String asciiParameters = null;
491 if (asciiParametersField != null) {
492 asciiParameters = asciiParametersField.getStringValue();
493 }
494
495 ps.format("%nGeoKey Table%n");
496 ps.println(" key ref len value/pos name");
497 int k = 0;
498 int n = elements.length / 4;
499 for (int i = 0; i < n; i++) {
500 final int key = elements[k];
501 final int ref = elements[k + 1];
502 final int len = elements[k + 2];
503 final int vop = elements[k + 3];
504 String label = "";
505 if (ref == GeoTiffTagConstants.EXIF_TAG_GEO_ASCII_PARAMS_TAG.tag) {
506 label = "(A)";
507 } else if (ref == GeoTiffTagConstants.EXIF_TAG_GEO_DOUBLE_PARAMS_TAG.tag) {
508 label = "(D)";
509 }
510 for (int j = 0; j < 4; j++) {
511 ps.format("%8d", elements[k++]);
512 }
513 ps.format(" %-3s", label);
514
515
516
517 final GeoKey geoKey;
518 final String name;
519 final String interpretation;
520 if (i == 0) {
521 name = "~~~";
522 interpretation = "~~~";
523 } else {
524 geoKey = keyMap.get(key);
525 if (geoKey == null) {
526 name = "Unknown GeoKey";
527 interpretation = "~~~";
528 } else {
529 name = geoKey.name();
530 interpretation = interpretElements(geoKey, ref, len, vop, doubleParameters, asciiParameters);
531 }
532 }
533
534 ps.format(nameFormat, name);
535 ps.format("%s", interpretation);
536 ps.format("%n");
537 }
538
539
540
541
542
543
544
545
546 final TiffField pixelScaleField = directory.findField(GeoTiffTagConstants.EXIF_TAG_MODEL_PIXEL_SCALE_TAG);
547 final double[] pixelScale;
548 if (pixelScaleField == null) {
549 ps.format("%nModelPixelScale is not supplied%n");
550 } else {
551 pixelScale = pixelScaleField.getDoubleArrayValue();
552 ps.format("%nModelPixelScale%n");
553 for (final double element : pixelScale) {
554 ps.format(" %15.10e", element);
555 }
556 ps.format("%n");
557 }
558
559 final TiffField modelTiepointField = directory.findField(GeoTiffTagConstants.EXIF_TAG_MODEL_TIEPOINT_TAG);
560 if (modelTiepointField != null) {
561 ps.format("%nModelTiepointTag%n");
562 ps.println(" Pixel Model");
563
564 final double[] tiePoints = modelTiepointField.getDoubleArrayValue();
565 n = tiePoints.length / 6;
566 for (int i = 0; i < n; i++) {
567 ps.format(" ");
568 for (int j = 0; j < 3; j++) {
569 ps.format("%6.1f", tiePoints[i * 6 + j]);
570 }
571 ps.format(" ");
572 for (int j = 3; j < 6; j++) {
573 ps.format("%13.3f", tiePoints[i * 6 + j]);
574 }
575 ps.format("%n");
576 }
577 }
578
579 final TiffField modelTransformField = directory.findField(GeoTiffTagConstants.EXIF_TAG_MODEL_TRANSFORMATION_TAG);
580 if (modelTransformField != null) {
581 ps.format("%nModelTransformationTag%n");
582 final double[] mtf = modelTiepointField.getDoubleArrayValue();
583 if (mtf.length >= 16) {
584 for (int i = 0; i < 4; i++) {
585 ps.format(" ");
586 for (int j = 0; j < 4; j++) {
587 ps.format("%13.3f", mtf[i * 4 + j]);
588 }
589 ps.format("%n");
590 }
591 }
592 }
593 }
594 }