private static void summarizeGeoTiffTags(final PrintStream ps, final TiffDirectory directory) throws ImagingException {

        if (keyMap == null) {
            final GeoKey[] values = GeoKey.values();
            int maxNameLength = 0;
            keyMap = new HashMap<>();
            for (final GeoKey g : values) {
                final String name = g.name();
                if (name.length() > maxNameLength) {
                    maxNameLength = name.length();
                }
                keyMap.put(g.key, g);
            }
            // create a formatting string that will pad all names
            // out with trailing spaces to provide text alignment in code below.
            nameFormat = String.format("   %%-%ds", maxNameLength);
        }

        // check to see if the directory has GeoTIFF tags.
        final short[] geoKeyDirectory = directory.getFieldValue(GeoTiffTagConstants.EXIF_TAG_GEO_KEY_DIRECTORY_TAG, false);
        if (geoKeyDirectory == null || geoKeyDirectory.length < 4) {
            // The TIFF directory does not contain GeoTIFF information
            return;
        }
        ps.println("");
        ps.println("Summary of GeoTIFF Elements ----------------------------");

        final short[] bitsPerSample = directory.getFieldValue(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE, false);
        final short[] sampleFormat = directory.getFieldValue(TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, false);
        String contentTypeString = null;
        if (bitsPerSample != null && sampleFormat != null) {
            if (bitsPerSample[0] == 16 && sampleFormat[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER) {
                contentTypeString = "Numeric, Short Integer";
            } else if ((bitsPerSample[0] == 32 || bitsPerSample[0] == 64) && sampleFormat[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) {
                contentTypeString = "Numeric, Floating Point (" + bitsPerSample[0] + "-bit samples)";
            }
        }
        if (contentTypeString != null) {
            ps.format("%nContent Type: %s", contentTypeString);
            final String[] gdalNoDataString = directory.getFieldValue(GdalLibraryTagConstants.EXIF_TAG_GDAL_NO_DATA, false);
            if (gdalNoDataString != null && gdalNoDataString.length > 0) {
                ps.format("    GDAL No-Data value: %s", gdalNoDataString[0]);
            }
            ps.format("%n");
        }

        // all GeoKeyDirectory elements are unsigned shorts (2 bytes).
        // Some of which exceed the value 32767, the maximum value of
        // a signed short). Because Java does not support an unsigned short type,
        // we need to mask in the low-order 2 bytes and obtain a 4-byte integer
        // equivalent.
        final int[] elements = new int[geoKeyDirectory.length];
        for (int i = 0; i < geoKeyDirectory.length; i++) {
            elements[i] = geoKeyDirectory[i] & 0xffff;
        }

        // Get the double field, Tag ID=34736 (0x87B0). This field
        // will usually be present. Some TIFF products supply only the
        // European Petroleum Survey Group (EPSG) map projection ID
        // and may omit the floating-point map-projection parameters.
        // That approach is generally discouraged, but not prohibited.
        final TiffField doubleParametersField = directory.findField(GeoTiffTagConstants.EXIF_TAG_GEO_DOUBLE_PARAMS_TAG);
        double[] doubleParameters = null;
        if (doubleParametersField != null) {
            doubleParameters = doubleParametersField.getDoubleArrayValue();
        }

        // Get the ASCII field, Tag ID=34737 (0x87B1). This field
        // is often, but not always, present.
        final TiffField asciiParametersField = directory.findField(GeoTiffTagConstants.EXIF_TAG_GEO_ASCII_PARAMS_TAG);
        String asciiParameters = null;
        if (asciiParametersField != null) {
            asciiParameters = asciiParametersField.getStringValue();
        }

        ps.format("%nGeoKey Table%n");
        ps.println("     key     ref     len   value/pos     name");
        int k = 0;
        int n = elements.length / 4;
        for (int i = 0; i < n; i++) {
            final int key = elements[k];
            final int ref = elements[k + 1];
            final int len = elements[k + 2];
            final int vop = elements[k + 3];
            String label = "";
            if (ref == GeoTiffTagConstants.EXIF_TAG_GEO_ASCII_PARAMS_TAG.tag) {
                label = "(A)";
            } else if (ref == GeoTiffTagConstants.EXIF_TAG_GEO_DOUBLE_PARAMS_TAG.tag) {
                label = "(D)";
            }
            for (int j = 0; j < 4; j++) {
                ps.format("%8d", elements[k++]);
            }
            ps.format("   %-3s", label);

            // The first four elements in the integer array are not
            // actually a GeoKey, but rather an overall identifier
            final GeoKey geoKey;
            final String name;
            final String interpretation;
            if (i == 0) {
                name = "~~~";
                interpretation = "~~~";
            } else {
                geoKey = keyMap.get(key);
                if (geoKey == null) {
                    name = "Unknown GeoKey";
                    interpretation = "~~~";
                } else {
                    name = geoKey.name();
                    interpretation = interpretElements(geoKey, ref, len, vop, doubleParameters, asciiParameters);
                }
            }

            ps.format(nameFormat, name);
            ps.format("%s", interpretation);
            ps.format("%n");
        }

        // Note: The y coordinate of the model pixel scale is backwards.
        // GeoTIFF images are stored from upper-left corner downward
        // (following the conventional graphics standards). In cases
        // where the rows in the image or raster run from north to south,
        // one might expect that the delta-Y between rows would be
        // a negative number. But by the GeoTIFF standard,
        // the vertical spacing is given as a positive number.
        final TiffField pixelScaleField = directory.findField(GeoTiffTagConstants.EXIF_TAG_MODEL_PIXEL_SCALE_TAG);
        final double[] pixelScale;
        if (pixelScaleField == null) {
            ps.format("%nModelPixelScale is not supplied%n");
        } else {
            pixelScale = pixelScaleField.getDoubleArrayValue();
            ps.format("%nModelPixelScale%n");
            for (final double element : pixelScale) {
                ps.format("   %15.10e", element);
            }
            ps.format("%n");
        }

        final TiffField modelTiepointField = directory.findField(GeoTiffTagConstants.EXIF_TAG_MODEL_TIEPOINT_TAG);
        if (modelTiepointField != null) {
            ps.format("%nModelTiepointTag%n");
            ps.println("           Pixel                           Model");

            final double[] tiePoints = modelTiepointField.getDoubleArrayValue();
            n = tiePoints.length / 6;
            for (int i = 0; i < n; i++) {
                ps.format("   ");
                for (int j = 0; j < 3; j++) {
                    ps.format("%6.1f", tiePoints[i * 6 + j]);
                }
                ps.format("     ");
                for (int j = 3; j < 6; j++) {
                    ps.format("%13.3f", tiePoints[i * 6 + j]);
                }
                ps.format("%n");
            }
        }

        final TiffField modelTransformField = directory.findField(GeoTiffTagConstants.EXIF_TAG_MODEL_TRANSFORMATION_TAG);
        if (modelTransformField != null) {
            ps.format("%nModelTransformationTag%n");
            final double[] mtf = modelTiepointField.getDoubleArrayValue();
            if (mtf.length >= 16) {
                for (int i = 0; i < 4; i++) {
                    ps.format("   ");
                    for (int j = 0; j < 4; j++) {
                        ps.format("%13.3f", mtf[i * 4 + j]);
                    }
                    ps.format("%n");
                }
            }
        }
    }
Checks to see if the directory has GeoTIFF tags and, if so, provides a summary of their content.

@param ps        a valid instance to receive output
@param directory a valid directory
@throws ImagingException in the event of a data-format error or unhandled I/O error.
src/test/java/org/apache/commons/imaging/examples/tiff/ReadTagsAndImages.java