Search in sources :

Example 1 with MetadataHelper

use of org.geotoolkit.image.io.metadata.MetadataHelper in project geotoolkit by Geomatys.

the class SpatialImageReader method getImageType.

/**
 * Returns an image type specifier indicating the {@link SampleModel} and {@link ColorModel}
 * to use for reading the image. In addition, this method also detects if some conversions
 * (represented by {@link SampleConverter} instances) are required in order to store the
 * sample values using the selected models. The conversions (if any) are keept as small as
 * possible, but are sometime impossible to avoid for example because {@link IndexColorModel}
 * does not allow negative sample values.
 * <p>
 * The default implementation applies the following steps:
 *
 * <ol>
 *   <li><p>The {@linkplain SampleDimension#getValidSampleValues() range of expected values}
 *       and the {@linkplain SampleDimension#getFillSampleValues() fill values} are extracted
 *       from the {@linkplain #getImageMetadata(int) image metadata}, if any.</p></li>
 *
 *   <li><p>If the given {@code parameters} argument is an instance of {@link SpatialImageReadParam},
 *       then the user-supplied {@linkplain SpatialImageReadParam#getPaletteName palette name}
 *       is fetched. Otherwise or if no palette name was explicitly set, then this method default
 *       to {@value org.geotoolkit.image.io.SpatialImageReadParam#DEFAULT_PALETTE_NAME}. The
 *       palette name will be used in order to {@linkplain PaletteFactory#getColors(String)
 *       read a predefined set of colors} (as [A]RGB values) to be given to the
 *       {@linkplain IndexColorModel index color model}.</p></li>
 *
 *   <li><p>If the {@linkplain #getRawDataType raw data type} is {@link DataBuffer#TYPE_FLOAT
 *       TYPE_FLOAT} or {@link DataBuffer#TYPE_DOUBLE TYPE_DOUBLE}, then this method builds
 *       a {@linkplain PaletteFactory#getContinuousPalette continuous palette} suitable for
 *       the range fetched at step 1. The data are assumed <cite>geophysics</cite> values
 *       rather than some packed values. Consequently, the {@linkplain SampleConverter sample
 *       converters} will replace no-data values by {@linkplain Float#NaN NaN}, but no other
 *       changes will be applied.</p></li>
 *
 *   <li><p>Otherwise, if the {@linkplain #getRawDataType raw data type} is a unsigned integer type
 *       like {@link DataBuffer#TYPE_BYTE TYPE_BYTE} or {@link DataBuffer#TYPE_USHORT TYPE_USHORT},
 *       then this method builds an {@linkplain PaletteFactory#getPalette indexed palette} (i.e. a
 *       palette backed by an {@linkplain IndexColorModel index color model}) with just the minimal
 *       {@linkplain IndexColorModel#getMapSize size} needed for containing fully the range and the
 *       no-data values fetched at step 1. The data are assumed <cite>packed</cite> values rather
 *       than geophysics values. Consequently, the {@linkplain SampleConverter sample converters}
 *       will be the {@linkplain SampleConverter#IDENTITY identity converter} except in the
 *       following cases:
 *       <ul>
 *         <li>The {@linkplain SampleDimension#getValidSampleValues() range of valid values} is
 *             outside the range allowed by the {@linkplain #getRawDataType raw data type} (e.g.
 *             the range of valid values contains negative integers). In this case, the sample
 *             converter will shift the values to a strictly positive range and replace fill
 *             values by 0.</li>
 *         <li>At least one {@linkplain SampleDimension#getFillSampleValues() fill value} is
 *             outside the range of values allowed by the {@linkplain #getRawDataType raw data
 *             type}. In this case, this method will try to only replace the fill values by 0,
 *             without shifting the valid values if this shift can be avoided.</li>
 *         <li>At least one {@linkplain SampleDimension#getFillSampleValues() fill value} is
 *             far away from the {@linkplain SampleDimension#getValidSampleValues() range of
 *             valid values} (for example 9999 while the range of valid values is [0&hellip;255]).
 *             The meaning of "far away" is determined by the {@link #collapseNoDataValues
 *             collapseNoDataValues} method.</li>
 *       </ul>
 *       </p></li>
 *
 *   <li><p>Otherwise, if the {@linkplain #getRawDataType raw data type} is a signed integer
 *       type like {@link DataBuffer#TYPE_SHORT TYPE_SHORT}, then this method builds an
 *       {@linkplain PaletteFactory#getPalette indexed palette} with the maximal {@linkplain
 *       IndexColorModel#getMapSize size} supported by the raw data type (note that this is
 *       memory expensive - typically 256 kilobytes). Negative values will be stored in their
 *       two's complement binary form in order to fit in the range of positive integers
 *       supported by the {@linkplain IndexColorModel index color model}.</p></li>
 * </ol>
 *
 * {@section Using the Sample Converters}
 * If the {@code converters} argument is non-null, then this method will store the
 * {@link SampleConverter} instances in the supplied array. The array length shall be equals
 * to the number of {@linkplain ImageReadParam#getSourceBands() source} and
 * {@linkplain ImageReadParam#getDestinationBands() destination bands}.
 * <p>
 * The converters shall be used by {@link #read(int,ImageReadParam) read} method
 * implementations for converting the values read in the datafile to values acceptable
 * by the {@linkplain ColorModel color model}. See the
 * {@link #getDestination(int, ImageReadParam, int, int, SampleConverter[]) getDestination}
 * method for code example.
 *
 * {@section Overriding this method}
 * Subclasses can override this method for example if the color {@linkplain Palette palette}
 * and range of values should be computed in a different way. The example below creates an
 * image type using hard-coded objects:
 *
 * {@preformat java
 *     int minimum     = -2000;      // Minimal expected value
 *     int maximum     = +2300;      // Maximal expected value
 *     int fillValue   = -9999;      // Value for missing data
 *     String colors   = "rainbow";  // Named set of RGB colors
 *     converters[0]   = SampleConverter.createOffset(1 - minimum, fillValue);
 *     Palette palette = PaletteFactory.getDefault().getPalettePadValueFirst(colors, maximum - minimum);
 *     return palette.getImageTypeSpecifier();
 * }
 *
 * @param imageIndex
 *          The index of the image to be queried.
 * @param parameters
 *          The user-supplied parameters, or {@code null}. Note: we recommend to supply
 *          {@link #getDefaultReadParam} instead of {@code null} since subclasses may
 *          override the later with default values suitable to a particular format.
 * @param converters
 *          If non-null, an array where to store the converters created by this method.
 *          The length of this array shall be equals to the number of target bands.
 * @return
 *          The image type (never {@code null}).
 * @throws IOException
 *          If an error occurs while reading the format information from the input source.
 *
 * @see #getRawDataType
 * @see #collapseNoDataValues
 * @see #getDestination(int, ImageReadParam, int, int, SampleConverter[])
 */
