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