Search in sources :

Example 1 with SpatialMetadata

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

the class TiffImageReader method createMetadata.

/**
 * {@inheritDoc }.
 */
@Override
protected SpatialMetadata createMetadata(final int imageIndex) throws IOException {
    if (imageIndex < 0) {
        // stream metadata
        return super.createMetadata(imageIndex);
    }
    checkLayers();
    final int layerIndex = getLayerIndex(imageIndex);
    if (metaHeads[layerIndex] == null) {
        selectLayer(layerIndex);
    }
    headProperties = metaHeads[layerIndex];
    fillRootMetadataNode(layerIndex);
    // -- find geotiff extensions
    if (extensions == null) {
        extensions = Collections.unmodifiableList(Arrays.stream(GeoTiffExtension.getExtensions()).filter(ext -> ext.isPresent(input)).map(GeoTiffExtension::newInstance).collect(Collectors.toList()));
    }
    final IIOTiffMetadata metadata = new IIOTiffMetadata(roots[layerIndex]);
    final GeoTiffMetaDataReader metareader = new GeoTiffMetaDataReader(metadata);
    SpatialMetadata spatialMetadata;
    /*
         * We'll try to fill referencing information from GeoTiff file. If it
         * fails, we delay error, because there's a chance that one of the
         * declared extensions is able to provide it. For example, user could
         * have provided prj and tfw file, transforming the geotiff in a world
         * file tiff image.
         */
    FactoryException referencingError = null;
    try {
        spatialMetadata = metareader.readSpatialMetaData();
    } catch (FactoryException ex) {
        if (extensions.isEmpty()) {
            throw new IOException("Bad referencing information found in GeoTiff", ex);
        }
        LOGGER.log(Level.WARNING, "Bad referencing information found. We'll continue and try to check on potential extensions (Dimap, Worldd file, etc.)", ex);
        spatialMetadata = new SpatialMetadata(SpatialMetadataFormat.getImageInstance(SpatialMetadataFormat.GEOTK_FORMAT_NAME));
        // Even if geographic information is bad, we try to get other metadata, as it could be important for data description.
        new ThirdPartyMetaDataReader(metadata).fillSpatialMetaData(spatialMetadata);
    }
    // -- fill extensions informations
    for (GeoTiffExtension ext : extensions) {
        spatialMetadata = ext.fillSpatialMetaData(this, spatialMetadata);
    }
    final CoordinateReferenceSystem crs = spatialMetadata.getInstanceForType(CoordinateReferenceSystem.class);
    final RectifiedGrid rectifiedGrid = spatialMetadata.getInstanceForType(RectifiedGrid.class);
    if ((crs == null || rectifiedGrid == null) && referencingError != null) {
        // Geotiff referencing is bad, and no extension allowed for proper correction, so we stop reading immediately.
        throw new IOException("Bad referencing information found", referencingError);
    }
    // -- search for the coordinate reference system
    if (crs == null) {
        LOGGER.log(Level.FINE, "Current data : " + input.toString() + " doesn't contain any Coordinate Reference System.");
    }
    // -- verify GridToCrs pertinency
    if (rectifiedGrid == null) {
        LOGGER.log(Level.FINE, "Current data : " + input.toString() + " doesn't contain any Tie Points or GridToCrs transformation function.");
    }
    return spatialMetadata;
}
Also used : GeoTiffMetaDataReader(org.geotoolkit.metadata.geotiff.GeoTiffMetaDataReader) SpatialMetadata(org.geotoolkit.image.io.metadata.SpatialMetadata) FactoryException(org.opengis.util.FactoryException) GeoTiffExtension(org.geotoolkit.metadata.geotiff.GeoTiffExtension) RectifiedGrid(org.opengis.coverage.grid.RectifiedGrid) IIOException(javax.imageio.IIOException) CoordinateReferenceSystem(org.opengis.referencing.crs.CoordinateReferenceSystem) ThirdPartyMetaDataReader(org.geotoolkit.metadata.geotiff.ThirdPartyMetaDataReader) SystemOverride(org.geotoolkit.lang.SystemOverride)

Example 2 with SpatialMetadata

use of org.geotoolkit.image.io.metadata.SpatialMetadata 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 3 with SpatialMetadata

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

the class NullImageReader method createMetadata.

/**
 * Returns the metadata specified at construction time.
 *
 * @param  imageIndex  The image index, numbered from 0.
 * @throws IOException Never thrown in default implementation.
 */
@Override
public SpatialMetadata createMetadata(final int imageIndex) throws IOException {
    final SpatialMetadata metadata = new SpatialMetadata(false, this, null);
    final DimensionAccessor accessor = new DimensionAccessor(metadata);
    accessor.selectChild(accessor.appendChild());
    accessor.setValueRange(minimum * scale + offset, maximum * scale + offset);
    accessor.setValidSampleValue(minimum, maximum);
    accessor.setFillSampleValues(padValue);
    return metadata;
}
Also used : SpatialMetadata(org.geotoolkit.image.io.metadata.SpatialMetadata) DimensionAccessor(org.geotoolkit.internal.image.io.DimensionAccessor)

Example 4 with SpatialMetadata

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

the class WorldFileImageReader method createMetadata.