@SuppressWarnings("fallthrough")
protected ImageTypeSpecifier getImageType(final int imageIndex, final ImageReadParam parameters, final SampleConverter[] converters) throws IOException {
    /*
         * Extracts all informations we will need from the user-supplied parameters, if any.
         * Note: the number of bands in the target image (as requested by the caller)
         * may be different than the number of bands in the source image (on disk).
         */
    final ImageTypeSpecifier userType;
    final String paletteName;
    final int[] sourceBands;
    final int[] targetBands;
    final int visibleBand;
    final int numBands;
    if (parameters != null) {
        sourceBands = parameters.getSourceBands();
        targetBands = parameters.getDestinationBands();
        userType = parameters.getDestinationType();
    } else {
        sourceBands = null;
        targetBands = null;
        userType = null;
    }
    if (sourceBands != null) {
        // == targetBands.length (assuming valid ImageReadParam).
        numBands = sourceBands.length;
    } else if (targetBands != null) {
        numBands = targetBands.length;
    } else {
        numBands = getNumBands(imageIndex);
    }
    List<? extends SampleDomain> bands = null;
    if (parameters instanceof SpatialImageReadParam) {
        final SpatialImageReadParam geoparam = (SpatialImageReadParam) parameters;
        paletteName = geoparam.getNonNullPaletteName();
        visibleBand = geoparam.getVisibleBand();
        bands = geoparam.getSampleDomains();
    } else {
        paletteName = SpatialImageReadParam.DEFAULT_PALETTE_NAME;
        visibleBand = 0;
    }
    /*
         * Gets the band metadata. If the user specified explicitly a SampleDomain in the
         * parameters, this is all the information we need - so we can avoid the cost of
         * querying IIOMetadata. Otherwise we will need to extract the image IIOMetadata.
         */
    boolean convertBandIndices = false;
    if (bands == null) {
        final SpatialMetadata metadata;
        final boolean oldIgnore = ignoreMetadata;
        try {
            ignoreMetadata = false;
            metadata = getImageMetadata(imageIndex);
        } finally {
            ignoreMetadata = oldIgnore;
        }
        if (metadata != null) {
            final List<SampleDimension> sd = metadata.getListForType(SampleDimension.class);
            if (!isNullOrEmpty(sd)) {
                convertBandIndices = (sourceBands != null);
                bands = sd;
            }
        }
    }
    /*
         * Gets the data type, and check if we should replace it by an other type. Type
         * replacements are allowed only if the appropriate SampleConversionType enum is set.
         */
    boolean replaceFillValues = false;
    int dataType = (userType != null) ? userType.getSampleModel().getDataType() : getRawDataType(imageIndex);
    if (userType == null && parameters instanceof SpatialImageReadParam) {
        final SpatialImageReadParam geoparam = (SpatialImageReadParam) parameters;
        switch(dataType) {
            case DataBuffer.TYPE_SHORT:
                {
                    if (geoparam.isSampleConversionAllowed(SHIFT_SIGNED_INTEGERS)) {
                        dataType = DataBuffer.TYPE_USHORT;
                    }
                // Fall through
                }
            case DataBuffer.TYPE_USHORT:
            case DataBuffer.TYPE_INT:
            case DataBuffer.TYPE_BYTE:
                {
                    if (bands == null || !geoparam.isSampleConversionAllowed(STORE_AS_FLOATS)) {
                        break;
                    }
                    boolean hasFillValues = false;
                    for (final SampleDomain domain : bands) {
                        final double[] fillValues = domain.getFillSampleValues();
                        if (fillValues != null && fillValues.length != 0) {
                            hasFillValues = true;
                            break;
                        }
                    }
                    if (!hasFillValues) {
                        break;
                    }
                    dataType = DataBuffer.TYPE_FLOAT;
                // Fall through
                }
            case DataBuffer.TYPE_FLOAT:
            case DataBuffer.TYPE_DOUBLE:
                {
                    replaceFillValues = geoparam.isSampleConversionAllowed(REPLACE_FILL_VALUES);
                }
        }
    }
    /*
         * Gets the minimal and maximal values allowed for the target image type.
         * Note that this is meanless for floating point types, so the values in
         * that case are arbitrary.
         *
         * The only integer types that are signed are SHORT (not to be confused with
         * USHORT) and INT. Other types like BYTE and USHORT are treated as unsigned.
         */
    final boolean isFloat;
    final long floor, ceil;
    switch(dataType) {
        // Actually we don't really know what to do for this case...
        case DataBuffer.TYPE_UNDEFINED:
        // Fall through since we can treat this case as float.
        case DataBuffer.TYPE_DOUBLE:
        case DataBuffer.TYPE_FLOAT:
            {
                isFloat = true;
                floor = Long.MIN_VALUE;
                ceil = Long.MAX_VALUE;
                break;
            }
        case DataBuffer.TYPE_INT:
            {
                isFloat = false;
                floor = Integer.MIN_VALUE;
                ceil = Integer.MAX_VALUE;
                break;
            }
        case DataBuffer.TYPE_SHORT:
            {
                isFloat = false;
                floor = Short.MIN_VALUE;
                ceil = Short.MAX_VALUE;
                break;
            }
        default:
            {
                isFloat = false;
                floor = 0;
                ceil = (1L << DataBuffer.getDataTypeSize(dataType)) - 1;
                break;
            }
    }
    /*
         * Computes a range of values for all bands, as the union in order to make sure that
         * we can stores every sample values. Also creates SampleConverters in the process.
         * The later is an opportunist action since we gather most of the needed information
         * during the loop.
         */
    NumberRange<?> allRanges = null;
    NumberRange<?> visibleRange = null;
    SampleConverter visibleConverter = SampleConverter.IDENTITY;
    // Only in the visible band, and must be positive.
    double maximumFillValue = 0;
    if (bands != null) {
        // To be created only if needed.
        MetadataHelper helper = null;
        // Never 0 - check was performed above.
        final int numMetadataBands = bands.size();
        for (int i = 0; i < numBands; i++) {
            int bandIndex = convertBandIndices ? sourceBands[i] : i;
            if (bandIndex < 0 || bandIndex >= numMetadataBands) {
                if (numMetadataBands != 1) {
                    // If there is exactly one metadata band, don't log any warning since
                    // we will assume that the metadata band apply to all data bands.
                    Warnings.log(this, null, SpatialImageReader.class, "getImageType", indexOutOfBounds(bandIndex, 0, numMetadataBands));
                }
                bandIndex = numMetadataBands - 1;
            }
            /*
                 * Before to get the range, get the fill values with maximal precision.
                 * Some values may need to be casted from 'double' to 'float' in order
                 * to match the sample values in the raster. This cast to various types
                 * will be performed internally by the SampleConverter implementations.
                 */
            final SampleDomain band = bands.get(bandIndex);
            final double[] fillValues = band.getFillSampleValues();
            final NumberRange<?> range;
            if (band instanceof SampleDimension) {
                if (helper == null) {
                    helper = new MetadataHelper(this);
                }
                range = helper.getValidSampleValues(bandIndex, (SampleDimension) band, fillValues);
            } else {
                range = band.getValidSampleValues();
            }
            double minimum, maximum;
            if (range != null) {
                minimum = range.getMinDouble();
                maximum = range.getMaxDouble();
                if (!isFloat) {
                    // treat as if we use the maximal range allowed by the data type.
                    if (minimum == Double.NEGATIVE_INFINITY)
                        minimum = floor;
                    if (maximum == Double.POSITIVE_INFINITY)
                        maximum = ceil;
                }
                final double extent = maximum - minimum;
                if (extent >= 0 && (isFloat || extent <= (ceil - floor))) {
                    allRanges = (allRanges != null) ? allRanges.unionAny(range) : range;
                } else {
                    // Use range.getMin/MaxValue() because they may be integers rather than doubles.
                    Warnings.log(this, null, SpatialImageReader.class, "getImageType", Errors.Keys.IllegalRange_2, range.getMinValue(), range.getMaxValue());
                    continue;
                }
            } else {
                minimum = Double.NaN;
                maximum = Double.NaN;
            }
            final int targetBand = (targetBands != null) ? targetBands[i] : i;
            /*
                 * For floating point types, replaces no-data values by NaN because the floating
                 * point numbers are typically used for geophysics data, so the raster is likely
                 * to be a "geophysics" view for GridCoverage2D. All other values are stored "as
                 * is" without any offset.
                 *
                 * For integer types, if the range of values from the source data file fits into
                 * the range of values allowed by the destination raster, we will use an identity
                 * converter. If the only required conversion is a shift from negative to positive
                 * values, creates an offset converter with no-data values collapsed to 0.
                 */
            final SampleConverter converter;
            if (isFloat) {
                // If the sample values are float values, we need to replace 99.99 fill value
                // (for example) by 99.99f, which is 99.98999786376953 in double precision,
                // otherwise the SampleConverter may not find them (denpending which method
                // is invoked). This cast is done by the PadValueMask constructor.
                converter = replaceFillValues ? SampleConverter.createPadValuesMask(fillValues) : SampleConverter.IDENTITY;
            } else {
                final boolean isZeroValid = (minimum <= 0 && maximum >= 0);
                boolean collapsePadValues = false;
                if (fillValues != null && fillValues.length != 0) {
                    final double[] sorted = fillValues.clone();
                    Arrays.sort(sorted);
                    double minFill = sorted[0];
                    double maxFill = minFill;
                    int indexMax = sorted.length;
                    while (--indexMax != 0 && Double.isNaN(maxFill = sorted[indexMax])) ;
                    assert minFill <= maxFill || Double.isNaN(minFill) : maxFill;
                    if (targetBand == visibleBand && maxFill > maximumFillValue) {
                        maximumFillValue = maxFill;
                    }
                    if (minFill < floor || maxFill > ceil) {
                        // At least one fill value is outside the range of acceptable values.
                        collapsePadValues = true;
                    } else if (minimum >= 0) {
                        /*
                             * Arbitrary optimization of memory usage:  if there is a "large" empty
                             * space between the range of valid values and a no-data value, then we
                             * may (at subclass implementors choice) collapse the no-data values to
                             * zero in order to avoid wasting the empty space.  Note that we do not
                             * perform this collapse if the valid range contains negative values
                             * because it would not save any memory. We do not check the no-data
                             * values between 0 and 'minimum' for the same reason.
                             */
                        int k = Arrays.binarySearch(sorted, maximum);
                        if (// We want the first element greater than maximum.
                        k >= 0)
                            // We want the first element greater than maximum.
                            k++;
                        else
                            // Really ~ operator, not -
                            k = ~k;
                        if (k <= indexMax) {
                            double unusedSpace = Math.max(sorted[k] - maximum - 1, 0);
                            while (++k <= indexMax) {
                                final double delta = sorted[k] - sorted[k - 1] - 1;
                                if (delta > 0) {
                                    unusedSpace += delta;
                                }
                            }
                            final int unused = (int) Math.min(Math.round(unusedSpace), Integer.MAX_VALUE);
                            collapsePadValues = collapseNoDataValues(isZeroValid, sorted, unused);
                        // We invoked 'collapseNoDataValues' unconditionally even if
                        // 'unused' is zero because the user may decide on the basis
                        // of other criterions, like 'isZeroValid'.
                        }
                    }
                }
                if (minimum < floor || maximum > ceil) {
                    // The range of valid values is outside the range allowed by raw data type.
                    converter = SampleConverter.createOffset(Math.ceil(1 - minimum), fillValues);
                } else if (collapsePadValues) {
                    if (isZeroValid) {
                        // We need to collapse the no-data values to 0, but it causes a clash
                        // with the range of valid values. So we also shift the later.
                        converter = SampleConverter.createOffset(Math.ceil(1 - minimum), fillValues);
                    } else {
                        // We need to collapse the no-data values and there is no clash.
                        converter = SampleConverter.createPadValuesMask(fillValues);
                    }
                } else {
                    /*
                         * Do NOT take 'fillValues' in account if there is no need to collapse
                         * them. This is not the converter's job to transform "packed" values to
                         * "geophysics" values. We just want them to fit in the IndexColorModel,
                         * and they already fit. So the identity converter is appropriate even
                         * in presence of pad values.
                         */
                    converter = SampleConverter.IDENTITY;
                }
            }
            if (converters != null && i < converters.length) {
                converters[i] = converter;
            }
            if (targetBand == visibleBand) {
                visibleConverter = converter;
                visibleRange = range;
            }
        }
    }
    /*
         * Ensure that all converters are defined. We typically have no converter if there
         * is no "ImageDescription/Dimensions" metadata. If the user specified explicitly
         * the image type, then we are done.
         */
    if (converters != null) {
        for (int i = Math.min(converters.length, numBands); --i >= 0; ) {
            if (converters[i] == null) {
                converters[i] = visibleConverter;
            }
        }
    }
    if (userType != null) {
        return userType;
    }
    /*
         * Creates a color palette suitable for the range of values in the visible band.
         * The case for floating points is the simplest: we should not have any offset,
         * at most a replacement of no-data values. In the case of integer values, we
         * must make sure that the indexed color map is large enough for containing both
         * the highest data value and the highest no-data value.
         */
    if (visibleRange == null) {
        visibleRange = (allRanges != null) ? allRanges : NumberRange.create(floor, true, ceil, true);
    }
    PaletteFactory factory = null;
    if (parameters instanceof SpatialImageReadParam) {
        factory = ((SpatialImageReadParam) parameters).getPaletteFactory();
    }
    if (factory == null) {
        factory = PaletteFactory.getDefault();
    }
    factory.setWarningLocale(locale);
    final double minimum = visibleRange.getMinDouble();
    final double maximum = visibleRange.getMaxDouble();
    final Palette palette;
    if (isFloat) {
        assert visibleConverter.getOffset() == 0 : visibleConverter;
        palette = factory.getContinuousPalette(paletteName, (float) minimum, (float) maximum, dataType, numBands, visibleBand);
    } else {
        final double offset = visibleConverter.getOffset();
        long lower, upper;
        if (minimum == Double.NEGATIVE_INFINITY) {
            lower = floor;
        } else {
            lower = Math.round(minimum + offset);
            if (!visibleRange.isMinIncluded()) {
                // Must be inclusive
                lower++;
            }
        }
        if (maximum == Double.POSITIVE_INFINITY) {
            upper = ceil;
        } else {
            upper = Math.round(maximum + offset);
            if (visibleRange.isMaxIncluded()) {
                // Must be exclusive
                upper++;
            }
        }
        long size = Math.max(upper, Math.round(maximumFillValue) + 1);
        if (lower < 0) {
            size -= lower;
        }
        /*
             * The target lower, upper and size parameters are usually in the range of SHORT
             * or USHORT data type.  The Palette class will perform the necessary checks and
             * throw an exception if those variables are out of range. However we may have
             * values out of this range for TYPE_INT, in which case we will use the same slow
             * color model than the one for floating point values.
             */
        if (lower >= Short.MIN_VALUE && (lower + size) <= (lower >= 0 ? IndexedPalette.MAX_UNSIGNED + 1 : Short.MAX_VALUE + 1)) {
            palette = factory.getPalette(paletteName, (int) lower, (int) upper, (int) size, numBands, visibleBand);
        } else {
            palette = factory.getContinuousPalette(paletteName, lower, upper, dataType, numBands, visibleBand);
        }
    }
    return palette.getImageTypeSpecifier();
}
Also used : IndexedPalette(org.geotoolkit.image.palette.IndexedPalette) Palette(org.geotoolkit.image.palette.Palette) SpatialMetadata(org.geotoolkit.image.io.metadata.SpatialMetadata) PaletteFactory(org.geotoolkit.image.palette.PaletteFactory) SampleDimension(org.geotoolkit.image.io.metadata.SampleDimension) SampleDomain(org.geotoolkit.image.io.metadata.SampleDomain) ImageTypeSpecifier(javax.imageio.ImageTypeSpecifier) MetadataHelper(org.geotoolkit.image.io.metadata.MetadataHelper)

