it.geosolutions.imageio.plugins.nitronitf.NITFImageWriter.java Source code

Java tutorial

Introduction

Here is the source code for it.geosolutions.imageio.plugins.nitronitf.NITFImageWriter.java

Source

/*
 *    ImageI/O-Ext - OpenSource Java Image translation Library
 *    http://www.geo-solutions.it/
 *    http://java.net/projects/imageio-ext/
 *    (C) 2007 - 2009, GeoSolutions
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    either version 3 of the License, or (at your option) any later version.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package it.geosolutions.imageio.plugins.nitronitf;

import it.geosolutions.imageio.plugins.jp2k.JP2KKakaduImageWriteParam;
import it.geosolutions.imageio.plugins.jp2k.JP2KKakaduImageWriteParam.Compression;
import it.geosolutions.imageio.plugins.jp2k.JP2KKakaduImageWriter;
import it.geosolutions.imageio.plugins.jp2k.JP2KKakaduImageWriterSpi;
import it.geosolutions.imageio.plugins.nitronitf.NITFUtilities.WriteCompression;
import it.geosolutions.imageio.plugins.nitronitf.wrapper.HeaderWrapper;
import it.geosolutions.imageio.plugins.nitronitf.wrapper.ImageWrapper;
import it.geosolutions.imageio.plugins.nitronitf.wrapper.NITFProperties;
import it.geosolutions.imageio.plugins.nitronitf.wrapper.ShapeFileWrapper;
import it.geosolutions.imageio.plugins.nitronitf.wrapper.TextWrapper;
import it.geosolutions.imageio.plugins.nitronitf.wrapper.ImageWrapper.Category;
import it.geosolutions.imageio.plugins.nitronitf.wrapper.ImageWrapper.ImageBand;
import it.geosolutions.imageio.stream.input.FileImageInputStreamExt;
import it.geosolutions.imageio.stream.input.FileImageInputStreamExtImpl;
import it.geosolutions.imageio.stream.output.FileImageOutputStreamExt;

import java.awt.image.DataBufferByte;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.IIOImage;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi;
import javax.media.jai.operator.BandSelectDescriptor;

import nitf.BandInfo;
import nitf.BandSource;
import nitf.DESegment;
import nitf.Extensions;
import nitf.FileHeader;
import nitf.IOHandle;
import nitf.IOInterface;
import nitf.ImageSegment;
import nitf.ImageSource;
import nitf.ImageSubheader;
import nitf.MemorySource;
import nitf.NITFException;
import nitf.Record;
import nitf.SegmentSource;
import nitf.SegmentWriter;
import nitf.StreamIOWriteHandler;
import nitf.TRE;
import nitf.TextSegment;
import nitf.TextSubheader;
import nitf.Version;
import nitf.WriteHandler;
import nitf.Writer;

import org.apache.commons.io.FilenameUtils;

public class NITFImageWriter extends ImageWriter {

    private File outputFile;

    private static final Logger LOGGER = Logger
            .getLogger("it.geosolutions.imageio.plugins.nitronitf.NITFImageWriter");

    private final static JP2KKakaduImageWriterSpi KAKADU_SPI = new JP2KKakaduImageWriterSpi();

    private static final String JP2_TEMP_FOLDER;

    public static final String JP2_TEMP_FOLDER_PROPERTY = "nitf.imageio.jp2folder";

    private static final boolean DO_VALIDATION = true;

    static {
        String jp2TempFolder = System.getProperty(JP2_TEMP_FOLDER_PROPERTY);
        if (jp2TempFolder != null) {
            final File file = new File(jp2TempFolder);
            final boolean exist = file.exists();
            final boolean isDirectory = file.isDirectory();
            final boolean canWrite = file.canWrite();
            if (!exist || !isDirectory || !canWrite) {
                if (LOGGER.isLoggable(Level.WARNING)) {
                    final StringBuilder warningMessage = new StringBuilder(
                            "The specified folder can't be used as jp2 temporary folder: " + jp2TempFolder);
                    warningMessage.append(" since it ");
                    boolean comma = false;
                    if (!exist) {
                        warningMessage.append("doesn't exist");
                        comma = true;
                    }
                    if (!isDirectory) {
                        warningMessage.append(comma ? "," : "").append("isn't a directory");
                    }
                    if (!canWrite) {
                        warningMessage.append(comma ? "," : "").append("can't be written");
                    }
                    warningMessage.append("\nUsing default temp dir: ");
                    jp2TempFolder = System.getProperty("java.io.tmpdir");
                    warningMessage.append(jp2TempFolder);
                    LOGGER.log(Level.WARNING, warningMessage.toString());
                }
            }
        } else {
            jp2TempFolder = null;
        }
        JP2_TEMP_FOLDER = jp2TempFolder;
    }

    public NITFImageWriter(ImageWriterSpi originatingProvider) {
        super(originatingProvider);
    }

    public void setOutput(Object output) {
        if (output instanceof FileImageOutputStreamExt) {
            outputFile = ((FileImageOutputStreamExt) output).getFile();
        } else if (output instanceof File) {
            outputFile = (File) output;
        } else {
            throw new IllegalArgumentException("unsupported output type");
        }

    }

    @Override
    public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
        throw new UnsupportedOperationException("getDefaultStreamMetadata not implemented yet");
    }

    @Override
    public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
        throw new UnsupportedOperationException("getDefaultImageMetadata not implemented yet");
    }

    @Override
    public IIOMetadata convertStreamMetadata(IIOMetadata inData, ImageWriteParam param) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType,
            ImageWriteParam param) {
        // TODO Auto-generated method stub
        return null;
    }

    /**
     * Setup all the header fields taking them from the wrapper
     * 
     * @param record
     * @param headerWrapper
     * @throws NITFException
     */
    private static void initFileHeader(Record record, HeaderWrapper headerWrapper) throws NITFException {
        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "Populating file header");
        }
        final FileHeader header = record.getHeader();
        NITFUtilities.setField("FHDR", header.getFileHeader(), NITFUtilities.Consts.DEFAULT_FILE_HEADER);
        NITFUtilities.setField("FVER", header.getFileVersion(), NITFUtilities.Consts.DEFAULT_FILE_VERSION);
        NITFUtilities.setField("STYPE", header.getSystemType(), NITFUtilities.Consts.DEFAULT_SYSTEM_TYPE);
        if (headerWrapper != null) {
            NITFUtilities.setField("OSTAID", header.getOriginStationID(), headerWrapper.getOriginStationId());
            NITFUtilities.setField("FDT", header.getFileDateTime(), headerWrapper.getDateTime());
            NITFUtilities.setField("FTITLE", header.getFileTitle(), headerWrapper.getTitle());
            NITFUtilities.setField("FSCLSY", header.getSecurityGroup().getClassificationSystem(),
                    headerWrapper.getSecurityClassificationSystem());
            NITFUtilities.setField("ENCRYP", header.getEncrypted(), Integer.toString(headerWrapper.getEncrypted()));
            header.getClassification().setData(headerWrapper.getSecurityClassificationSystem());
            NITFUtilities.setField("FBKGC", header.getBackgroundColor(), headerWrapper.getBackgroundColor());
            NITFUtilities.setField("ONAME", header.getOriginatorName(), headerWrapper.getOriginatorName());
            NITFUtilities.setField("OPHONE", header.getOriginatorPhone(), headerWrapper.getOriginatorPhone());
        }

        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "file header has been setup");
        }

        // Setting main header TREs if any
        Map<String, Map<String, String>> tresMap = headerWrapper.getTres();
        if (tresMap != null && !tresMap.isEmpty()) {
            Extensions extendedSection = header.getExtendedSection();
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "Populating Main Header TREs");
            }

            Set<String> keys = tresMap.keySet();
            Iterator<String> it = keys.iterator();
            while (it.hasNext()) {
                String treName = it.next();
                Map<String, String> fieldsMapping = tresMap.get(treName);
                TRE tre = setTRE(treName, fieldsMapping);
                extendedSection.appendTRE(tre);
            }
        }
    }

    /**
     * Setup the ImageSegment
     * 
     * @param record
     * @param images
     * @param fis
     * @param compression
     * @return
     * @throws NITFException
     * @throws IOException
     */
    private static void addImageSegment(final Record record, final List<ImageWrapper> images,
            final FileImageInputStreamExt fis, final WriteCompression compression)
            throws NITFException, IOException {
        ImageSegment segment = null;
        ImageSubheader subheader = null;
        int img = 0;

        // Using a loop for future usage of the cloudCover image
        for (ImageWrapper image : images) {

            // Getting compression parameter and imageProperties
            WriteCompression writeCompression = img == 0 ? compression : WriteCompression.UNCOMPRESSED;
            segment = record.newImageSegment();

            // Setting up the image Sub Header
            subheader = segment.getSubheader();
            double bpppb = initImageSubHeader(image, subheader, writeCompression, fis);

            if (img == 0) {
                initTREs(subheader, image, writeCompression, bpppb);
            }
            img++;

        }
    }

    /**
     * 
     * @param subheader
     * @param isSingleBand
     * @param compression
     * @param tresMap
     * @param bpppb
     * @throws NITFException
     */
    private static void initTREs(final ImageSubheader subheader, final ImageWrapper wrapper,
            final WriteCompression compression, final double bpppb) throws NITFException {
        Extensions extendedSection = subheader.getExtendedSection();
        final boolean isSingleBand = wrapper.getImage().getSampleModel().getNumBands() == 1;
        final Map<String, Map<String, String>> tresMap = wrapper.getTres();
        if (tresMap != null && !tresMap.isEmpty()) {
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "Populating TRE");
            }

            Set<String> keys = tresMap.keySet();
            Iterator<String> it = keys.iterator();
            while (it.hasNext()) {
                String treName = it.next();
                Map<String, String> fieldsMapping = tresMap.get(treName);
                TRE tre = setTRE(treName, fieldsMapping);
                extendedSection.appendTRE(tre);
            }
        }

        if (compression != WriteCompression.UNCOMPRESSED) {
            //Setting the J2KLRA TRE in case the image need to be jp2 compressed
            setJ2KLRA(extendedSection, compression, isSingleBand, bpppb);
        }

    }

    /**
     * Set the J2KLRA Tagged record extension containing information about quality layers and bit rates.
     * 
     * @param extendedSection
     * @param compression
     * @param isSingleBand
     * @param lastRate
     * @throws NITFException
     */
    private static void setJ2KLRA(final Extensions extendedSection, final WriteCompression compression,
            final boolean isSingleBand, final double lastRate) throws NITFException {
        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "Populating J2KLRA TRE");
        }

        // Initialize values
        final String compressionString = compression.toString();
        final String origData = compressionString.startsWith("NPJE") ? "0"
                : compressionString.startsWith("EPJE") ? "2" : "8";
        final boolean isVL = compression.getCompression() == Compression.LOSSY;
        final int nLayers = compression.getQualityLayers();
        final double bitRates[] = compression.getBitRates();

        Map<String, String> j2klraMapping = new LinkedHashMap<String, String>();
        j2klraMapping.put("ORIG", origData);
        j2klraMapping.put("NLEVELS_O", "5");
        j2klraMapping.put("NBANDS_O", isSingleBand ? "1" : "3");

        j2klraMapping.put("NLAYERS_O", String.valueOf(nLayers));
        for (int i = 0; i < nLayers; i++) {
            double rate = i != nLayers - 1 ? bitRates[i]
                    : isVL ? bitRates[i] : (!Double.isNaN(lastRate) ? lastRate : NITFUtilities.BPPPB[i - 1]);
            String bitrate = customFormat(rate);
            j2klraMapping.put("LAYER_ID[" + i + "]", String.valueOf(i));
            j2klraMapping.put("BITRATE[" + i + "]", bitrate);
        }
        extendedSection.appendTRE(setTRE("J2KLRA", j2klraMapping));
    }

    /**
     * 
     * @param imageWrapper
     * @param subheader
     * @param compression
     * @param fis
     * @return
     * @throws IOException
     * @throws NITFException
     */
    private static double initImageSubHeader(final ImageWrapper imageWrapper, final ImageSubheader subheader,
            final WriteCompression compression, final FileImageInputStreamExt fis)
            throws IOException, NITFException {
        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "Populating ImageSubHeader");
        }

        final RenderedImage ri = imageWrapper.getImage();
        final List<String> comments = imageWrapper.getComments();

        // Setting up rows, cols, blocks, bits properties
        final boolean isJP2 = compression != null && compression != WriteCompression.UNCOMPRESSED;
        final int nCols = ri.getWidth();
        final int nRows = ri.getHeight();
        final int nBits = ri.getSampleModel().getSampleSize(0);
        final String numBlocksPerRow = isJP2
                ? String.valueOf((int) Math.ceil((double) nCols / NITFUtilities.DEFAULT_TILE_WIDTH))
                : String.valueOf(1);
        final String numBlocksPerCol = isJP2
                ? String.valueOf((int) Math.ceil((double) nRows / NITFUtilities.DEFAULT_TILE_HEIGHT))
                : String.valueOf(1);
        final String numPixelsPerVertBlock = isJP2 ? String.valueOf(NITFUtilities.DEFAULT_TILE_HEIGHT) : "0000"; // As per specification
        final String numPixelsPerHorizBlock = isJP2 ? String.valueOf(NITFUtilities.DEFAULT_TILE_WIDTH) : "0000"; // As per specification

        double ratio = Double.NaN;

        NITFUtilities.setField("IM", subheader.getFilePartType(), "IM");
        NITFUtilities.setField("IID1", subheader.getImageId(), imageWrapper.getId());
        NITFUtilities.setField("IDATIM", subheader.getImageDateAndTime(), imageWrapper.getDateTime());
        NITFUtilities.setField("IID2", subheader.getImageTitle(), imageWrapper.getTitle());
        NITFUtilities.setField("ISCLAS", subheader.getImageSecurityClass(),
                imageWrapper.getSecurityClassification());
        NITFUtilities.setField("ISCLSY", subheader.getSecurityGroup().getClassificationSystem(),
                imageWrapper.getSecurityClassificationSystem());
        NITFUtilities.setField("ENCRYP", subheader.getEncrypted(), Integer.toString(imageWrapper.getEncrypted()));
        NITFUtilities.setField("ISORCE", subheader.getImageSource(), imageWrapper.getSource());
        NITFUtilities.setField("NROWS", subheader.getNumRows(), String.valueOf(nRows));
        NITFUtilities.setField("NCOLS", subheader.getNumCols(), String.valueOf(nCols));
        NITFUtilities.setField("PVTYPE", subheader.getPixelValueType(), NITFUtilities.Consts.DEFAULT_PVTYPE);
        NITFUtilities.setField("IREP", subheader.getImageRepresentation(),
                imageWrapper.getRepresentation().toString());
        NITFUtilities.setField("ICAT", subheader.getImageCategory(), imageWrapper.getImageCategory().toString());
        NITFUtilities.setField("ABPP", subheader.getActualBitsPerPixel(), Integer.toString(nBits));
        NITFUtilities.setField("PJUST", subheader.getPixelJustification(), imageWrapper.getPixelJustification());
        NITFUtilities.setField("ICORDS", subheader.getImageCoordinateSystem(),
                imageWrapper.getImageCoordinateSystem());
        NITFUtilities.setField("IGEOLO", subheader.getCornerCoordinates(), imageWrapper.getIgeolo());

        if (comments != null && !comments.isEmpty()) {
            int i = 0;
            for (String comment : comments) {
                subheader.insertImageComment(comment, i++);
            }
        }

        ratio = setImageCompression(subheader, compression, ri, fis);
        setImageBands(subheader, imageWrapper);

        NITFUtilities.setField("ISYNC", subheader.getImageSyncCode(), NITFUtilities.Consts.ZERO);
        NITFUtilities.setField("IMODE", subheader.getImageMode(), NITFUtilities.Consts.DEFAULT_IMODE);
        NITFUtilities.setField("NBPR", subheader.getNumBlocksPerRow(), numBlocksPerRow);
        NITFUtilities.setField("NBPC", subheader.getNumBlocksPerCol(), numBlocksPerCol);
        NITFUtilities.setField("NPPBH", subheader.getNumPixelsPerHorizBlock(), numPixelsPerHorizBlock);
        NITFUtilities.setField("NPPBV", subheader.getNumPixelsPerVertBlock(), numPixelsPerVertBlock);
        NITFUtilities.setField("NBPP", subheader.getNumBitsPerPixel(), Integer.toString(nBits));
        NITFUtilities.setField("IDLVL", subheader.getImageDisplayLevel(), NITFUtilities.Consts.ONE);
        NITFUtilities.setField("IALVL", subheader.getImageAttachmentLevel(), NITFUtilities.Consts.ZERO);
        NITFUtilities.setField("ILOC", subheader.getImageLocation(), NITFUtilities.Consts.ZERO);
        NITFUtilities.setField("IMAG", subheader.getImageMagnification(), imageWrapper.getImageMagnification());
        return ratio;

    }

    private static void setImageBands(final ImageSubheader subheader, final ImageWrapper imageWrapper)
            throws NITFException {
        BandInfo[] bandInfos = null;
        final ImageBand[] bands = imageWrapper.getBands();
        if (bands == null || bands.length == 0) {
            throw new IllegalArgumentException("ImageBands must be specified");
        }
        subheader.createBands(bands.length);
        bandInfos = subheader.getBandInfo();
        for (int i = 0; i < bandInfos.length; i++) {
            BandInfo bandInfo = bandInfos[i];
            NITFUtilities.setField("IREPBAND" + i, bandInfo.getRepresentation(), bands[i].getRepresentation());
            // ISUBCAT shouldn't be null for MultiSpectral Imagery
            NITFUtilities.setField("ISUBCAT" + i, bandInfo.getSubcategory(), bands[i].getSubCategory(),
                    imageWrapper.getImageCategory() == Category.MS);
            NITFUtilities.setField("IFC" + i, bandInfo.getImageFilterCondition(), NITFUtilities.Consts.NONE);
            NITFUtilities.setField("NLUTS" + i, bandInfo.getNumLUTs(), NITFUtilities.Consts.ZERO);
        }
    }

    /**
     * Setup Image Compression related fields depending on the compression properties.
     * 
     * @param subheader the {@link ImageSubheader} to be set
     * @param compression the specified {@link WriteCompression}
     * @param isJP2 whether a JP2
     * @param ri the renderedImage
     * @param fis
     * @return
     * @throws IOException
     */
    private static double setImageCompression(final ImageSubheader subheader, final WriteCompression compression,
            final RenderedImage ri, final FileImageInputStreamExt fis) throws IOException {
        double ratio = Double.NaN;
        final int numBits = ri.getSampleModel().getSampleSize(0);
        if (compression != null && compression != WriteCompression.UNCOMPRESSED) {
            // Setting up compression type and compression ratio
            NITFUtilities.setField("IC", subheader.getImageCompression(), NITFUtilities.Consts.COMPRESSION_JP2);
            if (fis != null) {
                final long codeStreamSize = fis.length();
                final long imageSize = ri.getWidth() * ri.getHeight() * ri.getSampleModel().getNumBands();
                ratio = codeStreamSize / (double) (imageSize / (double) numBits);
            }
            String comrat = "";
            if (compression.getCompression() == Compression.NUMERICALLY_LOSSLESS) {
                String ratioString = Double.toString(ratio);
                ratioString = ratioString.replace(".", "");
                ratioString = ratioString.replace(",", "");
                ratioString = "N0" + ratioString.substring(0, 2); // ImproveMe
                comrat = ratioString;
            } else if (compression == WriteCompression.RATIO_15_1) {
                // Wait for NGA feedbacks to fix this value
                comrat = NITFUtilities.Consts.COMPRESSION_L005;
            } else if (compression.toString().endsWith("VL")) {
                // Visually lossless uses a static value
                comrat = NITFUtilities.Consts.COMPRESSION_V039;
            }
            NITFUtilities.setField("COMRAT", subheader.getCompressionRate(), comrat);
        } else {
            NITFUtilities.setField("IC", subheader.getImageCompression(), NITFUtilities.Consts.COMPRESSION_NONE);
        }
        return ratio;
    }

    /**
     * Set a new Tagged Record Extension on top of the specified fields map.
     * 
     * @param treName the name of the TRE to be setup
     * @param fieldsMap the map of fields <key,value> pairs
     * @return the populated TRE
     * @throws NITFException
     */
    private static TRE setTRE(String treName, Map<String, String> fieldsMap) throws NITFException {
        TRE tre = new TRE(treName);
        Set<String> keysSet = fieldsMap.keySet();
        Iterator<String> keys = keysSet.iterator();
        while (keys.hasNext()) {
            String key = keys.next();
            String value = fieldsMap.get(key);
            if (key.contains("[") && key.contains("]")) {
                // Fields involved in LOOPS require special management
                // Getting the field from the TRE won't work, therefore
                // we need to directly set it
                NITFUtilities.setTREFieldDirect(tre, key, value);
            } else {
                NITFUtilities.setTREField(tre, key, value, true);
            }
        }
        return tre;
    }

    /**
     * Do the real write operation (writing images, texts, ...)
     * 
     * @param record
     * @param images
     * @param shp
     * @param fis
     * @param text
     * @return
     * @throws NITFException
     * @throws IOException
     */
    private boolean writeNITF(final Record record, final List<ImageWrapper> images, final ShapeFileWrapper shp,
            final FileImageInputStreamExt fis, final List<TextWrapper> texts) throws NITFException, IOException {
        final int numImages = images.size();
        ImageWrapper image = images.get(0);
        RenderedImage ri = image.getImage();
        WriteCompression compression = image.getCompression();
        int nBands = ri.getSampleModel().getNumBands();
        boolean written = false;
        Writer writer = new Writer();
        IOHandle handle = new IOHandle(outputFile.getCanonicalPath(), IOHandle.NITF_ACCESS_WRITEONLY,
                IOHandle.NITF_CREATE);

        byte[] shapeFileData = null;
        final boolean isJP2 = !(compression == WriteCompression.UNCOMPRESSED);
        if (shp != null) {
            shapeFileData = getShapeData(record, shp);
        }

        boolean prepared = false;
        if (isJP2) {
            // //
            //
            // get the JP2 Codestream previously written with Kakadu and transfer its content within
            // the NITF imageSegment
            //
            // //
            WriteHandler codeStream = null;
            IOInterface io;
            final int size = (int) fis.length();
            io = new IOFileInputStream(fis);

            writer.prepare(record, handle);

            if (shapeFileData != null) {
                writeData(shapeFileData, writer);
            }

            codeStream = new StreamIOWriteHandler(io, 0, size);
            writer.setImageWriteHandler(0, codeStream);
            prepared = true;

        }
        if (!isJP2 || numImages > 1) {

            if (!prepared) {
                writer.prepare(record, handle);
            }

            if (numImages == 1) {

                // setup a Writer
                if (shapeFileData != null) {
                    writeData(shapeFileData, writer);
                }

                ImageSource imageSource = new ImageSource();
                nitf.ImageWriter imageWriter = writer.getNewImageWriter(0);
                boolean[] successes = new boolean[nBands];
                final boolean isMono = images.get(0).getImage().getSampleModel().getNumBands() == 1;
                if (isMono) {
                    DataBufferByte dbb = (DataBufferByte) ri.getData().getDataBuffer();
                    BandSource bs = new MemorySource(dbb.getData(), dbb.getSize(), 0, 0, 0);
                    successes[0] = imageSource.addBand(bs);
                } else {
                    for (int i = 0; i < nBands; i++) {
                        RenderedImage band = BandSelectDescriptor.create(ri, new int[] { i }, null);
                        DataBufferByte dbb = (DataBufferByte) band.getData().getDataBuffer();
                        BandSource bs = new MemorySource(dbb.getData(), dbb.getSize(), 0, 0, 0);
                        successes[i] = imageSource.addBand(bs);
                    }
                }

                imageWriter.attachSource(imageSource);
            } else {
                ImageWrapper img = images.get(1);
                ri = img.getImage();
                nBands = ri.getSampleModel().getNumBands();
                ImageSource imageSource = new ImageSource();
                nitf.ImageWriter imageWriter2 = writer.getNewImageWriter(1);
                boolean[] successes = new boolean[nBands];
                DataBufferByte dbb = (DataBufferByte) ri.getData().getDataBuffer();
                BandSource bs = new MemorySource(dbb.getData(), dbb.getSize(), 0, 0, 0);
                successes[0] = imageSource.addBand(bs);
                imageWriter2.attachSource(imageSource);
            }

        }

        // Adding text
        if (texts != null && !texts.isEmpty()) {
            int i = 0;
            for (TextWrapper text : texts) {
                byte[] textContent = text.getTextContent();
                if (textContent != null) {
                    SegmentWriter textWriter = writer.getNewTextWriter(i++);
                    SegmentSource source = SegmentSource.makeSegmentMemorySource(textContent, textContent.length, 0,
                            0);
                    textWriter.attachSource(source);
                }
            }
        }

        written = writer.write();
        if (handle != null) {
            handle.close();
        }

        return written;
    }

    /**
     * Encode a RenderedImage as a JP2K codestream on the specified outputFile, using the proper set of compression parameters.
     * 
     * @param outputFile
     * @param compression
     * @param ri
     * @throws FileNotFoundException
     * @throws IOException
     */
    private void prepareJP2Image(final RenderedImage ri, final File outputFile, final WriteCompression compression)
            throws FileNotFoundException, IOException {
        JP2KKakaduImageWriter kakaduWriter = null;

        try {
            // TODO: Check PAN/MULTI can really be known from number of bands
            final int numBands = ri.getSampleModel().getNumBands();
            final boolean isMulti = numBands == 1 ? false : true;
            kakaduWriter = new JP2KKakaduImageWriter(KAKADU_SPI);
            kakaduWriter.setOutput(outputFile);
            JP2KKakaduImageWriteParam param = NITFUtilities.getCompressionParam(kakaduWriter, compression, isMulti);

            kakaduWriter.write(null, new IIOImage(ri, null, null), param);
        } finally {
            if (kakaduWriter != null) {
                try {
                    kakaduWriter.dispose();
                } catch (Throwable t) {

                }
            }
        }
    }

    /**
     * Write data extension on a segment
     */
    private void writeData(byte[] fullData, Writer writer) throws NITFException {
        final SegmentWriter deWriter = writer.getNewDEWriter(0);
        final SegmentSource source = SegmentSource.makeSegmentMemorySource(fullData, fullData.length, 0, 0);
        deWriter.attachSource(source);
    }

    /**
     * 
     * @param record
     * @param shape
     * @return
     * @throws NITFException
     * @throws IOException
     */
    private byte[] getShapeData(Record record, ShapeFileWrapper shape) throws NITFException, IOException {
        final DESegment des = record.newDESegment();
        byte[] fullData = null;
        TRE csshpa = new TRE("CSSHPA");
        TRE tre = des.getSubheader().setSubheaderFields(csshpa);

        final byte[] bshp = shape.getShp();
        final byte[] bshx = shape.getShx();
        final byte[] bdbf = shape.getDbf();
        final int shp = shape.getShpLength();
        final int shx = shape.getShxLength();
        final int dbf = shape.getDbfLength();

        if (bshp == null || bshx == null || bdbf == null) {
            throw new NITFException("Unable to write CSSHPA ShapeFile");
        }

        fullData = new byte[shp + shx + dbf];
        System.arraycopy(bshp, 0, fullData, 0, shp);
        System.arraycopy(bshx, 0, fullData, shp, shx);
        System.arraycopy(bdbf, 0, fullData, shp + shx, dbf);

        tre.setField("SHAPE_USE", "IMAGE_SHAPE              ");
        NITFUtilities.setTREField(tre, "SHAPE_CLASS", "POLYGON   ", false);
        NITFUtilities.setTREField(tre, "SHAPE1_NAME", "SHP", false);
        NITFUtilities.setTREField(tre, "SHAPE1_START", NITFUtilities.Consts.ZERO, false);
        NITFUtilities.setTREField(tre, "SHAPE2_NAME", "SHX", false);
        NITFUtilities.setTREField(tre, "SHAPE2_START", String.valueOf(shp), false);
        NITFUtilities.setTREField(tre, "SHAPE3_NAME", "DBF", false);
        NITFUtilities.setTREField(tre, "SHAPE3_START", String.valueOf(shx + shp), false);

        NITFUtilities.setField("DE", des.getSubheader().getFilePartType(), "DE");
        NITFUtilities.setField("DESID", des.getSubheader().getTypeID(), "CSSHPA DES");
        NITFUtilities.setField("DESVER", des.getSubheader().getVersion(), "01");
        NITFUtilities.setField("DECLAS", des.getSubheader().getSecurityClass(), "U");
        NITFUtilities.setField("DESCLSY", des.getSubheader().getSecurityGroup().getClassificationSystem(), "US");

        return fullData;

    }

    @Override
    public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {

        // Headers and segments initialization
        NITFImageWriteParam nitfParam = null;
        HeaderWrapper header = null;
        Map<String, Map<String, String>> extensionsMap = null;
        ShapeFileWrapper shape = null;
        List<TextWrapper> texts = null;
        WriteCompression compression = null;
        List<ImageWrapper> inputImages = null;
        if (param != null && param instanceof NITFImageWriteParam) {
            nitfParam = (NITFImageWriteParam) param;
            NITFProperties nitfMetadata = nitfParam.getNitfProperties();
            if (nitfMetadata != null) {
                header = nitfMetadata.getHeader();
                shape = nitfMetadata.getShape();
                texts = nitfMetadata.getTextsWrapper();
                inputImages = nitfMetadata.getImagesWrapper();
            }
            compression = nitfParam.getWriteCompression();

        }

        ImageWrapper imageW = inputImages.get(0);
        RenderedImage ri = imageW.getImage();
        final boolean isJP2 = (compression != null && compression != WriteCompression.UNCOMPRESSED);
        FileImageInputStreamExt jp2Stream = null;
        File tempFile = null;
        try {
            Record record = new Record(Version.NITF_21);
            if (isJP2) {
                // Proceeding with jp2 compression
                if (JP2_TEMP_FOLDER != null) {
                    tempFile = File.createTempFile("jp2compressed", ".jpc", new File(JP2_TEMP_FOLDER));
                }
                String parentPath = outputFile.getParent();
                String name = FilenameUtils.getBaseName(outputFile.getCanonicalPath());
                tempFile = new File(parentPath + File.separatorChar + name + ".j2c");
                prepareJP2Image(ri, tempFile, compression);
                jp2Stream = new FileImageInputStreamExtImpl(tempFile);
            }

            // populating the file header
            initFileHeader(record, header);

            // adding an image segment to the record
            addImageSegment(record, inputImages, jp2Stream, compression);
            if (texts != null && !texts.isEmpty()) {
                // adding a text segment if present
                addTextSegment(record, texts);
            }

            if (!writeNITF(record, inputImages, shape, jp2Stream, texts)) {
                throw new IOException("Unable to successfully write");
            }
        } catch (Throwable t) {
            IOException ioe = new IOException();
            ioe.initCause(t);
            throw ioe;
        } finally {

            // Releasing resources
            if (jp2Stream != null) {
                try {
                    jp2Stream.close();
                } catch (Throwable thr) {
                    // Eat exception
                }
            }
            if (tempFile != null) {
                try {
                    tempFile.delete();
                } catch (Throwable thr) {

                }
            }

        }

        // record.destruct();
        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "Successfully wrote NITF: " + outputFile);
        }
    }

    /**
     * Add a new text Segment to the record with the information provided by the {@link TextWrapper} instance
     * 
     * @param record
     * @param wrapper
     * @throws NITFException
     */
    private void addTextSegment(Record record, List<TextWrapper> texts) throws NITFException {
        if (texts != null && !texts.isEmpty()) {
            for (TextWrapper wrapper : texts) {
                TextSegment text = record.newTextSegment();
                TextSubheader textSubHeader = text.getSubheader();
                NITFUtilities.setField("TEXTID", textSubHeader.getTextID(), wrapper.getId());
                NITFUtilities.setField("TXTITL", textSubHeader.getTitle(), wrapper.getTitle());
                NITFUtilities.setField("TXTALVL", textSubHeader.getAttachmentLevel(), wrapper.getAttachmentLevel());
                NITFUtilities.setField("TSCLSY", textSubHeader.getSecurityGroup().getClassificationSystem(),
                        wrapper.getSecurityClassificationSystem());
                NITFUtilities.setField("TXTDT", textSubHeader.getDateTime(), wrapper.getDateTime());
                NITFUtilities.setField("ENCRYP", textSubHeader.getEncrypted(),
                        Integer.toString(wrapper.getEncrypted()));
                NITFUtilities.setField("TXTFMT", textSubHeader.getFormat(), wrapper.getFormat());
            }
        }
    }

    private static String customFormat(double value) {
        // Creating a new one since it isn't thread safe
        DecimalFormat myFormatter = new DecimalFormat("00.000000");
        return myFormatter.format(value).replace(",", ".");
    }
}