/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2005-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2007-2012, Geomatys * * 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; * version 2.1 of the License. * * 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 org.geotoolkit.coverage.sql; import java.util.Set; import java.util.List; import java.util.Arrays; import java.util.Objects; import java.util.Locale; import java.util.HashSet; import java.util.Iterator; import javax.imageio.spi.IIORegistry; import javax.imageio.spi.ImageReaderSpi; import org.geotoolkit.coverage.Category; import org.geotoolkit.coverage.GridSampleDimension; import org.geotoolkit.coverage.grid.ViewType; import org.geotoolkit.coverage.io.GridCoverageStorePool; import org.geotoolkit.gui.swing.tree.MutableTreeNode; import org.geotoolkit.gui.swing.tree.DefaultMutableTreeNode; import org.geotoolkit.image.io.metadata.SampleDomain; import org.apache.sis.internal.util.UnmodifiableArrayList; import org.apache.sis.util.ArraysExt; import org.apache.sis.measure.MeasurementRange; import org.geotoolkit.internal.sql.table.DefaultEntry; import static org.geotoolkit.internal.InternalUtilities.adjustForRoundingError; /** * Information about an image format. * * @author Martin Desruisseaux (IRD, Geomatys) * @version 3.13 * * @since 3.09 (derived from Seagis) * @module */ final class FormatEntry extends DefaultEntry { /** * For cross-version compatibility. */ private static final long serialVersionUID = -8790032968708208057L; /** * The image format name as declared in the database. This value shall be a name * usable in calls to {@link javax.imageio.ImageIO#getImageReadersByFormatName}. * <p> * For compatibility reason, the user should be prepared to handle MIME type * (as understood by {@link javax.imageio.ImageIO#getImageReadersByMIMEType}). * as well. As a heuristic rule, we can consider this value as a MIME type if * it contains the {@code '/'} character. */ public final String imageFormat; /** * Alternative image format names or MIME types for the same reader than the one * identified by {@link #imageFormat}. This array is computed when first needed * from the list of registered {@link ImageReaderSpi}. */ private transient String[] imageFormats; /** * The sample dimensions for coverages encoded with this format, or {@code null} if undefined. * If non-null, then the list is guaranteed to be non-empty and the list size is equals to the * expected number of bands. * <p> * Empty lists are not allowed because our Image I/O framework interprets that as "no bands", * as opposed to "unknown bands" (which is what we mean in the particular case of our database * schema). * <p> * Each {@code SampleDimension} specifies how to convert pixel values to geophysics values, * or conversely. Their type (geophysics or not) is format dependent. For example coverages * read from PNG files will typically store their data as integer values (non-geophysics), * while coverages read from ASCII files will often store their pixel values as real numbers * (geophysics values). * * @see GridSampleDimension#geophysics(boolean) */ public final List<GridSampleDimension> sampleDimensions; /** * The range of valid sample values and the fill values for each sample dimension. */ public final List<SampleDomain> sampleDomains; /** * The name of the color palette, or {@code null} if unspecified. */ public final String paletteName; /** * {@code GEOPHYSICS} if the coverage to be read contains already geophysics values, or * {@code PACKED} if the coverage to be read use packed integers. */ public final ViewType viewType; /** * The pool of coverage loaders, to be created when first needed. We use a different pool * instance for each format in order to reuse the same {@link javax.imageio.ImageReader} * instance when a {@code coverageLoaders.acquireReader().read(...)} method is invoked. */ private transient GridCoverageStorePool coverageLoaders; /** * Creates a new entry for this format. * * @param name An identifier for this entry. * @param imageFormat The Image I/O format name (i.e. the plugin to use). * @param paletteName The name of the color palette, or {@code null} if unspecified. * @param bands Sample dimensions for coverages encoded with this format, or {@code null}. * The bands given to this constructor shall <strong>not</strong> be geophysics. */ protected FormatEntry(final String name, final String imageFormat, final String paletteName, final GridSampleDimension[] bands, final ViewType viewType, final String comments) { super(name, comments); this.imageFormat = imageFormat.trim(); if (bands != null) { final boolean geophysics = (viewType == ViewType.GEOPHYSICS); final SampleDomain[] domains = new SampleDomain[bands.length]; for (int i=0; i<bands.length; i++) { final GridSampleDimension band = bands[i]; domains[i] = new FormatSampleDomain(band); bands [i] = band.geophysics(geophysics); } sampleDimensions = UnmodifiableArrayList.wrap(bands); sampleDomains = UnmodifiableArrayList.wrap(domains); } else { sampleDimensions = null; sampleDomains = null; } this.paletteName = paletteName; this.viewType = viewType; } /** * Returns the name of this format. */ @Override public String getIdentifier() { return (String) super.getIdentifier(); } /** * Returns alternative image format names or MIME types for the same reader than the one * identified by {@link #imageFormat}. This array is computed from the list of registered * {@link ImageReaderSpi}. * * @return Alternative image format names. This method returns a direct reference to * its internal array - do not modify. */ public synchronized String[] getImageFormats() { if (imageFormats == null) { imageFormats = getImageFormats(imageFormat); } return imageFormats; } /** * Returns alternatives to the given image format name or MIME type. * * @param imageFormat The image format for which to search for alternatives. * @return Possible alternatives to the given image format. */ static String[] getImageFormats(final String imageFormat) { final Set<String> names = new HashSet<>(); for (final Iterator<ImageReaderSpi> it = IIORegistry.getDefaultInstance() .getServiceProviders(ImageReaderSpi.class, false); it.hasNext();) { final ImageReaderSpi spi = it.next(); final String[] candidates = spi.getFormatNames(); final String[] mimeTypes = spi.getMIMETypes(); if (ArraysExt.containsIgnoreCase(candidates, imageFormat) || ArraysExt.containsIgnoreCase(mimeTypes, imageFormat)) { names.addAll(Arrays.asList(candidates)); names.addAll(Arrays.asList(mimeTypes)); } } return names.toArray(new String[names.size()]); } /** * Returns the ranges of valid sample values for each band in this format. * The range are always expressed in <cite>geophysics</cite> values. */ final MeasurementRange<Double>[] getSampleValueRanges() { final List<GridSampleDimension> bands = sampleDimensions; if (bands == null) { return null; } @SuppressWarnings({"unchecked","rawtypes"}) // Generic array creation. final MeasurementRange<Double>[] ranges = new MeasurementRange[bands.size()]; for (int i=0; i<ranges.length; i++) { final GridSampleDimension band = bands.get(i).geophysics(true); /* * The call 'roundIfAlmostInteger' is a work-around for rounding error. We perform the * workaround here instead than at GridSampleDimension construction time because the * minimal and maximal values are the result of a computation, not a stored value. */ ranges[i] = MeasurementRange.create( adjustForRoundingError(band.getMinimumValue()), true, adjustForRoundingError(band.getMaximumValue()), true, band.getUnits()); } return ranges; } /** * Returns the pool of coverage loaders associated with this format. * * @return The pool of coverage loaders. */ public synchronized GridCoverageStorePool getCoverageLoaders() { if (coverageLoaders == null) { coverageLoaders = new GridCoverageLoader.Pool(this); } return coverageLoaders; } /** * Returns a tree representation of this format, including * {@linkplain SampleDimension sample dimensions} and {@linkplain Category categories}. * * @param locale The locale to use for formatting labels in the tree. * @return The tree root. */ public MutableTreeNode getTree(final Locale locale) { final DefaultMutableTreeNode root = new FormatTreeNode(this); if (sampleDimensions != null) { for (final GridSampleDimension band : sampleDimensions) { final List<Category> categories = band.getCategories(); final int categoryCount = categories.size(); final DefaultMutableTreeNode node = new FormatTreeNode(band, locale); for (int j=0; j<categoryCount; j++) { node.add(new FormatTreeNode(categories.get(j), locale)); } root.add(node); } } return root; } /** * Overridden as a safety, but should not be necessary since identifiers are supposed * to be unique in a given database. We don't compare the sample dimensions because * it may be costly. */ @Override public boolean equals(final Object object) { if (object == this) { return true; } if (super.equals(object)) { final FormatEntry that = (FormatEntry) object; return Objects.equals(imageFormat, that.imageFormat); } return false; } }