Example 2 with MetadataHelper

use of org.geotoolkit.image.io.metadata.MetadataHelper in project geotoolkit by Geomatys.

the class AsciiGridWriter method prepareHeader.

/**
 * Fills the given {@code header} map with values extracted from the given image metadata.
 * The {@code "NCOLS"} and {@code "NROWS"} attributes are already defined when this method
 * is invoked. This method is responsible for filling the remaining attributes.
 *
 * @param  metadata The metadata.
 * @param  header The map in which to store the (<var>key</var>, <var>value</var>) pairs
 *         to be written.
 * @return The fill value, or {@code Double#NaN} if none.
 * @throws IOException If the metadata can not be prepared.
 */
private String prepareHeader(final SpatialMetadata metadata, final Map<String, String> header, final ImageWriteParam param) throws IOException {
    final MetadataHelper helper = new MetadataHelper(this);
    final Georectified spatialRp = metadata.getInstanceForType(Georectified.class);
    final RectifiedGrid domain = metadata.getInstanceForType(RectifiedGrid.class);
    final PixelOrientation ptInPixel = (spatialRp != null) ? spatialRp.getPointInPixel() : null;
    final AffineTransform gridToCRS = helper.getAffineTransform(domain, param);
    String xll = "XLLCORNER";
    String yll = "YLLCORNER";
    // reverted (i.e. the corresponding value in OffsetVectors is negative).
    if (ptInPixel != null && !ptInPixel.equals(PixelOrientation.UPPER_LEFT)) {
        if (ptInPixel.equals(PixelOrientation.CENTER)) {
            xll = "XLLCENTER";
            yll = "YLLCENTER";
        } else if (ptInPixel.equals(PixelOrientation.valueOf("UPPER"))) {
            yll = "YLLCENTER";
        } else if (ptInPixel.equals(PixelOrientation.valueOf("LEFT"))) {
            xll = "XLLCENTER";
        } else {
            throw new ImageMetadataException(Warnings.message(this, Errors.Keys.IllegalParameterValue_2, "pointInPixel", ptInPixel));
        }
    }
    header.put(xll, String.valueOf(gridToCRS.getTranslateX()));
    header.put(yll, String.valueOf(gridToCRS.getTranslateY()));
    /*
         * Use the CELLSIZE attribute if the pixels are square, or the DX, DY attibutes
         * if they are rectangular and we are allowed to use those non-standard attributes.
         */
    try {
        header.put("CELLSIZE", String.valueOf(helper.getCellSize(gridToCRS)));
    } catch (IIOException e) {
        final Dimension2D size;
        if (strictCellSize || (size = helper.getCellDimension(gridToCRS)) == null) {
            throw e;
        }
        Warnings.log(this, null, AsciiGridWriter.class, "writeHeader", e);
        header.put("DX", String.valueOf(size.getWidth()));
        header.put("DY", String.valueOf(size.getHeight()));
    }
    /*
         * Get the fill sample value, which is optional. The default defined by
         * the ASCII grid format is -9999.
         */
    String fillValue = DEFAULT_FILL;
    final List<SampleDimension> dimensions = metadata.getListForType(SampleDimension.class);
    if (!isNullOrEmpty(dimensions)) {
        final SampleDimension dim = dimensions.get(0);
        if (dim != null) {
            final double[] fillValues = dim.getFillSampleValues();
            if (fillValues != null && fillValues.length != 0) {
                final double value = fillValues[0];
                if (!Double.isNaN(value)) {
                    fillValue = CharSequences.trimFractionalPart(String.valueOf(value)).toString();
                    header.put("NODATA_VALUE", fillValue);
                }
            }
        }
    }
    return fillValue;
}
Also used : PixelOrientation(org.opengis.metadata.spatial.PixelOrientation) Dimension2D(java.awt.geom.Dimension2D) IIOException(javax.imageio.IIOException) Georectified(org.opengis.metadata.spatial.Georectified) SampleDimension(org.geotoolkit.image.io.metadata.SampleDimension) MetadataHelper(org.geotoolkit.image.io.metadata.MetadataHelper) RectifiedGrid(org.opengis.coverage.grid.RectifiedGrid) AffineTransform(java.awt.geom.AffineTransform) ImageMetadataException(org.geotoolkit.image.io.ImageMetadataException)