/**
 * Creates a new stream or image metadata. This method first delegates to the main reader as
 * documented in the {@linkplain ImageReaderAdapter#createMetadata(int) super-class method},
 * then completes the metadata with information read from the <cite>World File</cite> and
 * <cite>Map Projection</cite> files.
 * <p>
 * The <cite>World File</cite> and <cite>Map Projection</cite> files are determined by calls
 * to the {@link #createInput(String)} method with {@code "tfw"} and {@code "prj"} argument
 * values. Subclasses can override the later method if they want to specify different files
 * to be read.
 */
@Override
protected SpatialMetadata createMetadata(final int imageIndex) throws IOException {
    SpatialMetadata metadata = super.createMetadata(imageIndex);
    if (imageIndex >= 0) {
        AffineTransform gridToCRS = null;
        CoordinateReferenceSystem crs = null;
        Object in = getVerifiedInput("tfw");
        if (in != null) {
            gridToCRS = SupportFiles.parseTFW(IOUtilities.open(in), in);
        }
        in = getVerifiedInput("prj");
        if (in != null) {
            crs = PrjFiles.read(IOUtilities.open(in), true);
        }
        /*
             * If we have found information in TFW or PRJ files, complete metadata.
             */
        if (gridToCRS != null || crs != null) {
            // -- if exist some metadata from sub reader complete them, else create new spatial metadata
            if (main instanceof SpatialImageReader) {
                metadata = ((SpatialImageReader) main).getImageMetadata(imageIndex);
            } else {
                metadata = new SpatialMetadata(false, this, null);
            }
            if (gridToCRS != null) {
                final int width = getWidth(imageIndex);
                final int height = getHeight(imageIndex);
                new GridDomainAccessor(metadata).setAll(gridToCRS, new Rectangle(width, height), null, PixelOrientation.UPPER_LEFT);
            }
            if (crs != null) {
                new ReferencingBuilder(metadata).setCoordinateReferenceSystem(crs);
            }
        }
    }
    return metadata;
}
Also used : SpatialMetadata(org.geotoolkit.image.io.metadata.SpatialMetadata) ReferencingBuilder(org.geotoolkit.image.io.metadata.ReferencingBuilder) Rectangle(java.awt.Rectangle) AffineTransform(java.awt.geom.AffineTransform) SpatialImageReader(org.geotoolkit.image.io.SpatialImageReader) CoordinateReferenceSystem(org.opengis.referencing.crs.CoordinateReferenceSystem) GridDomainAccessor(org.geotoolkit.internal.image.io.GridDomainAccessor)

Example 5 with SpatialMetadata

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

the class TextImageWriterTestBase method createMetadata.

/**
 * Creates dummy metadata for the image to be returned by {@link #createImage()}.
 */
private static IIOMetadata createMetadata() {
    final IIOMetadata metadata = new SpatialMetadata(SpatialMetadataFormat.getImageInstance(GEOTK_FORMAT_NAME));
    final GridDomainAccessor domain = new GridDomainAccessor(metadata);
    domain.setOrigin(-500, 400);
    domain.addOffsetVector(100, 0);
    domain.addOffsetVector(0, -100);
    final DimensionAccessor dimensions = new DimensionAccessor(metadata);
    dimensions.selectChild(dimensions.appendChild());
    dimensions.setValueRange(0f, 88.97f);
    // Intentionnaly use a value different than -9999.
    dimensions.setFillSampleValues(-9998);
    /*
         * Adds a Coordinate Reference System.
         * We use a simple Mercator projection.
         */
    try {
        new ReferencingBuilder(metadata).setCoordinateReferenceSystem(CRS.fromWKT(WKT.PROJCS_MERCATOR));
    } catch (FactoryException e) {
        fail(e.toString());
    }
    return metadata;
}
Also used : IIOMetadata(javax.imageio.metadata.IIOMetadata) SpatialMetadata(org.geotoolkit.image.io.metadata.SpatialMetadata) ReferencingBuilder(org.geotoolkit.image.io.metadata.ReferencingBuilder) FactoryException(org.opengis.util.FactoryException) GridDomainAccessor(org.geotoolkit.internal.image.io.GridDomainAccessor) DimensionAccessor(org.geotoolkit.internal.image.io.DimensionAccessor)

Aggregations

SpatialMetadata (org.geotoolkit.image.io.metadata.SpatialMetadata)29 DimensionAccessor (org.geotoolkit.internal.image.io.DimensionAccessor)9 IOException (java.io.IOException)6 GridDomainAccessor (org.geotoolkit.internal.image.io.GridDomainAccessor)6 CoordinateReferenceSystem (org.opengis.referencing.crs.CoordinateReferenceSystem)5 IIOMetadata (javax.imageio.metadata.IIOMetadata)4 SampleDimension (org.apache.sis.coverage.SampleDimension)4 SpatialImageReader (org.geotoolkit.image.io.SpatialImageReader)4 ReferencingBuilder (org.geotoolkit.image.io.metadata.ReferencingBuilder)4 RectifiedGrid (org.opengis.coverage.grid.RectifiedGrid)4 FactoryException (org.opengis.util.FactoryException)4 AffineTransform (java.awt.geom.AffineTransform)3 ImageReader (javax.imageio.ImageReader)3 MetadataHelper (org.geotoolkit.image.io.metadata.MetadataHelper)3 RenderedImage (java.awt.image.RenderedImage)2 File (java.io.File)2 ArrayList (java.util.ArrayList)2 HashMap (java.util.HashMap)2 List (java.util.List)2 Map (java.util.Map)2