Source Code Cross Referenced for GridSampleDimension.java in  » GIS » GeoTools-2.4.1 » org » geotools » coverage » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » GIS » GeoTools 2.4.1 » org.geotools.coverage 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         *    GeoTools - OpenSource mapping toolkit
0003:         *    http://geotools.org
0004:         *    (C) 2003-2006, Geotools Project Management Committee (PMC)
0005:         *    (C) 2001, Institut de Recherche pour le Développement
0006:         *
0007:         *    This library is free software; you can redistribute it and/or
0008:         *    modify it under the terms of the GNU Lesser General Public
0009:         *    License as published by the Free Software Foundation; either
0010:         *    version 2.1 of the License, or (at your option) any later version.
0011:         *
0012:         *    This library is distributed in the hope that it will be useful,
0013:         *    but WITHOUT ANY WARRANTY; without even the implied warranty of
0014:         *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015:         *    Lesser General Public License for more details.
0016:         *
0017:         *    This package contains documentation from OpenGIS specifications.
0018:         *    OpenGIS consortium's work is fully acknowledged here.
0019:         */
0020:        package org.geotools.coverage;
0021:
0022:        // J2SE dependencies and extensions
0023:        import java.awt.Color;
0024:        import java.awt.image.ColorModel;
0025:        import java.awt.image.DataBuffer; // For javadoc
0026:        import java.awt.image.IndexColorModel;
0027:        import java.awt.image.RenderedImage;
0028:        import java.io.Serializable;
0029:        import java.util.ArrayList;
0030:        import java.util.Arrays;
0031:        import java.util.List;
0032:        import java.util.Locale;
0033:        import javax.units.Unit;
0034:
0035:        // JAI dependencies
0036:        import javax.media.jai.JAI;
0037:        import javax.media.jai.util.Range;
0038:
0039:        // OpenGIS dependencies
0040:        import org.geotools.util.SimpleInternationalString;
0041:        import org.opengis.coverage.ColorInterpretation;
0042:        import org.opengis.coverage.MetadataNameNotFoundException;
0043:        import org.opengis.coverage.PaletteInterpretation;
0044:        import org.opengis.coverage.SampleDimension;
0045:        import org.opengis.coverage.SampleDimensionType;
0046:        import org.opengis.referencing.operation.MathTransform1D;
0047:        import org.opengis.referencing.operation.TransformException;
0048:        import org.opengis.util.InternationalString;
0049:
0050:        // Geotools dependencies
0051:        import org.geotools.referencing.operation.transform.LinearTransform1D;
0052:        import org.geotools.resources.ClassChanger;
0053:        import org.geotools.resources.Utilities;
0054:        import org.geotools.resources.XArray;
0055:        import org.geotools.resources.XMath;
0056:        import org.geotools.resources.i18n.Errors;
0057:        import org.geotools.resources.i18n.ErrorKeys;
0058:        import org.geotools.resources.i18n.Vocabulary;
0059:        import org.geotools.resources.i18n.VocabularyKeys;
0060:        import org.geotools.resources.image.ColorUtilities;
0061:        import org.geotools.util.NumberRange;
0062:
0063:        /**
0064:         * Describes the data values for a coverage as a list of {@linkplain Category categories}. For
0065:         * a grid coverage a sample dimension is a band. Sample values in a band may be organized in
0066:         * categories. This {@code GridSampleDimension} implementation is capable to differenciate
0067:         * <em>qualitative</em> and <em>quantitative</em> categories. For example an image of sea surface
0068:         * temperature (SST) could very well defines the following categories:
0069:         *
0070:         * <blockquote><pre>
0071:         *   [0]       : no data
0072:         *   [1]       : cloud
0073:         *   [2]       : land
0074:         *   [10..210] : temperature to be converted into Celsius degrees through a linear equation
0075:         * </pre></blockquote>
0076:         *
0077:         * In this example, sample values in range {@code [10..210]} defines a quantitative category,
0078:         * while all others categories are qualitative. The difference between those two kinds of category
0079:         * is that the {@link Category#getSampleToGeophysics} method returns a non-null transform if and
0080:         * only if the category is quantitative.
0081:         * <p>
0082:         * While this class can be used with arbitrary {@linkplain org.opengis.coverage.Coverage coverage},
0083:         * the primary target for this implementation is {@linkplain org.opengis.coverage.grid.GridCoverage
0084:         * grid coverage} storing their sample values as integers. This explain the "{@code Grid}" prefix
0085:         * in the class name.
0086:         *
0087:         * @since 2.1
0088:         * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/GridSampleDimension.java $
0089:         * @version $Id: GridSampleDimension.java 23398 2006-12-12 05:57:00Z desruisseaux $
0090:         * @author Martin Desruisseaux
0091:         */
0092:        public class GridSampleDimension implements  SampleDimension,
0093:                Serializable {
0094:            /**
0095:             * Serial number for interoperability with different versions.
0096:             */
0097:            private static final long serialVersionUID = 6026936545776852758L;
0098:
0099:            /**
0100:             * An empty array of metadata names.
0101:             */
0102:            private static final String[] EMPTY_METADATA = new String[0];
0103:
0104:            /**
0105:             * A sample dimension wrapping the list of categories {@code CategoryList.inverse}.
0106:             * This object is constructed and returned by {@link #geophysics}. Constructed when first
0107:             * needed, but serialized anyway because it may be a user-supplied object.
0108:             */
0109:            private GridSampleDimension inverse;
0110:
0111:            /**
0112:             * The category list for this sample dimension, or {@code null} if this sample
0113:             * dimension has no category. This field is read by {@code SampleTranscoder} only.
0114:             */
0115:            final CategoryList categories;
0116:
0117:            /**
0118:             * {@code true} if all categories in this sample dimension have been already scaled
0119:             * to geophysics ranges. If {@code true}, then the {@link #getSampleToGeophysics()}
0120:             * method should returns an identity transform. Note that the opposite do not always hold:
0121:             * an identity transform doesn't means that all categories are geophysics. For example,
0122:             * some qualitative categories may map to some values differents than {@code NaN}.
0123:             * <p>
0124:             * Assertions:
0125:             *  <ul>
0126:             *    <li>{@code isGeophysics} == {@code categories.isScaled(true)}.</li>
0127:             *    <li>{@code isGeophysics} != {@code categories.isScaled(false)}, except
0128:             *        if {@code categories.geophysics(true) == categories.geophysics(false)}</li>
0129:             * </ul>
0130:             */
0131:            private final boolean isGeophysics;
0132:
0133:            /**
0134:             * {@code true} if this sample dimension has at least one qualitative category.
0135:             * An arbitrary number of qualitative categories is allowed, providing their sample
0136:             * value ranges do not overlap. A sample dimension can have both qualitative and
0137:             * quantitative categories.
0138:             */
0139:            private final boolean hasQualitative;
0140:
0141:            /**
0142:             * {@code true} if this sample dimension has at least one quantitative category.
0143:             * An arbitrary number of quantitative categories is allowed, providing their sample
0144:             * value ranges do not overlap.
0145:             * <p>
0146:             * If {@code sampleToGeophysics} is non-null, then {@code hasQuantitative}
0147:             * <strong>must</strong> be true.  However, the opposite do not hold in all cases: a
0148:             * {@code true} value doesn't means that {@code sampleToGeophysics} should
0149:             * be non-null.
0150:             */
0151:            private final boolean hasQuantitative;
0152:
0153:            /**
0154:             * The {@link Category#getSampleToGeophysics sampleToGeophysics} transform used by every
0155:             * quantitative {@link Category}, or {@code null}. This field may be null for two
0156:             * reasons:
0157:             *
0158:             * <ul>
0159:             *   <li>There is no quantitative category in this sample dimension.</li>
0160:             *   <li>There is more than one quantitative category, and all of them
0161:             *       don't use the same {@link Category#getSampleToGeophysics
0162:             *       sampleToGeophysics} transform.</li>
0163:             * </ul>
0164:             *
0165:             * This field is used by {@link #getOffset} and {@link #getScale}. The
0166:             * {@link #getSampleToGeophysics} method may also returns directly this
0167:             * value in some conditions.
0168:             */
0169:            private final MathTransform1D sampleToGeophysics;
0170:
0171:            /**
0172:             * Decription for this sample dimension. It is particularly important to
0173:             * have the possiblity to specify a description for a sample dimension in
0174:             * order to be able to perform a band select by using human comprehensible
0175:             * descriptions instead of just numbers. As an instance a service like the
0176:             * WCS would use this feature in order to perform band subsetting as
0177:             * directed from a user request.
0178:             */
0179:            private final InternationalString description;
0180:
0181:            /**
0182:             * Constructs a sample dimension with no description and no category.
0183:             *
0184:             * @deprecated Use {@link #GridSampleDimension(CharSequence)} instead.
0185:             */
0186:            public GridSampleDimension() {
0187:                this (null, (CategoryList) null);
0188:            }
0189:
0190:            /**
0191:             * Constructs a sample dimension with specified name and no category.
0192:             *
0193:             * @param description
0194:             *            The sample dimension title or description, or {@code null} if
0195:             *            none. This is the value to be returned by {@link #getDescription}.
0196:             *
0197:             * @since 2.3
0198:             */
0199:            public GridSampleDimension(final CharSequence description) {
0200:                this (description, (CategoryList) null);
0201:            }
0202:
0203:            /**
0204:             * Constructs a sample dimension with a set of qualitative categories only.
0205:             * This constructor expects only a sequence of category names for the values
0206:             * contained in a sample dimension. This allows for names to be assigned to
0207:             * numerical values. The first entry in the sequence relates to a cell value
0208:             * of zero. For example: [0]="Background", [1]="Water", [2]="Forest",
0209:             * [3]="Urban". The created sample dimension will have no unit and a default
0210:             * set of colors.
0211:             * 
0212:             * @param description
0213:             *            The sample dimension title or description, or {@code null} for the default
0214:             *            (the name of what looks like the "main" category). This is the value to be
0215:             *            returned by {@link #getDescription}.
0216:             * @param categoriesNames
0217:             *            Sequence of category names for the values contained in a
0218:             *            sample dimension, as {@link String} or
0219:             *            {@link InternationalString} objects.
0220:             *
0221:             * @since 2.3
0222:             */
0223:            public GridSampleDimension(final CharSequence description,
0224:                    final CharSequence[] categoriesNames) {
0225:                // TODO: 'list(...)' should be inlined there if only Sun was to fix RFE #4093999
0226:                // ("Relax constraint on placement of this()/super() call in constructors").
0227:                this (description, list(categoriesNames));
0228:            }
0229:
0230:            /**
0231:             * Constructs a sample dimension with a set of qualitative categories only.
0232:             * This constructor expects only a sequence of category names for the values
0233:             * contained in a sample dimension. This allows for names to be assigned to
0234:             * numerical values. The first entry in the sequence relates to a cell value
0235:             * of zero. For example: [0]="Background", [1]="Water", [2]="Forest",
0236:             * [3]="Urban". The created sample dimension will have no unit and a default
0237:             * set of colors.
0238:             * 
0239:             * @param categoriesNames
0240:             *            Sequence of category names for the values contained in a
0241:             *            sample dimension, as {@link String} or
0242:             *            {@link InternationalString} objects.
0243:             *
0244:             * @deprecated Replaced by {@link #GridSampleDimension(CharSequence, CharSequence[])}.
0245:             */
0246:            public GridSampleDimension(final CharSequence[] categoriesNames) {
0247:                this (null, categoriesNames);
0248:            }
0249:
0250:            /** Constructs a list of categories. Used by constructors only. */
0251:            private static CategoryList list(final CharSequence[] names) {
0252:                final int length = names.length;
0253:                final Color[] colors = new Color[length];
0254:                final double scale = 255.0 / length;
0255:                for (int i = 0; i < length; i++) {
0256:                    final int r = (int) Math.round(scale * i);
0257:                    colors[i] = new Color(r, r, r);
0258:                }
0259:                return list(names, colors);
0260:            }
0261:
0262:            /**
0263:             * Constructs a sample dimension with a set of qualitative categories and
0264:             * colors. This constructor expects a sequence of category names for the
0265:             * values contained in a sample dimension. This allows for names to be
0266:             * assigned to numerical values. The first entry in the sequence relates to
0267:             * a cell value of zero. For example: [0]="Background", [1]="Water",
0268:             * [2]="Forest", [3]="Urban". The created sample dimension will have no unit
0269:             * and a default set of colors.
0270:             * 
0271:             * @param description
0272:             *            The sample dimension title or description, or {@code null} for the default
0273:             *            (the name of what looks like the "main" category). This is the value to be
0274:             *            returned by {@link #getDescription}.
0275:             * @param names
0276:             *            Sequence of category names for the values contained in a
0277:             *            sample dimension, as {@link String} or
0278:             *            {@link InternationalString} objects.
0279:             * @param colors
0280:             *            Color to assign to each category. This array must have the
0281:             *            same length than {@code names}.
0282:             *
0283:             * @since 2.3
0284:             */
0285:            public GridSampleDimension(final CharSequence description,
0286:                    final CharSequence[] names, final Color[] colors) {
0287:                // TODO: 'list(...)' should be inlined there if only Sun was to fix RFE #4093999
0288:                // ("Relax constraint on placement of this()/super() call in constructors").
0289:                this (description, list(names, colors));
0290:            }
0291:
0292:            /**
0293:             * Constructs a sample dimension with a set of qualitative categories and
0294:             * colors. This constructor expects a sequence of category names for the
0295:             * values contained in a sample dimension. This allows for names to be
0296:             * assigned to numerical values. The first entry in the sequence relates to
0297:             * a cell value of zero. For example: [0]="Background", [1]="Water",
0298:             * [2]="Forest", [3]="Urban". The created sample dimension will have no unit
0299:             * and a default set of colors.
0300:             * 
0301:             * @param names
0302:             *            Sequence of category names for the values contained in a
0303:             *            sample dimension, as {@link String} or
0304:             *            {@link InternationalString} objects.
0305:             * @param colors
0306:             *            Color to assign to each category. This array must have the
0307:             *            same length than {@code names}.
0308:             *
0309:             * @deprecated Replaced by {@link #GridSampleDimension(CharSequence, CharSequence[], Color[])}.
0310:             */
0311:            public GridSampleDimension(final CharSequence[] names,
0312:                    final Color[] colors) {
0313:                this (null, names, colors);
0314:            }
0315:
0316:            /** Constructs a list of categories. Used by constructors only. */
0317:            private static CategoryList list(final CharSequence[] names,
0318:                    final Color[] colors) {
0319:                if (names.length != colors.length) {
0320:                    throw new IllegalArgumentException(Errors
0321:                            .format(ErrorKeys.MISMATCHED_ARRAY_LENGTH));
0322:                }
0323:                final int length = names.length;
0324:                final Category[] categories = new Category[length];
0325:                for (int i = 0; i < length; i++) {
0326:                    categories[i] = new Category(names[i], colors[i], i);
0327:                }
0328:                return list(categories, null);
0329:            }
0330:
0331:            /**
0332:             * Constructs a sample dimension with the specified properties. For
0333:             * convenience, any argument which is not a {@code double} primitive can be
0334:             * {@code null}, and any {@linkplain CharSequence char sequence} can be
0335:             * either a {@link String} or {@link InternationalString} object.
0336:             * <p>
0337:             * This constructor allows the construction of a {@code GridSampleDimension}
0338:             * without explicit construction of {@link Category} objects. An heuristic
0339:             * approach is used for dispatching the informations into a set of
0340:             * {@link Category} objects. However, this constructor still less general
0341:             * and provides less fine-grain control than the constructor expecting an
0342:             * array of {@link Category} objects.
0343:             * 
0344:             * @param description
0345:             *            The sample dimension title or description, or {@code null} for the default
0346:             *            (the name of what looks like the "main" category). This is the value to be
0347:             *            returned by {@link #getDescription}.
0348:             * @param type
0349:             *            The grid value data type (which indicate the number of bits
0350:             *            for the data type), or {@code null} for computing it
0351:             *            automatically from the range {@code [minimum..maximum]}. This
0352:             *            is the value to be returned by {@link #getSampleDimensionType}.
0353:             * @param color
0354:             *            The color interpretation, or {@code null} for a default value
0355:             *            (usually
0356:             *            {@link ColorInterpretation#PALETTE_INDEX PALETTE_INDEX}).
0357:             *            This is the value to be returned by
0358:             *            {@link #getColorInterpretation}.
0359:             * @param palette
0360:             *            The color palette associated with the sample dimension, or
0361:             *            {@code null} for a default color palette (usually grayscale).
0362:             *            If {@code categories} is non-null, then both arrays usually
0363:             *            have the same length. However, this constructor is tolerant on
0364:             *            this array length. This is the value to be returned
0365:             *            (indirectly) by {@link #getColorModel}.
0366:             * @param categories
0367:             *            A sequence of category names for the values contained in the
0368:             *            sample dimension, or {@code null} if none. This is the values
0369:             *            to be returned by {@link #getCategoryNames}.
0370:             * @param nodata
0371:             *            the values to indicate "no data", or {@code null} if none.
0372:             *            This is the values to be returned by {@link #getNoDataValues}.
0373:             * @param minimum
0374:             *            The lower value, inclusive. The {@code [minimum..maximum]}
0375:             *            range may or may not includes the {@code nodata} values; the
0376:             *            range will be adjusted as needed. If {@code categories} was
0377:             *            non-null, then {@code minimum} is usually 0. This is the value
0378:             *            to be returned by {@link #getMinimumValue}.
0379:             * @param maximum
0380:             *            The upper value, <strong>inclusive</strong> as well. The
0381:             *            {@code [minimum..maximum]} range may or may not includes the
0382:             *            {@code nodata} values; the range will be adjusted as needed.
0383:             *            If {@code categories} was non-null, then {@code maximum} is
0384:             *            usually equals to {@code categories.length-1}. This is the
0385:             *            value to be returned by {@link #getMaximumValue}.
0386:             * @param scale
0387:             *            The value which is multiplied to grid values, or 1 if none.
0388:             *            This is the value to be returned by {@link #getScale}.
0389:             * @param offset
0390:             *            The value to add to grid values, or 0 if none. This is the
0391:             *            value to be returned by {@link #getOffset}.
0392:             * @param unit
0393:             *            The unit information for this sample dimension, or
0394:             *            {@code null} if none. This is the value to be returned by
0395:             *            {@link #getUnits}.
0396:             * 
0397:             * @throws IllegalArgumentException
0398:             *             if the range {@code [minimum..maximum]} is not valid.
0399:             */
0400:            public GridSampleDimension(final CharSequence description,
0401:                    final SampleDimensionType type,
0402:                    final ColorInterpretation color, final Color[] palette,
0403:                    final CharSequence[] categories, final double[] nodata,
0404:                    final double minimum, final double maximum,
0405:                    final double scale, final double offset, final Unit unit) {
0406:                // TODO: 'list(...)' should be inlined there if only Sun was to fix RFE #4093999
0407:                //       ("Relax constraint on placement of this()/super() call in constructors").        
0408:                this (description, list(description, type, color, palette,
0409:                        categories, nodata, minimum, maximum, scale, offset,
0410:                        unit));
0411:            }
0412:
0413:            /** Constructs a list of categories. Used by constructors only. */
0414:            private static CategoryList list(CharSequence description,
0415:                    SampleDimensionType type, ColorInterpretation color,
0416:                    final Color[] palette, final CharSequence[] categories,
0417:                    final double[] nodata, double minimum, double maximum,
0418:                    final double scale, final double offset, final Unit unit) {
0419:                if (description == null) {
0420:                    description = Vocabulary
0421:                            .formatInternational(VocabularyKeys.UNTITLED);
0422:                }
0423:                if (Double.isInfinite(minimum) || Double.isInfinite(maximum)
0424:                        || !(minimum < maximum)) {
0425:                    throw new IllegalArgumentException(Errors.format(
0426:                            ErrorKeys.BAD_RANGE_$2, new Double(minimum),
0427:                            new Double(maximum)));
0428:                }
0429:                if (Double.isNaN(scale) || Double.isInfinite(scale)
0430:                        || scale == 0) {
0431:                    throw new IllegalArgumentException(Errors.format(
0432:                            ErrorKeys.BAD_PARAMETER_$2, "scale", new Double(
0433:                                    scale)));
0434:                }
0435:                if (Double.isNaN(offset) || Double.isInfinite(offset)) {
0436:                    throw new IllegalArgumentException(Errors.format(
0437:                            ErrorKeys.BAD_PARAMETER_$2, "offset", new Double(
0438:                                    offset)));
0439:                }
0440:                if (type == null) {
0441:                    type = TypeMap.getSampleDimensionType(minimum, maximum);
0442:                }
0443:                if (color == null) {
0444:                    color = ColorInterpretation.PALETTE_INDEX;
0445:                }
0446:                final int nameCount = (categories != null) ? categories.length
0447:                        : 0;
0448:                final int nodataCount = (nodata != null) ? nodata.length : 0;
0449:                final List categoryList = new ArrayList(nameCount + nodataCount
0450:                        + 2);
0451:                /*
0452:                 * STEP 1 - Add a qualitative category for each 'nodata' value.
0453:                 *   NAME: Fetched from 'categories' if available, otherwise default to the value.
0454:                 *   COLOR: Fetched from 'palette' if available, otherwise use Category default.
0455:                 */
0456:                for (int i = 0; i < nodataCount; i++) {
0457:                    CharSequence name = null;
0458:                    final double padValue = nodata[i];
0459:                    final int intValue = (int) Math.floor(padValue);
0460:                    if (intValue >= 0 && intValue < nameCount) {
0461:                        if (intValue == padValue) {
0462:                            // This category will be added in step 2 below.
0463:                            continue;
0464:                        }
0465:                        name = categories[intValue];
0466:                    }
0467:                    final Number value = TypeMap.wrapSample(padValue, type,
0468:                            false);
0469:                    if (name == null) {
0470:                        name = value.toString();
0471:                    }
0472:                    final NumberRange range = new NumberRange(value.getClass(),
0473:                            value, value);
0474:                    final Color[] colors = ColorUtilities.subarray(palette,
0475:                            intValue, intValue + 1);
0476:                    categoryList.add(new Category(name, colors, range,
0477:                            (MathTransform1D) null));
0478:                }
0479:                /*
0480:                 * STEP 2 - Add a qualitative category for each category name.
0481:                 *   RANGE: Fetched from the index (position) in the 'categories' array.
0482:                 *   COLOR: Fetched from 'palette' if available, otherwise use Category default.
0483:                 */
0484:                if (nameCount != 0) {
0485:                    int lower = 0;
0486:                    final int length = categories.length;
0487:                    for (int upper = 1; upper <= length; upper++) {
0488:                        if (upper != length
0489:                                && categories[lower].toString().trim()
0490:                                        .equalsIgnoreCase(
0491:                                                categories[upper].toString()
0492:                                                        .trim())) {
0493:                            // If there is a suite of categories with identical name,  create only one
0494:                            // category with range [lower..upper] instead of one new category for each
0495:                            // sample value.
0496:                            continue;
0497:                        }
0498:                        final CharSequence name = categories[lower];
0499:                        Number min = TypeMap.wrapSample(lower, type, false);
0500:                        Number max = TypeMap.wrapSample(upper - 1, type, false);
0501:                        final Class classe;
0502:                        if (min.equals(max)) {
0503:                            min = max;
0504:                            classe = max.getClass();
0505:                        } else {
0506:                            classe = ClassChanger.getWidestClass(min, max);
0507:                            min = ClassChanger.cast(min, classe);
0508:                            max = ClassChanger.cast(max, classe);
0509:                        }
0510:                        final NumberRange range = new NumberRange(classe, min,
0511:                                max);
0512:                        final Color[] colors = ColorUtilities.subarray(palette,
0513:                                lower, upper);
0514:                        categoryList.add(new Category(name, colors, range,
0515:                                (MathTransform1D) null));
0516:                        lower = upper;
0517:                    }
0518:                }
0519:                /*
0520:                 * STEP 3 - Changes some qualitative categories into quantitative ones.  The hard questions
0521:                 *          is: do we want to mark a category as "quantitative"?   OpenGIS has no notion of
0522:                 *          "qualitative" versus "quantitative" category. As an heuristic approach, we will
0523:                 *          look for quantitative category if:
0524:                 *
0525:                 *          - 'scale' and 'offset' do not map to an identity transform. Those
0526:                 *            coefficients can be stored in quantitative category only.
0527:                 *
0528:                 *          - 'nodata' were specified. If the user wants to declare "nodata" values,
0529:                 *            then we can reasonably assume that he have real values somewhere else.
0530:                 *
0531:                 *          - Only 1 category were created so far. A classified raster with only one
0532:                 *            category is useless. Consequently, it is probably a numeric raster instead.
0533:                 */
0534:                boolean needQuantitative = false;
0535:                if (scale != 1 || offset != 0 || nodataCount != 0
0536:                        || categoryList.size() <= 1) {
0537:                    needQuantitative = true;
0538:                    for (int i = categoryList.size(); --i >= 0;) {
0539:                        Category category = (Category) categoryList.get(i);
0540:                        if (!category.isQuantitative()) {
0541:                            final NumberRange range = category.getRange();
0542:                            final Comparable min = range.getMinValue();
0543:                            final Comparable max = range.getMaxValue();
0544:                            if (min.compareTo(max) != 0) {
0545:                                final double xmin = ((Number) min)
0546:                                        .doubleValue();
0547:                                final double xmax = ((Number) max)
0548:                                        .doubleValue();
0549:                                if (!rangeContains(xmin, xmax, nodata)) {
0550:                                    final InternationalString name = category
0551:                                            .getName();
0552:                                    final Color[] colors = category.getColors();
0553:                                    category = new Category(name, colors,
0554:                                            range, scale, offset);
0555:                                    categoryList.set(i, category);
0556:                                    needQuantitative = false;
0557:                                }
0558:                            }
0559:                        }
0560:                    }
0561:                }
0562:                /*
0563:                 * STEP 4 - Create at most one quantitative category for the remaining sample values.
0564:                 *          The new category will range from 'minimum' to 'maximum' inclusive, minus
0565:                 *          all ranges used by previous categories.  If there is no range left, then
0566:                 *          no new category will be created.  This step will be executed only if the
0567:                 *          information provided by the user seem to be incomplete.
0568:                 *
0569:                 *          Note that substractions way break a range into many smaller ranges.
0570:                 *          The naive algorithm used here try to keep the widest range.
0571:                 */
0572:                if (needQuantitative) {
0573:                    boolean minIncluded = true;
0574:                    boolean maxIncluded = true;
0575:                    for (int i = categoryList.size(); --i >= 0;) {
0576:                        final NumberRange range = ((Category) categoryList
0577:                                .get(i)).getRange();
0578:                        final double min = range.getMinimum();
0579:                        final double max = range.getMaximum();
0580:                        if (max - minimum < maximum - min) {
0581:                            if (max >= minimum) {
0582:                                // We are loosing some sample values in
0583:                                // the lower range because of nodata values.
0584:                                minimum = max;
0585:                                minIncluded = !range.isMaxIncluded();
0586:                            }
0587:                        } else {
0588:                            if (min <= maximum) {
0589:                                // We are loosing some sample values in
0590:                                // the upper range because of nodata values.
0591:                                maximum = min;
0592:                                maxIncluded = !range.isMinIncluded();
0593:                            }
0594:                        }
0595:                    }
0596:                    // If the remaining range is wide enough, add the category.
0597:                    if (maximum - minimum > (minIncluded && maxIncluded ? 0 : 1)) {
0598:                        Number min = TypeMap.wrapSample(minimum, type, false);
0599:                        Number max = TypeMap.wrapSample(maximum, type, false);
0600:                        final Class classe = ClassChanger.getWidestClass(min,
0601:                                max);
0602:                        min = ClassChanger.cast(min, classe);
0603:                        max = ClassChanger.cast(max, classe);
0604:                        final NumberRange range = new NumberRange(classe, min,
0605:                                minIncluded, max, maxIncluded);
0606:                        final Color[] colors = ColorUtilities.subarray(palette,
0607:                                (int) Math.ceil(minimum), (int) Math
0608:                                        .floor(maximum));
0609:                        categoryList.add(new Category(description, colors,
0610:                                range, scale, offset));
0611:                        needQuantitative = false;
0612:                    }
0613:                }
0614:                /*
0615:                 * STEP 5 - Now, the list of categories should be complete. Construct a
0616:                 *          sample dimension appropriate for the type of palette used.
0617:                 */
0618:                final Category[] cl = (Category[]) categoryList
0619:                        .toArray(new Category[categoryList.size()]);
0620:                if (ColorInterpretation.PALETTE_INDEX.equals(color)
0621:                        || ColorInterpretation.GRAY_INDEX.equals(color)) {
0622:                    return list(cl, unit);
0623:                }
0624:                throw new UnsupportedOperationException("Not yet implemented");
0625:            }
0626:
0627:            /**
0628:             * Constructs a sample dimension with an arbitrary set of categories, which
0629:             * may be both quantitative and qualitative. It is possible to specify more
0630:             * than one quantitative categories, providing that their sample value
0631:             * ranges do not overlap. Quantitative categories can map sample values to
0632:             * geophysics values using arbitrary relation (not necessarly linear).
0633:             * 
0634:             * @param description
0635:             *            The sample dimension title or description, or {@code null} for the default
0636:             *            (the name of what looks like the "main" category). This is the value to be
0637:             *            returned by {@link #getDescription}.
0638:             * @param categories
0639:             *            The list of categories.
0640:             * @param units
0641:             *            The unit information for this sample dimension. May be
0642:             *            {@code null} if no category has units. This unit apply to
0643:             *            values obtained after the
0644:             *            {@link #getSampleToGeophysics sampleToGeophysics}
0645:             *            transformation.
0646:             * @throws IllegalArgumentException
0647:             *             if {@code categories} contains incompatible categories. If
0648:             *             may be the case for example if two or more categories have
0649:             *             overlapping ranges of sample values.
0650:             *
0651:             * @since 2.3
0652:             */
0653:            public GridSampleDimension(CharSequence description,
0654:                    Category[] categories, Unit units)
0655:                    throws IllegalArgumentException {
0656:                // TODO: 'list(...)' should be inlined there if only Sun was to fix RFE #4093999
0657:                // ("Relax constraint on placement of this()/super() call in constructors").
0658:                this (description, list(categories, units));
0659:            }
0660:
0661:            /**
0662:             * Constructs a sample dimension with an arbitrary set of categories, which
0663:             * may be both quantitative and qualitative. It is possible to specify more
0664:             * than one quantitative categories, providing that their sample value
0665:             * ranges do not overlap. Quantitative categories can map sample values to
0666:             * geophysics values using arbitrary relation (not necessarly linear).
0667:             * 
0668:             * @param categories
0669:             *            The list of categories.
0670:             * @param units
0671:             *            The unit information for this sample dimension. May be
0672:             *            {@code null} if no category has units. This unit apply to
0673:             *            values obtained after the
0674:             *            {@link #getSampleToGeophysics sampleToGeophysics}
0675:             *            transformation.
0676:             * @throws IllegalArgumentException
0677:             *             if {@code categories} contains incompatible categories. If
0678:             *             may be the case for example if two or more categories have
0679:             *             overlapping ranges of sample values.
0680:             *
0681:             * @deprecated Use {@link #GridSampleDimension(CharSequence, Category[], Unit)} instead.
0682:             */
0683:            public GridSampleDimension(final Category[] categories,
0684:                    final Unit units) throws IllegalArgumentException {
0685:                this (null, categories, units);
0686:            }
0687:
0688:            /** Construct a list of categories. Used by constructors only. */
0689:            private static CategoryList list(final Category[] categories,
0690:                    final Unit units) {
0691:                if (categories == null) {
0692:                    return null;
0693:                }
0694:                final CategoryList list = new CategoryList(categories, units);
0695:                if (CategoryList.isScaled(categories, false))
0696:                    return list;
0697:                if (CategoryList.isScaled(categories, true))
0698:                    return list.inverse;
0699:                throw new IllegalArgumentException(Errors
0700:                        .format(ErrorKeys.MIXED_CATEGORIES));
0701:            }
0702:
0703:            /**
0704:             * Constructs a sample dimension with the specified list of categories.
0705:             * 
0706:             * @param description
0707:             *            The sample dimension title or description, or {@code null} for the default
0708:             *            (the name of what looks like the "main" category). This is the value to be
0709:             *            returned by {@link #getDescription}.
0710:             * @param list
0711:             *            The list of categories, or {@code null}.
0712:             */
0713:            private GridSampleDimension(final CharSequence description,
0714:                    final CategoryList list) {
0715:                /*
0716:                 * Checks the supplied description to see if it is null. In such a case it
0717:                 * builds up a new description by using the list of categories supplied.
0718:                 * This secondo description is much less human readable and it is therefore
0719:                 * much better if the user provide a meaningful name for this sample
0720:                 * dimension.
0721:                 */
0722:                if (description != null) {
0723:                    this .description = SimpleInternationalString
0724:                            .wrap(description);
0725:                } else {
0726:                    // we need to build one. Let's use the category list in
0727:                    // order to build the name of the sample dimension
0728:                    if (list != null) {
0729:                        this .description = list.getName();
0730:                    } else {
0731:                        this .description = Vocabulary
0732:                                .formatInternational(VocabularyKeys.UNTITLED);
0733:                    }
0734:                }
0735:                /*
0736:                 * Now process to the category examination.
0737:                 */
0738:                MathTransform1D main = null;
0739:                boolean isMainValid = true;
0740:                boolean qualitative = false;
0741:                if (list != null) {
0742:                    for (int i = list.size(); --i >= 0;) {
0743:                        final MathTransform1D candidate = ((Category) list
0744:                                .get(i)).getSampleToGeophysics();
0745:                        if (candidate == null) {
0746:                            qualitative = true;
0747:                            continue;
0748:                        }
0749:                        if (main != null) {
0750:                            isMainValid &= main.equals(candidate);
0751:                        }
0752:                        main = candidate;
0753:                    }
0754:                    this .isGeophysics = list.isScaled(true);
0755:                } else {
0756:                    this .isGeophysics = false;
0757:                }
0758:                this .categories = list;
0759:                this .hasQualitative = qualitative;
0760:                this .hasQuantitative = (main != null);
0761:                this .sampleToGeophysics = isMainValid ? main : null;
0762:            }
0763:
0764:            /**
0765:             * Constructs a new sample dimension with the same categories and
0766:             * units than the specified sample dimension.
0767:             *
0768:             * @param other The other sample dimension, or {@code null}.
0769:             */
0770:            protected GridSampleDimension(final GridSampleDimension other) {
0771:                if (other != null) {
0772:                    inverse = other.inverse;
0773:                    categories = other.categories;
0774:                    isGeophysics = other.isGeophysics;
0775:                    hasQualitative = other.hasQualitative;
0776:                    hasQuantitative = other.hasQuantitative;
0777:                    sampleToGeophysics = other.sampleToGeophysics;
0778:                    description = other.description;
0779:                } else {
0780:                    // 'inverse' will be set when needed.
0781:                    categories = null;
0782:                    isGeophysics = false;
0783:                    hasQualitative = false;
0784:                    hasQuantitative = false;
0785:                    sampleToGeophysics = null;
0786:                    description = Vocabulary
0787:                            .formatInternational(VocabularyKeys.UNTITLED);
0788:                }
0789:            }
0790:
0791:            /**
0792:             * Wrap the specified OpenGIS's sample dimension into a Geotools's
0793:             * implementation of {@code GridSampleDimension}.
0794:             * 
0795:             * @param sd
0796:             *            The sample dimension to wrap into a Geotools implementation.
0797:             */
0798:            public static GridSampleDimension wrap(final SampleDimension sd) {
0799:                if (sd instanceof  GridSampleDimension) {
0800:                    return (GridSampleDimension) sd;
0801:                }
0802:                final int[][] palette = sd.getPalette();
0803:                final Color[] colors;
0804:                if (palette != null) {
0805:                    final int length = palette.length;
0806:                    colors = new Color[length];
0807:                    for (int i = 0; i < length; i++) {
0808:                        // Assuming RGB. It will be checked in the constructor.
0809:                        final int[] color = palette[i];
0810:                        colors[i] = new Color(color[0], color[1], color[2]);
0811:                    }
0812:                } else {
0813:                    colors = null;
0814:                }
0815:                return new GridSampleDimension(sd.getDescription(), sd
0816:                        .getSampleDimensionType(), sd.getColorInterpretation(),
0817:                        colors, sd.getCategoryNames(), sd.getNoDataValues(), sd
0818:                                .getMinimumValue(), sd.getMaximumValue(), sd
0819:                                .getScale(), sd.getOffset(), sd.getUnits());
0820:            }
0821:
0822:            /**
0823:             * Returns a code value indicating grid value data type.
0824:             * This will also indicate the number of bits for the data type.
0825:             *
0826:             * @return a code value indicating grid value data type.
0827:             */
0828:            public SampleDimensionType getSampleDimensionType() {
0829:                final NumberRange range = getRange();
0830:                if (range == null) {
0831:                    return SampleDimensionType.REAL_32BITS;
0832:                }
0833:                return TypeMap.getSampleDimensionType(range);
0834:            }
0835:
0836:            /**
0837:             * Get the sample dimension title or description.
0838:             * This string may be {@code null} if no description is present.
0839:             */
0840:            public InternationalString getDescription() {
0841:                return description;
0842:            }
0843:
0844:            /**
0845:             * Returns a sequence of category names for the values contained in this sample dimension.
0846:             * This allows for names to be assigned to numerical values. The first entry in the sequence
0847:             * relates to a cell value of zero. For example:
0848:             *
0849:             *  <blockquote><pre>
0850:             *    [0] Background
0851:             *    [1] Water
0852:             *    [2] Forest
0853:             *    [3] Urban
0854:             *  </pre></blockquote>
0855:             *
0856:             * @return The sequence of category names for the values contained in this sample dimension,
0857:             *         or {@code null} if there is no category in this sample dimension.
0858:             * @throws IllegalStateException if a sequence can't be mapped because some category use
0859:             *         negative or non-integer sample values.
0860:             *
0861:             * @see #getCategories
0862:             * @see #getCategory
0863:             */
0864:            public InternationalString[] getCategoryNames()
0865:                    throws IllegalStateException {
0866:                if (categories == null) {
0867:                    return null;
0868:                }
0869:                if (categories.isEmpty()) {
0870:                    return new InternationalString[0];
0871:                }
0872:                InternationalString[] names = null;
0873:                for (int i = categories.size(); --i >= 0;) {
0874:                    final Category category = (Category) categories.get(i);
0875:                    final int lower = (int) category.minimum;
0876:                    final int upper = (int) category.maximum;
0877:                    if (lower != category.minimum || lower < 0
0878:                            || upper != category.maximum || upper < 0) {
0879:                        throw new IllegalStateException(Errors
0880:                                .format(ErrorKeys.NON_INTEGER_CATEGORY));
0881:                    }
0882:                    if (names == null) {
0883:                        names = new InternationalString[upper + 1];
0884:                    }
0885:                    Arrays.fill(names, lower, upper + 1, category.getName());
0886:                }
0887:                return names;
0888:            }
0889:
0890:            /**
0891:             * Returns all categories in this sample dimension. Note that a {@link Category} object may
0892:             * apply to an arbitrary range of sample values.    Consequently, the first element in this
0893:             * collection may not be directly related to the sample value {@code 0}.
0894:             *
0895:             * @return The list of categories in this sample dimension, or {@code null} if none.
0896:             *
0897:             * @see #getCategoryNames
0898:             * @see #getCategory
0899:             */
0900:            public List getCategories() {
0901:                return categories;
0902:            }
0903:
0904:            /**
0905:             * Returns the category for the specified sample value. If this method can't maps
0906:             * a category to the specified value, then it returns {@code null}.
0907:             *
0908:             * @param  sample The value (can be one of {@code NaN} values).
0909:             * @return The category for the supplied value, or {@code null} if none.
0910:             *
0911:             * @see #getCategories
0912:             * @see #getCategoryNames
0913:             */
0914:            public Category getCategory(final double sample) {
0915:                return (categories != null) ? categories.getCategory(sample)
0916:                        : null;
0917:            }
0918:
0919:            /**
0920:             * Returns a default category to use for background. A background category is used
0921:             * when an image is <A HREF="../gp/package-summary.html#Resample">resampled</A> (for
0922:             * example reprojected in an other coordinate system) and the resampled image do not
0923:             * fit in a rectangular area. It can also be used in various situation where a raisonable
0924:             * "no data" category is needed. The default implementation try to returns one
0925:             * of the {@linkplain #getNoDataValues no data values}. If no suitable category is found,
0926:             * then a {@linkplain Category#NODATA default} one is returned.
0927:             *
0928:             * @return A category to use as background for the "Resample" operation. Never {@code null}.
0929:             */
0930:            public Category getBackground() {
0931:                return (categories != null) ? categories.nodata
0932:                        : Category.NODATA;
0933:            }
0934:
0935:            /**
0936:             * Returns the values to indicate "no data" for this sample dimension.  The default
0937:             * implementation deduces the "no data" values from the list of categories supplied
0938:             * at construction time. The rules are:
0939:             *
0940:             * <ul>
0941:             *   <li>If {@link #getSampleToGeophysics} returns {@code null}, then
0942:             *       {@code getNoDataValues()} returns {@code null} as well.
0943:             *       This means that this sample dimension contains no category or contains
0944:             *       only qualitative categories (e.g. a band from a classified image).</li>
0945:             *
0946:             *   <li>If {@link #getSampleToGeophysics} returns an identity transform,
0947:             *       then {@code getNoDataValues()} returns {@code null}.
0948:             *       This means that sample value in this sample dimension are already
0949:             *       expressed in geophysics values and that all "no data" values (if any)
0950:             *       have already been converted into {@code NaN} values.</li>
0951:             *
0952:             *   <li>Otherwise, if there is at least one quantitative category, returns the sample values
0953:             *       of all non-quantitative categories. For example if "Temperature" is a quantitative
0954:             *       category and "Land" and "Cloud" are two qualitative categories, then sample values
0955:             *       for "Land" and "Cloud" will be considered as "no data" values. "No data" values
0956:             *       that are already {@code NaN} will be ignored.</li>
0957:             * </ul>
0958:             *
0959:             * Together with {@link #getOffset()} and {@link #getScale()}, this method provides a limited
0960:             * way to transform sample values into geophysics values. However, the recommended way is to
0961:             * use the {@link #getSampleToGeophysics sampleToGeophysics} transform instead, which is more
0962:             * general and take care of converting automatically "no data" values into {@code NaN}.
0963:             *
0964:             * @return The values to indicate no data values for this sample dimension,
0965:             *         or {@code null} if not applicable.
0966:             * @throws IllegalStateException if some qualitative categories use a range of
0967:             *         non-integer values.
0968:             *
0969:             * @see #getSampleToGeophysics
0970:             */
0971:            public double[] getNoDataValues() throws IllegalStateException {
0972:                if (!hasQuantitative) {
0973:                    return null;
0974:                }
0975:                int count = 0;
0976:                double[] padValues = null;
0977:                final int size = categories.size();
0978:                for (int i = 0; i < size; i++) {
0979:                    final Category category = (Category) categories.get(i);
0980:                    if (!category.isQuantitative()) {
0981:                        final double min = category.minimum;
0982:                        final double max = category.maximum;
0983:                        if (!Double.isNaN(min) || !Double.isNaN(max)) {
0984:                            if (padValues == null) {
0985:                                padValues = new double[size - i];
0986:                            }
0987:                            if (count >= padValues.length) {
0988:                                padValues = XArray.resize(padValues, count * 2);
0989:                            }
0990:                            padValues[count++] = min;
0991:                            /*
0992:                             * The "no data" value has been extracted. Now, check if we have a range
0993:                             * of "no data" values instead of a single one for this category.  If we
0994:                             * have a single value, it can be of any type. But if we have a range,
0995:                             * then it must be a range of integers (otherwise we can't expand it).
0996:                             */
0997:                            if (max != min) {
0998:                                int lower = (int) min;
0999:                                int upper = (int) max;
1000:                                if (lower != min
1001:                                        || upper != max
1002:                                        || !XMath.isInteger(category.getRange()
1003:                                                .getElementClass())) {
1004:                                    throw new IllegalStateException(
1005:                                            Errors
1006:                                                    .format(ErrorKeys.NON_INTEGER_CATEGORY));
1007:                                }
1008:                                final int requiredLength = count
1009:                                        + (upper - lower);
1010:                                if (requiredLength > padValues.length) {
1011:                                    padValues = XArray.resize(padValues,
1012:                                            requiredLength * 2);
1013:                                }
1014:                                while (++lower <= upper) {
1015:                                    padValues[count++] = lower;
1016:                                }
1017:                            }
1018:                        }
1019:                    }
1020:                }
1021:                if (padValues != null) {
1022:                    padValues = XArray.resize(padValues, count);
1023:                }
1024:                return padValues;
1025:            }
1026:
1027:            /**
1028:             * Returns the minimum value occurring in this sample dimension.
1029:             * The default implementation fetch this value from the categories supplied at
1030:             * construction time. If the minimum value can't be computed, then this method
1031:             * returns {@link Double#NEGATIVE_INFINITY}.
1032:             *
1033:             * @see #getRange
1034:             */
1035:            public double getMinimumValue() {
1036:                if (categories != null && !categories.isEmpty()) {
1037:                    final double value = ((Category) categories.get(0)).minimum;
1038:                    if (!Double.isNaN(value)) {
1039:                        return value;
1040:                    }
1041:                }
1042:                return Double.NEGATIVE_INFINITY;
1043:            }
1044:
1045:            /**
1046:             * Returns the maximum value occurring in this sample dimension.
1047:             * The default implementation fetch this value from the categories supplied at
1048:             * construction time. If the maximum value can't be computed, then this method
1049:             * returns {@link Double#POSITIVE_INFINITY}.
1050:             *
1051:             * @see #getRange
1052:             */
1053:            public double getMaximumValue() {
1054:                if (categories != null) {
1055:                    for (int i = categories.size(); --i >= 0;) {
1056:                        final double value = ((Category) categories.get(i)).maximum;
1057:                        if (!Double.isNaN(value)) {
1058:                            return value;
1059:                        }
1060:                    }
1061:                }
1062:                return Double.POSITIVE_INFINITY;
1063:            }
1064:
1065:            /**
1066:             * Returns the range of values in this sample dimension. This is the union of the range of
1067:             * values of every categories, excluding {@code NaN} values. A {@link NumberRange} object
1068:             * gives more informations than {@link #getMinimumValue} and {@link #getMaximumValue} methods
1069:             * since it contains also the data type (integer, float, etc.) and inclusion/exclusion
1070:             * informations.
1071:             *
1072:             * @return The range of values. May be {@code null} if this sample dimension has no
1073:             *         quantitative category.
1074:             *
1075:             * @see Category#getRange
1076:             * @see #getMinimumValue
1077:             * @see #getMaximumValue
1078:             *
1079:             * @todo We should do a better job in {@code CategoryList.getRange()} when selecting
1080:             *       the appropriate data type. {@link TypeMap#getSampleDimensionType(Range)}
1081:             *       may be of some help.
1082:             */
1083:            public NumberRange getRange() {
1084:                return (categories != null) ? categories.getRange() : null;
1085:            }
1086:
1087:            /**
1088:             * Returns {@code true} if at least one value of {@code values} is
1089:             * in the range {@code lower} inclusive to {@code upper} exclusive.
1090:             */
1091:            private static boolean rangeContains(final double lower,
1092:                    final double upper, final double[] values) {
1093:                if (values != null) {
1094:                    final int length = values.length;
1095:                    for (int i = 0; i < length; i++) {
1096:                        final double v = values[i];
1097:                        if (v >= lower && v < upper) {
1098:                            return true;
1099:                        }
1100:                    }
1101:                }
1102:                return false;
1103:            }
1104:
1105:            /**
1106:             * Returns a string representation of a sample value. This method try to returns
1107:             * a representation of the geophysics value; the transformation is automatically
1108:             * applied when necessary. More specifically:
1109:             *
1110:             * <ul>
1111:             *   <li>If {@code value} maps a qualitative category, then the
1112:             *       category name is returned as of {@link Category#getName}.</li>
1113:             *
1114:             *   <li>Otherwise, if {@code value} maps a quantitative category, then the value is
1115:             *       transformed into a geophysics value as with the {@link #getSampleToGeophysics()
1116:             *       sampleToGeophysics} transform, the result is formatted as a number and the unit
1117:             *       symbol is appened.</li>
1118:             * </ul>
1119:             *
1120:             * @param  value  The sample value (can be one of {@code NaN} values).
1121:             * @param  locale Locale to use for formatting, or {@code null} for the default locale.
1122:             * @return A string representation of the geophysics value, or {@code null} if there is
1123:             *         none.
1124:             *
1125:             * @todo What should we do when the value can't be formatted?
1126:             *       {@code GridSampleDimension} returns {@code null} if there is no
1127:             *       category or if an exception is thrown, but {@code CategoryList}
1128:             *       returns "Untitled" if the value is an unknow NaN, and try to format
1129:             *       the number anyway in other cases.
1130:             */
1131:            public String getLabel(final double value, final Locale locale) {
1132:                if (categories != null) {
1133:                    if (isGeophysics) {
1134:                        return categories.format(value, locale);
1135:                    } else
1136:                        try {
1137:                            return categories.inverse.format(categories
1138:                                    .transform(value), locale);
1139:                        } catch (TransformException exception) {
1140:                            // Value probably don't match a category. Ignore...
1141:                        }
1142:                }
1143:                return null;
1144:            }
1145:
1146:            /**
1147:             * Returns the unit information for this sample dimension.
1148:             * May returns {@code null} if this dimension has no units.
1149:             * This unit apply to values obtained after the {@link #getSampleToGeophysics
1150:             * sampleToGeophysics} transformation.
1151:             *
1152:             * @see #getSampleToGeophysics
1153:             */
1154:            public Unit getUnits() {
1155:                return (categories != null) ? categories.geophysics(true)
1156:                        .getUnits() : null;
1157:            }
1158:
1159:            /**
1160:             * Returns the value to add to grid values for this sample dimension.
1161:             * This attribute is typically used when the sample dimension represents
1162:             * elevation data. The transformation equation is:
1163:             *
1164:             * <blockquote><pre>offset + scale*sample</pre></blockquote>
1165:             *
1166:             * Together with {@link #getScale()} and {@link #getNoDataValues()}, this method provides a
1167:             * limited way to transform sample values into geophysics values. However, the recommended
1168:             * way is to use the {@link #getSampleToGeophysics sampleToGeophysics} transform instead,
1169:             * which is more general and take care of converting automatically "no data" values
1170:             * into {@code NaN}.
1171:             *
1172:             * @return The offset to add to grid values.
1173:             * @throws IllegalStateException if the transform from sample to geophysics values
1174:             *         is not a linear relation.
1175:             *
1176:             * @see #getSampleToGeophysics
1177:             * @see #rescale
1178:             */
1179:            public double getOffset() throws IllegalStateException {
1180:                return getCoefficient(0);
1181:            }
1182:
1183:            /**
1184:             * Returns the value which is multiplied to grid values for this sample dimension.
1185:             * This attribute is typically used when the sample dimension represents elevation
1186:             * data. The transformation equation is:
1187:             *
1188:             * <blockquote><pre>offset + scale*sample</pre></blockquote>
1189:             *
1190:             * Together with {@link #getOffset()} and {@link #getNoDataValues()}, this method provides a
1191:             * limited way to transform sample values into geophysics values. However, the recommended
1192:             * way is to use the {@link #getSampleToGeophysics sampleToGeophysics} transform instead,
1193:             * which is more general and take care of converting automatically "no data" values
1194:             * into {@code NaN}.
1195:             *
1196:             * @return The scale to multiply to grid value.
1197:             * @throws IllegalStateException if the transform from sample to geophysics values
1198:             *         is not a linear relation.
1199:             *
1200:             * @see #getSampleToGeophysics
1201:             * @see #rescale
1202:             */
1203:            public double getScale() {
1204:                return getCoefficient(1);
1205:            }
1206:
1207:            /**
1208:             * Returns a coefficient of the linear transform from sample to geophysics values.
1209:             *
1210:             * @param  order The coefficient order (0 for the offset, or 1 for the scale factor,
1211:             *         2 if we were going to implement quadratic relation, 3 for cubic, etc.).
1212:             * @return The coefficient.
1213:             * @throws IllegalStateException if the transform from sample to geophysics values
1214:             *         is not a linear relation.
1215:             */
1216:            private double getCoefficient(final int order)
1217:                    throws IllegalStateException {
1218:                if (!hasQuantitative) {
1219:                    // Default value for "offset" is 0; default value for "scale" is 1.
1220:                    // This is equal to the order if 0 <= order <= 1.
1221:                    return order;
1222:                }
1223:                Exception cause = null;
1224:                if (sampleToGeophysics != null)
1225:                    try {
1226:                        final double value;
1227:                        switch (order) {
1228:                        case 0:
1229:                            value = sampleToGeophysics.transform(0);
1230:                            break;
1231:                        case 1:
1232:                            value = sampleToGeophysics.derivative(Double.NaN);
1233:                            break;
1234:                        default:
1235:                            throw new AssertionError(order); // Should not happen
1236:                        }
1237:                        if (!Double.isNaN(value)) {
1238:                            return value;
1239:                        }
1240:                    } catch (TransformException exception) {
1241:                        cause = exception;
1242:                    }
1243:                IllegalStateException exception = new IllegalStateException(
1244:                        Errors.format(ErrorKeys.NON_LINEAR_RELATION));
1245:                exception.initCause(cause);
1246:                throw exception;
1247:            }
1248:
1249:            /**
1250:             * Returns a transform from sample values to geophysics values. If this sample dimension
1251:             * has no category, then this method returns {@code null}. If all sample values are
1252:             * already geophysics values (including {@code NaN} for "no data" values), then this
1253:             * method returns an identity transform. Otherwise, this method returns a transform expecting
1254:             * sample values as input and computing geophysics value as output. This transform will take
1255:             * care of converting all "{@linkplain #getNoDataValues() no data values}" into
1256:             * {@code NaN} values.
1257:             * The <code>sampleToGeophysics.{@linkplain MathTransform1D#inverse() inverse()}</code>
1258:             * transform is capable to differenciate {@code NaN} values to get back the original
1259:             * sample value.
1260:             *
1261:             * @return The transform from sample to geophysics values, or {@code null} if this
1262:             *         sample dimension do not defines any transform (which is not the same that
1263:             *         defining an identity transform).
1264:             *
1265:             * @see #getScale
1266:             * @see #getOffset
1267:             * @see #getNoDataValues
1268:             * @see #rescale
1269:             */
1270:            public MathTransform1D getSampleToGeophysics() {
1271:                if (isGeophysics) {
1272:                    return LinearTransform1D.IDENTITY;
1273:                }
1274:                if (!hasQualitative && sampleToGeophysics != null) {
1275:                    // If there is only quantitative categories and they all use the same transform,
1276:                    // then we don't need the indirection level provided by CategoryList.
1277:                    return sampleToGeophysics;
1278:                }
1279:                // CategoryList is a MathTransform1D.
1280:                return categories;
1281:            }
1282:
1283:            /**
1284:             * If {@code true}, returns the geophysics companion of this sample dimension. By
1285:             * definition, a <cite>geophysics sample dimension</cite> is a sample dimension with a
1286:             * {@linkplain #getRange range of sample values} transformed in such a way that the
1287:             * {@link #getSampleToGeophysics sampleToGeophysics} transform is always the identity
1288:             * transform, or {@code null} if no such transform existed in the first place. In
1289:             * other words, the range of sample values in all category maps directly the "real world"
1290:             * values without the need for any transformation.
1291:             * <p>
1292:             * {@code GridSampleDimension} objects live by pair: a <cite>geophysics</cite> one
1293:             * (used for computation) and a <cite>non-geophysics</cite> one (used for packing data, usually
1294:             * as integers). The {@code geo} argument specifies which object from the pair is wanted,
1295:             * regardless if this method is invoked on the geophysics or non-geophysics instance of the
1296:             * pair. In other words, the result of {@code geophysics(b1).geophysics(b2).geophysics(b3)}
1297:             * depends only on the value in the last call ({@code b3}).
1298:             *
1299:             * @param  geo {@code true} to get a sample dimension with an identity
1300:             *         {@linkplain #getSampleToGeophysics transform} and a {@linkplain #getRange range of
1301:             *         sample values} matching the geophysics values, or {@code false} to get back the
1302:             *         original sample dimension.
1303:             * @return The sample dimension. Never {@code null}, but may be {@code this}.
1304:             *
1305:             * @see Category#geophysics
1306:             * @see org.geotools.coverage.grid.GridCoverage2D#geophysics
1307:             */
1308:            public GridSampleDimension geophysics(final boolean geo) {
1309:                if (geo == isGeophysics) {
1310:                    return this ;
1311:                }
1312:                if (inverse == null) {
1313:                    if (categories != null) {
1314:                        inverse = new GridSampleDimension(description,
1315:                                categories.inverse);
1316:                        inverse.inverse = this ;
1317:                    } else {
1318:                        /*
1319:                         * If there is no categories, then there is no real difference between
1320:                         * "geophysics" and "indexed" sample dimensions.  Both kinds of sample
1321:                         * dimensions would be identical objects, so we are better to just
1322:                         * returns 'this'.
1323:                         */
1324:                        inverse = this ;
1325:                    }
1326:                }
1327:                return inverse;
1328:            }
1329:
1330:            /**
1331:             * Color palette associated with the sample dimension.
1332:             * A color palette can have any number of colors.
1333:             * See palette interpretation for meaning of the palette entries.
1334:             * If the grid coverage has no color palette, {@code null} will be returned.
1335:             *
1336:             * @return The color palette associated with the sample dimension.
1337:             *
1338:             * @see #getPaletteInterpretation
1339:             * @see #getColorInterpretation
1340:             * @see IndexColorModel
1341:             *
1342:             * @deprecated No replacement.
1343:             */
1344:            public int[][] getPalette() {
1345:                final ColorModel color = getColorModel();
1346:                if (color instanceof  IndexColorModel) {
1347:                    final IndexColorModel cm = (IndexColorModel) color;
1348:                    final int[][] colors = new int[cm.getMapSize()][];
1349:                    final int length = colors.length;
1350:                    for (int i = 0; i < length; i++) {
1351:                        colors[i] = new int[] { cm.getRed(i), cm.getGreen(i),
1352:                                cm.getBlue(i) };
1353:                    }
1354:                    return colors;
1355:                }
1356:                return null;
1357:            }
1358:
1359:            /**
1360:             * Indicates the type of color palette entry for sample dimensions which have a
1361:             * palette. If a sample dimension has a palette, the color interpretation must
1362:             * be {@link ColorInterpretation#GRAY_INDEX GRAY_INDEX}
1363:             * or {@link ColorInterpretation#PALETTE_INDEX PALETTE_INDEX}.
1364:             * A palette entry type can be Gray, RGB, CMYK or HLS.
1365:             *
1366:             * @return The type of color palette entry for sample dimensions which have a palette.
1367:             *
1368:             * @deprecated No replacement.
1369:             */
1370:            public PaletteInterpretation getPaletteInterpretation() {
1371:                return PaletteInterpretation.RGB;
1372:            }
1373:
1374:            /**
1375:             * Returns the color interpretation of the sample dimension.
1376:             * A sample dimension can be an index into a color palette or be a color model
1377:             * component. If the sample dimension is not assigned a color interpretation
1378:             * the value is {@link ColorInterpretation#UNDEFINED}.
1379:             *
1380:             * @deprecated No replacement.
1381:             */
1382:            public ColorInterpretation getColorInterpretation() {
1383:                // The 'Grid2DSampleDimension' class overrides this method
1384:                // with better values for 'band' and 'numBands' constants.
1385:                final int band = 0;
1386:                final int numBands = 1;
1387:                return TypeMap.getColorInterpretation(getColorModel(band,
1388:                        numBands), band);
1389:            }
1390:
1391:            /**
1392:             * Returns a color model for this sample dimension. The default implementation create a color
1393:             * model with 1 band using each category's colors as returned by {@link Category#getColors}.
1394:             * The returned color model will typically use data type {@link DataBuffer#TYPE_FLOAT} if this
1395:             * {@code GridSampleDimension} instance is "geophysics", or an integer data type otherwise.
1396:             * <p>
1397:             * Note that {@link org.geotools.coverage.grid.GridCoverage2D#getSampleDimension} returns
1398:             * special implementations of {@code GridSampleDimension}. In this particular case,
1399:             * the color model created by this {@code getColorModel()} method will have the same number of
1400:             * bands than the grid coverage's {@link RenderedImage}.
1401:             *
1402:             * @return The requested color model, suitable for {@link RenderedImage} objects with values
1403:             *         in the <code>{@link #getRange}</code> range. May be {@code null} if this
1404:             *         sample dimension has no category.
1405:             */
1406:            public ColorModel getColorModel() {
1407:                // The 'Grid2DSampleDimension' class overrides this method
1408:                // with better values for 'band' and 'numBands' constants.
1409:                final int band = 0;
1410:                final int numBands = 1;
1411:                return getColorModel(band, numBands);
1412:            }
1413:
1414:            /**
1415:             * Returns a color model for this sample dimension. The default implementation create the
1416:             * color model using each category's colors as returned by {@link Category#getColors}. The
1417:             * returned color model will typically use data type {@link DataBuffer#TYPE_FLOAT} if this
1418:             * {@code GridSampleDimension} instance is "geophysics", or an integer data type otherwise.
1419:             *
1420:             * @param  visibleBand The band to be made visible (usually 0). All other bands, if any
1421:             *         will be ignored.
1422:             * @param  numBands The number of bands for the color model (usually 1). The returned color
1423:             *         model will renderer only the {@code visibleBand} and ignore the others, but
1424:             *         the existence of all {@code numBands} will be at least tolerated. Supplemental
1425:             *         bands, even invisible, are useful for processing with Java Advanced Imaging.
1426:             * @return The requested color model, suitable for {@link RenderedImage} objects with values
1427:             *         in the <code>{@link #getRange}</code> range. May be {@code null} if this
1428:             *         sample dimension has no category.
1429:             *
1430:             * @todo This method may be deprecated in a future version. It it strange to use
1431:             *       only one {@code SampleDimension} object for creating a multi-bands color
1432:             *       model. Logically, we would expect as many {@code SampleDimension}s as bands.
1433:             */
1434:            public ColorModel getColorModel(final int visibleBand,
1435:                    final int numBands) {
1436:                if (categories != null) {
1437:                    return categories.getColorModel(visibleBand, numBands);
1438:                }
1439:                return null;
1440:            }
1441:
1442:            /**
1443:             * Returns a color model for this sample dimension. The default implementation create the
1444:             * color model using each category's colors as returned by {@link Category#getColors}. 
1445:             *
1446:             * @param  visibleBand The band to be made visible (usually 0). All other bands, if any
1447:             *         will be ignored.
1448:             * @param  numBands The number of bands for the color model (usually 1). The returned color
1449:             *         model will renderer only the {@code visibleBand} and ignore the others, but
1450:             *         the existence of all {@code numBands} will be at least tolerated. Supplemental
1451:             *         bands, even invisible, are useful for processing with Java Advanced Imaging.
1452:             * @param  type The data type that has to be used for the sample model
1453:             * @return The requested color model, suitable for {@link RenderedImage} objects with values
1454:             *         in the <code>{@link #getRange}</code> range. May be {@code null} if this
1455:             *         sample dimension has no category.
1456:             *
1457:             * @todo This method may be deprecated in a future version. It it strange to use
1458:             *       only one {@code SampleDimension} object for creating a multi-bands color
1459:             *       model. Logically, we would expect as many {@code SampleDimension}s as bands.
1460:             */
1461:            public ColorModel getColorModel(final int visibleBand,
1462:                    final int numBands, final int type) {
1463:                if (categories != null) {
1464:                    return categories
1465:                            .getColorModel(visibleBand, numBands, type);
1466:                }
1467:                return null;
1468:            }
1469:
1470:            /**
1471:             * Returns a sample dimension using new {@link #getScale scale} and {@link #getOffset offset}
1472:             * coefficients. Other properties like the {@linkplain #getRange sample value range},
1473:             * {@linkplain #getNoDataValues no data values} and {@linkplain #getColorModel colors}
1474:             * are unchanged.
1475:             *
1476:             * @param scale  The value which is multiplied to grid values for the new sample dimension.
1477:             * @param offset The value to add to grid values for the new sample dimension.
1478:             *
1479:             * @see #getScale
1480:             * @see #getOffset
1481:             * @see Category#rescale
1482:             */
1483:            public GridSampleDimension rescale(final double scale,
1484:                    final double offset) {
1485:                final MathTransform1D sampleToGeophysics = Category
1486:                        .createLinearTransform(scale, offset);
1487:                final Category[] categories = (Category[]) getCategories()
1488:                        .toArray();
1489:                final Category[] reference = (Category[]) categories.clone();
1490:                final int length = categories.length;
1491:                for (int i = 0; i < length; i++) {
1492:                    if (categories[i].isQuantitative()) {
1493:                        categories[i] = categories[i]
1494:                                .rescale(sampleToGeophysics);
1495:                    }
1496:                    categories[i] = categories[i].geophysics(isGeophysics);
1497:                }
1498:                if (Arrays.equals(categories, reference)) {
1499:                    return this ;
1500:                }
1501:                return new GridSampleDimension(description, categories,
1502:                        getUnits());
1503:            }
1504:
1505:            /**
1506:             * The list of metadata keywords for a sample dimension.
1507:             * If no metadata is available, the sequence will be empty.
1508:             *
1509:             * @return The list of metadata keywords for a sample dimension.
1510:             *
1511:             * @see #getMetadataValue
1512:             * @see javax.media.jai.PropertySource#getPropertyNames
1513:             *
1514:             * @deprecated Not implemented.
1515:             */
1516:            public String[] getMetaDataNames() {
1517:                return EMPTY_METADATA;
1518:            }
1519:
1520:            /**
1521:             * Retrieve the metadata value for a given metadata name.
1522:             *
1523:             * @param  name Metadata keyword for which to retrieve metadata.
1524:             * @return The metadata value for a given metadata name.
1525:             * @throws MetadataNameNotFoundException if there is no value for the specified metadata name.
1526:             *
1527:             * @see #getMetaDataNames
1528:             * @see javax.media.jai.PropertySource#getProperty
1529:             *
1530:             * @deprecated Not implemented.
1531:             */
1532:            public String getMetadataValue(String name)
1533:                    throws MetadataNameNotFoundException {
1534:                throw new MetadataNameNotFoundException();
1535:            }
1536:
1537:            /**
1538:             * Returns a hash value for this sample dimension.
1539:             * This value need not remain consistent between
1540:             * different implementations of the same class.
1541:             */
1542:            public int hashCode() {
1543:                return (categories != null) ? categories.hashCode()
1544:                        : (int) serialVersionUID;
1545:            }
1546:
1547:            /**
1548:             * Compares the specified object with this sample dimension for equality.
1549:             */
1550:            public boolean equals(final Object object) {
1551:                if (object == this ) {
1552:                    // Slight optimization
1553:                    return true;
1554:                }
1555:                if (object instanceof  GridSampleDimension) {
1556:                    final GridSampleDimension that = (GridSampleDimension) object;
1557:                    return Utilities.equals(this .categories, that.categories);
1558:                    // Since everything is deduced from CategoryList, two sample dimensions
1559:                    // should be equal if they have the same list of categories.
1560:                }
1561:                return false;
1562:            }
1563:
1564:            /**
1565:             * Returns a string representation of this sample dimension.
1566:             * This string is for debugging purpose only and may change
1567:             * in future version. The default implementation format the
1568:             * sample value range, then the list of categories. A "*"
1569:             * mark is put in front of what seems the "main" category.
1570:             */
1571:            public String toString() {
1572:                if (categories != null) {
1573:                    return categories.toString(this );
1574:                } else {
1575:                    return Utilities.getShortClassName(this );
1576:                }
1577:            }
1578:
1579:            /////////////////////////////////////////////////////////////////////////////////
1580:            ////////                                                                 ////////
1581:            ////////        REGISTRATION OF "SampleTranscode" IMAGE OPERATION        ////////
1582:            ////////                                                                 ////////
1583:            /////////////////////////////////////////////////////////////////////////////////
1584:
1585:            /**
1586:             * Register the "SampleTranscode" image operation.
1587:             * Registration is done when the class is first loaded.
1588:             *
1589:             * @todo This static initializer will imply immediate class loading of a lot of
1590:             *       JAI dependencies.  This is a pretty high overhead if JAI is not wanted
1591:             *       right now. The correct approach is to declare the image operation into
1592:             *       the {@code META-INF/registryFile.jai} file, which is automatically
1593:             *       parsed during JAI initialization. Unfortunatly, it can't access private
1594:             *       classes and we don't want to make our registration classes public. We
1595:             *       can't move our registration classes into a hidden "resources" package
1596:             *       neither because we need package-private access to {@code CategoryList}.
1597:             *       For now, we assume that people using the GC package probably want to work
1598:             *       with {@link org.geotools.coverage.grid.GridCoverage2D}, which make extensive
1599:             *       use of JAI. Peoples just working with {@link org.geotools.coverage.Coverage} are
1600:             *       stuck with the overhead. Note that we register the image operation here because
1601:             *       the only operation's argument is of type {@code GridSampleDimension[]}.
1602:             *       Consequently, the image operation may be invoked at any time after class
1603:             *       loading of {@link GridSampleDimension}.
1604:             *       <p>
1605:             *       Additional note: moving the initialization into the
1606:             *       {@code META-INF/registryFile.jai} file may not be the best idea neithter,
1607:             *       since peoples using JAI without the GCS module may be stuck with the overhead
1608:             *       of loading GC classes.
1609:             */
1610:            static {
1611:                SampleTranscoder.register(JAI.getDefaultInstance());
1612:            }
1613:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.