Example 3 with MetadataHelper

use of org.geotoolkit.image.io.metadata.MetadataHelper in project geotoolkit by Geomatys.

the class ImageCoverageWriterInspector method assertRectifiedGridEquals.

/**
 * Tests that the rectified grid is equals to the given affine transform coefficients.
 *
 * @since 3.17
 */
public void assertRectifiedGridEquals(final double scaleX, final double scaleY, final double translateX, final double translateY) throws ImageMetadataException {
    assertInstanceOf("Expected a spatial metadata", SpatialMetadata.class, metadata);
    final MetadataHelper helper = new MetadataHelper(null);
    final RectifiedGrid rg = ((SpatialMetadata) metadata).getInstanceForType(RectifiedGrid.class);
    final AffineTransform tr = helper.getAffineTransform(rg, null);
    assertEquals("shearX", 0, tr.getShearX(), EPS);
    assertEquals("shearY", 0, tr.getShearY(), EPS);
    assertEquals("scaleX", scaleX, tr.getScaleX(), EPS);
    assertEquals("scaleY", scaleY, tr.getScaleY(), EPS);
    assertEquals("translateX", translateX, tr.getTranslateX(), EPS);
    assertEquals("translateY", translateY, tr.getTranslateY(), EPS);
}
Also used : MetadataHelper(org.geotoolkit.image.io.metadata.MetadataHelper) SpatialMetadata(org.geotoolkit.image.io.metadata.SpatialMetadata) RectifiedGrid(org.opengis.coverage.grid.RectifiedGrid) AffineTransform(java.awt.geom.AffineTransform)

Example 4 with MetadataHelper

use of org.geotoolkit.image.io.metadata.MetadataHelper in project geotoolkit by Geomatys.

the class WorldFileImageWriter method writeImageMetadata.

/**
 * Invoked by the {@code write} methods when image metadata needs to be written.
 * The default implementation writes the <cite>World File</cite> if an affine
 * transform can be build from the {@linkplain RectifiedGrid rectified grid domain}.
 */
@Override
protected void writeImageMetadata(final IIOMetadata metadata, final int imageIndex, final ImageWriteParam param) throws IOException {
    if (imageIndex != 0) {
        throw new IIOException(Errors.getResources(locale).getString(Errors.Keys.IndexOutOfBounds_1, imageIndex));
    }
    if (metadata instanceof SpatialMetadata) {
        final SpatialMetadata md = (SpatialMetadata) metadata;
        final RectifiedGrid rf = md.getInstanceForType(RectifiedGrid.class);
        if (rf != null) {
            final MetadataHelper mh = new MetadataHelper(md);
            final AffineTransform tr = mh.getAffineTransform(rf, param);
            final Object path = createOutput("tfw");
            if (path != null) {
                try (OutputStream out = IOUtilities.openWrite(path)) {
                    SupportFiles.writeTFW(out, tr);
                }
            }
        }
        /*
             * Write the CRS if non-null and not an instance of ImageCRS. The ImageCRS case is
             * excluded because it is the default CRS assigned by WorldFileImageReader when no
             * ".prj" file were found.
             */
        final CoordinateReferenceSystem crs = md.getInstanceForType(CoordinateReferenceSystem.class);
        if (crs != null && !(crs instanceof ImageCRS)) {
            final Object path = createOutput("prj");
            if (path != null) {
                try (OutputStream out = IOUtilities.openWrite(path)) {
                    PrjFiles.write(crs, out);
                }
            }
        }
    }
}
Also used : MetadataHelper(org.geotoolkit.image.io.metadata.MetadataHelper) SpatialMetadata(org.geotoolkit.image.io.metadata.SpatialMetadata) ImageCRS(org.opengis.referencing.crs.ImageCRS) RectifiedGrid(org.opengis.coverage.grid.RectifiedGrid) OutputStream(java.io.OutputStream) AffineTransform(java.awt.geom.AffineTransform) IIOException(javax.imageio.IIOException) CoordinateReferenceSystem(org.opengis.referencing.crs.CoordinateReferenceSystem)

Aggregations

MetadataHelper (org.geotoolkit.image.io.metadata.MetadataHelper)4 AffineTransform (java.awt.geom.AffineTransform)3 SpatialMetadata (org.geotoolkit.image.io.metadata.SpatialMetadata)3 RectifiedGrid (org.opengis.coverage.grid.RectifiedGrid)3 IIOException (javax.imageio.IIOException)2 SampleDimension (org.geotoolkit.image.io.metadata.SampleDimension)2 Dimension2D (java.awt.geom.Dimension2D)1 OutputStream (java.io.OutputStream)1 ImageTypeSpecifier (javax.imageio.ImageTypeSpecifier)1 ImageMetadataException (org.geotoolkit.image.io.ImageMetadataException)1 SampleDomain (org.geotoolkit.image.io.metadata.SampleDomain)1 IndexedPalette (org.geotoolkit.image.palette.IndexedPalette)1 Palette (org.geotoolkit.image.palette.Palette)1 PaletteFactory (org.geotoolkit.image.palette.PaletteFactory)1 Georectified (org.opengis.metadata.spatial.Georectified)1 PixelOrientation (org.opengis.metadata.spatial.PixelOrientation)1 CoordinateReferenceSystem (org.opengis.referencing.crs.CoordinateReferenceSystem)1 ImageCRS (org.opengis.referencing.crs.ImageCRS)1