Source Code Cross Referenced for Category.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) 


001:        /*
002:         *    GeoTools - OpenSource mapping toolkit
003:         *    http://geotools.org
004:         *    (C) 2003-2006, Geotools Project Management Committee (PMC)
005:         *    (C) 2001, Institut de Recherche pour le Développement
006:         *
007:         *    This library is free software; you can redistribute it and/or
008:         *    modify it under the terms of the GNU Lesser General Public
009:         *    License as published by the Free Software Foundation; either
010:         *    version 2.1 of the License, or (at your option) any later version.
011:         *
012:         *    This library is distributed in the hope that it will be useful,
013:         *    but WITHOUT ANY WARRANTY; without even the implied warranty of
014:         *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015:         *    Lesser General Public License for more details.
016:         *
017:         *    This package contains documentation from OpenGIS specifications.
018:         *    OpenGIS consortium's work is fully acknowledged here.
019:         */
020:        package org.geotools.coverage;
021:
022:        // J2SE dependencies
023:        import java.awt.Color;
024:        import java.util.Arrays;
025:        import java.io.Serializable;
026:
027:        // JAI dependencies
028:        import javax.media.jai.operator.PiecewiseDescriptor;
029:
030:        // OpenGIS dependencies
031:        import org.opengis.referencing.operation.MathTransform;
032:        import org.opengis.referencing.operation.MathTransform1D;
033:        import org.opengis.referencing.operation.MathTransformFactory;
034:        import org.opengis.referencing.operation.TransformException;
035:        import org.opengis.util.InternationalString;
036:
037:        // Geotools dependencies
038:        import org.geotools.referencing.operation.transform.LinearTransform1D;
039:        import org.geotools.resources.Utilities;
040:        import org.geotools.resources.XMath;
041:        import org.geotools.resources.i18n.Errors;
042:        import org.geotools.resources.i18n.ErrorKeys;
043:        import org.geotools.resources.i18n.Vocabulary;
044:        import org.geotools.resources.i18n.VocabularyKeys;
045:        import org.geotools.util.SimpleInternationalString;
046:        import org.geotools.util.NumberRange;
047:
048:        /**
049:         * A category delimited by a range of sample values. A categogy may be either
050:         * <em>qualitative</em> or <em>quantitative</em>.   For exemple, a classified
051:         * image may have a qualitative category defining sample value {@code 0}
052:         * as water. An other qualitative category may defines sample value {@code 1}
053:         * as forest, etc.  An other image may define elevation data as sample values
054:         * in the range {@code [0..100]}.   The later is a <em>quantitative</em>
055:         * category, because sample values are related to some measurement in the real
056:         * world. For example, elevation data may be related to an altitude in metres
057:         * through the following linear relation:
058:         *
059:         * <var>altitude</var>&nbsp;=&nbsp;<var>sample&nbsp;value</var>&times;100.
060:         * 
061:         * Some image mixes both qualitative and quantitative categories. For example,
062:         * images of Sea Surface Temperature  (SST)  may have a quantitative category
063:         * for temperature with values ranging from –2 to 35°C,  and three qualitative
064:         * categories for cloud, land and ice.
065:         * <p>
066:         * All categories must have a human readable name. In addition, quantitative
067:         * categories may define a transformation between sample values <var>s</var>
068:         * and geophysics values <var>x</var>.   This transformation is usually (but
069:         * not always) a linear equation of the form:
070:         *
071:         * <P align="center"><var>x</var><code>&nbsp;=&nbsp;{@linkplain GridSampleDimension#getOffset()
072:         * offset}&nbsp;+&nbsp;{@linkplain GridSampleDimension#getScale()
073:         * scale}&times;</code><var>s</var></P>
074:         *
075:         * More general equation are allowed. For example, <cite>SeaWiFS</cite> images
076:         * use a logarithmic transform. General transformations are expressed with a
077:         * {@link MathTransform1D} object. In the special case where the transformation
078:         * is a linear one (as in the formula above), then a {@code Category} object
079:         * may be understood as the interval between two breakpoints in the JAI's
080:         * {@linkplain PiecewiseDescriptor piecewise} operation.
081:         * <p>
082:         * All {@code Category} objects are immutable and thread-safe.
083:         *
084:         * @since 2.1
085:         * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/Category.java $
086:         * @version $Id: Category.java 26695 2007-08-23 18:58:56Z desruisseaux $
087:         * @author Martin Desruisseaux
088:         *
089:         * @see GridSampleDimension
090:         * @see PiecewiseDescriptor
091:         */
092:        public class Category implements  Serializable {
093:            /**
094:             * Serial number for interoperability with different versions.
095:             */
096:            private static final long serialVersionUID = 6215962897884256696L;
097:
098:            /**
099:             * The 0 value as a byte. Used for {@link #FALSE} categories.
100:             */
101:            private static final NumberRange BYTE_0;
102:            static {
103:                final Byte index = new Byte((byte) 0);
104:                BYTE_0 = new NumberRange(Byte.class, index, index);
105:            }
106:
107:            /**
108:             * The 1 value as a byte. Used for {@link #TRUE} categories.
109:             */
110:            private static final NumberRange BYTE_1;
111:            static {
112:                final Byte index = new Byte((byte) 1);
113:                BYTE_1 = new NumberRange(Byte.class, index, index);
114:            }
115:
116:            /**
117:             * A default category for "no data" values. This default qualitative category use
118:             * sample value 0, which is mapped to geophysics value {@link Float#NaN} for those who work
119:             * with floating point images. The rendering color default to a fully transparent color and
120:             * the name is "no data" localized to the requested locale.
121:             */
122:            public static final Category NODATA = new Category(Vocabulary
123:                    .formatInternational(VocabularyKeys.NODATA), new Color(0,
124:                    0, 0, 0), 0);
125:
126:            /**
127:             * A default category for the boolean "{@link Boolean#FALSE false}" value. This default
128:             * identity category uses sample value 0, the color {@linkplain Color#BLACK black} and
129:             * the name "false" localized to the specified locale.
130:             */
131:            public static final Category FALSE = new Category(Vocabulary
132:                    .formatInternational(VocabularyKeys.FALSE), Color.BLACK,
133:                    false);
134:
135:            /**
136:             * A default category for the boolean "{@link Boolean#TRUE true}" value. This default
137:             * identity category uses sample value 1, the color {@linkplain Color#WHITE white}
138:             * and the name "true" localized to the specified locale.
139:             */
140:            public static final Category TRUE = new Category(Vocabulary
141:                    .formatInternational(VocabularyKeys.TRUE), Color.WHITE,
142:                    true);
143:
144:            /**
145:             * The category name.
146:             */
147:            private final InternationalString name;
148:
149:            /**
150:             * The minimal sample value (inclusive). This category is made of all values
151:             * in the range {@code minimum} to {@code maximum} inclusive.
152:             *
153:             * If this category is an instance of {@code GeophysicsCategory},
154:             * then this field is the minimal geophysics value in this category.
155:             * For qualitative categories, the geophysics value is one of {@code NaN} values.
156:             */
157:            final double minimum;
158:
159:            /**
160:             * The maximal sample value (inclusive). This category is made of all values
161:             * in the range {@code minimum} to {@code maximum} inclusive.
162:             *
163:             * If this category is an instance of {@code GeophysicsCategory},
164:             * then this field is the maximal geophysics value in this category.
165:             * For qualitative categories, the geophysics value is one of {@code NaN} values.
166:             */
167:            final double maximum;
168:
169:            /**
170:             * The range of values {@code [minimum..maximum]}.
171:             * May be computed only when first requested, or may be
172:             * user-supplied (which is why it must be serialized).
173:             */
174:            NumberRange range;
175:
176:            /**
177:             * The math transform from sample to geophysics values (never {@code null}).
178:             *
179:             * If this category is an instance of {@code GeophysicsCategory}, then this transform
180:             * is the inverse (as computed by {@link MathTransform#inverse()}), except for qualitative
181:             * categories. Since {@link #getSampleToGeophysics} returns {@code null} for
182:             * qualitative categories, this difference is not visible to the user.
183:             *
184:             * @see GridSampleDimension#getScale()
185:             * @see GridSampleDimension#getOffset()
186:             */
187:            final MathTransform1D transform;
188:
189:            /**
190:             * A reference to the {@code GeophysicsCategory}. If this category is already an
191:             * instance of {@code GeophysicsCategory}, then {@code inverse} is a reference
192:             * to the {@link Category} object that own it.
193:             */
194:            final Category inverse;
195:
196:            /**
197:             * Codes ARGB des couleurs de la catégorie. Les couleurs par
198:             * défaut seront un gradient allant du noir au blanc opaque.
199:             */
200:            private final int[] ARGB;
201:
202:            /**
203:             * Codes ARGB par défaut. On utilise un exemplaire unique
204:             * pour toutes les création d'objets {@link Category}.
205:             */
206:            private static final int[] DEFAULT = { 0xFF000000, 0xFFFFFFFF };
207:
208:            /**
209:             * A set of default category colors.
210:             */
211:            private static final Color[] CYCLE = { Color.BLUE, Color.RED,
212:                    Color.ORANGE, Color.YELLOW, Color.PINK, Color.MAGENTA,
213:                    Color.GREEN, Color.CYAN, Color.LIGHT_GRAY, Color.GRAY };
214:
215:            /**
216:             * Constructs a qualitative category for a boolean value.
217:             *
218:             * @param  name    The category name as a {@link String} or {@link InternationalString} object.
219:             * @param  color   The category color, or {@code null} for a default color.
220:             * @param  sample  The sample value as a boolean.
221:             */
222:            public Category(final CharSequence name, final Color color,
223:                    final boolean sample) {
224:                this (name, new Color[] { color }, sample ? BYTE_0 : BYTE_1,
225:                        LinearTransform1D.IDENTITY);
226:            }
227:
228:            /**
229:             * Constructs a qualitative category for sample value {@code sample}.
230:             *
231:             * @param  name    The category name as a {@link String} or {@link InternationalString} object.
232:             * @param  color   The category color, or {@code null} for a default color.
233:             * @param  sample  The sample value as an integer, usually in the range 0 to 255.
234:             */
235:            public Category(final CharSequence name, final Color color,
236:                    final int sample) {
237:                this (name, toARGB(color, sample), new Integer(sample));
238:                assert minimum == sample : minimum;
239:                assert maximum == sample : maximum;
240:            }
241:
242:            /**
243:             * Constructs a qualitative category for sample value {@code sample}.
244:             *
245:             * @param  name    The category name as a {@link String} or {@link InternationalString} object.
246:             * @param  color   The category color, or {@code null} for a default color.
247:             * @param  sample  The sample value as a double. May be one of {@code NaN} values.
248:             */
249:            public Category(final CharSequence name, final Color color,
250:                    final double sample) {
251:                this (name, toARGB(color, (int) sample), new Double(sample));
252:                assert Double.doubleToRawLongBits(minimum) == Double
253:                        .doubleToRawLongBits(sample) : minimum;
254:                assert Double.doubleToRawLongBits(maximum) == Double
255:                        .doubleToRawLongBits(sample) : maximum;
256:            }
257:
258:            /**
259:             * Constructs a qualitative category for sample value {@code sample}.
260:             */
261:            private Category(final CharSequence name, final int[] ARGB,
262:                    final Number sample) {
263:                this (name, ARGB, new NumberRange(sample.getClass(), sample,
264:                        sample), null);
265:                assert Double.isNaN(inverse.minimum) : inverse.minimum;
266:                assert Double.isNaN(inverse.maximum) : inverse.maximum;
267:            }
268:
269:            /**
270:             * Constructs a quantitative category for samples in the specified range.
271:             *
272:             * @param  name    The category name as a {@link String} or {@link InternationalString} object.
273:             * @param  color   The category color, or {@code null} for a default color.
274:             * @param  sampleValueRange The range of sample values for this category. Element class
275:             *                 is usually {@link Integer}, but {@link Float} and {@link Double} are
276:             *                 accepted as well.
277:             */
278:            public Category(final CharSequence name, final Color color,
279:                    final NumberRange sampleValueRange)
280:                    throws IllegalArgumentException {
281:                this (name, new Color[] { color }, sampleValueRange,
282:                        (MathTransform1D) null);
283:            }
284:
285:            /**
286:             * Constructs a quantitative category for sample values ranging from {@code lower}
287:             * inclusive to {@code upper} exclusive. Sample values are converted into geophysics
288:             * values using the following linear equation:
289:             *
290:             * <center><var>x</var><code>&nbsp;=&nbsp;{@linkplain GridSampleDimension#getOffset()
291:             * offset}&nbsp;+&nbsp;{@linkplain GridSampleDimension#getScale()
292:             * scale}&times;</code><var>s</var></center>
293:             *
294:             * @param  name    The category name as a {@link String} or {@link InternationalString} object.
295:             * @param  colors  A set of colors for this category. This array may have any length;
296:             *                 colors will be interpolated as needed. An array of length 1 means
297:             *                 that an uniform color should be used for all sample values. An array
298:             *                 of length 0 or a {@code null} array means that some default colors
299:             *                 should be used (usually a gradient from opaque black to opaque white).
300:             * @param  lower   The lower sample value, inclusive.
301:             * @param  upper   The upper sample value, exclusive.
302:             * @param  scale   The {@link GridSampleDimension#getScale() scale} value which is
303:             *                 multiplied to sample values for this category.
304:             * @param  offset  The {@link GridSampleDimension#getOffset() offset} value to add
305:             *                 to sample values for this category.
306:             *
307:             * @throws IllegalArgumentException if {@code lower} is not smaller than {@code upper}.
308:             * @throws IllegalArgumentException if {@code scale} or {@code offset} are not real numbers.
309:             */
310:            public Category(final CharSequence name, final Color[] colors,
311:                    final int lower, final int upper, final double scale,
312:                    final double offset) throws IllegalArgumentException {
313:                this (name, colors, new NumberRange(Integer.class, new Integer(
314:                        lower), true, new Integer(upper), false), scale, offset);
315:            }
316:
317:            /**
318:             * Constructs a quantitative category for sample values in the specified range.
319:             * Sample values are converted into geophysics values using the following linear
320:             * equation:
321:             *
322:             * <center><var>x</var><code>&nbsp;=&nbsp;{@linkplain GridSampleDimension#getOffset()
323:             * offset}&nbsp;+&nbsp;{@linkplain GridSampleDimension#getScale()
324:             * scale}&times;</code><var>s</var></center>
325:             *
326:             * @param  name    The category name as a {@link String} or {@link InternationalString} object.
327:             * @param  colors  A set of colors for this category. This array may have any length;
328:             *                 colors will be interpolated as needed. An array of length 1 means
329:             *                 that an uniform color should be used for all sample values. An array
330:             *                 of length 0 or a {@code null} array means that some default colors
331:             *                 should be used (usually a gradient from opaque black to opaque white).
332:             * @param  sampleValueRange The range of sample values for this category. Element class
333:             *                 is usually {@link Integer}, but {@link Float} and {@link Double} are
334:             *                 accepted as well.
335:             * @param  scale   The {@link GridSampleDimension#getScale() scale} value which is
336:             *                 multiplied to sample values for this category.
337:             * @param  offset  The {@link GridSampleDimension#getOffset() offset} value to add
338:             *                 to sample values for this category.
339:             *
340:             * @throws IllegalArgumentException if {@code lower} is not smaller than {@code upper}.
341:             * @throws IllegalArgumentException if {@code scale} or {@code offset} are not real numbers.
342:             */
343:            public Category(final CharSequence name, final Color[] colors,
344:                    final NumberRange sampleValueRange, final double scale,
345:                    final double offset) throws IllegalArgumentException {
346:                this (name, colors, sampleValueRange, createLinearTransform(
347:                        scale, offset));
348:                try {
349:                    assert Double.doubleToLongBits(transform.derivative(0)) == Double
350:                            .doubleToLongBits(scale);
351:                    assert Double.doubleToLongBits(transform.transform(0)) == Double
352:                            .doubleToLongBits(offset);
353:                } catch (TransformException exception) {
354:                    throw new AssertionError(exception);
355:                }
356:                if (Double.isNaN(scale) || Double.isInfinite(scale)) {
357:                    throw new IllegalArgumentException(Errors.format(
358:                            ErrorKeys.BAD_COEFFICIENT_$2, "scale", new Double(
359:                                    scale)));
360:                }
361:                if (Double.isNaN(offset) || Double.isInfinite(offset)) {
362:                    throw new IllegalArgumentException(Errors.format(
363:                            ErrorKeys.BAD_COEFFICIENT_$2, "offset", new Double(
364:                                    offset)));
365:                }
366:            }
367:
368:            /**
369:             * Constructs a quantitative category mapping samples to geophysics values in the specified
370:             * range. Sample values in the {@code sampleValueRange} will be mapped to geophysics
371:             * values in the {@code geophysicsValueRange} through a linear equation of the form:
372:             *
373:             * <center><var>x</var><code>&nbsp;=&nbsp;{@linkplain GridSampleDimension#getOffset()
374:             * offset}&nbsp;+&nbsp;{@linkplain GridSampleDimension#getScale()
375:             * scale}&times;</code><var>s</var></center>
376:             *
377:             * {@code scale} and {@code offset} coefficients are computed from the ranges supplied in
378:             * arguments.
379:             *
380:             * @param  name    The category name as a {@link String} or {@link InternationalString} object.
381:             * @param  colors  A set of colors for this category. This array may have any length;
382:             *                 colors will be interpolated as needed. An array of length 1 means
383:             *                 that an uniform color should be used for all sample values. An array
384:             *                 of length 0 or a {@code null} array means that some default colors
385:             *                 should be used (usually a gradient from opaque black to opaque white).
386:             * @param  sampleValueRange The range of sample values for this category. Element class
387:             *                 is usually {@link Integer}, but {@link Float} and {@link Double} are
388:             *                 accepted as well.
389:             * @param  geophysicsValueRange The range of geophysics values for this category.
390:             *                 Element class is usually {@link Float} or {@link Double}.
391:             *
392:             * @throws ClassCastException if the range element class is not a {@link Number} subclass.
393:             * @throws IllegalArgumentException if the range is invalid.
394:             */
395:            public Category(final CharSequence name, final Color[] colors,
396:                    final NumberRange sampleValueRange,
397:                    final NumberRange geophysicsValueRange)
398:                    throws IllegalArgumentException {
399:                this (name, colors, sampleValueRange, createLinearTransform(
400:                        sampleValueRange, geophysicsValueRange));
401:                inverse.range = geophysicsValueRange;
402:                assert range.equals(sampleValueRange);
403:            }
404:
405:            /**
406:             * Constructs a qualitative or quantitative category for samples in the specified range.
407:             * Sample values (usually integers) will be converted into geophysics values (usually
408:             * floating-point) through the {@code sampleToGeophysics} transform.
409:             *
410:             * @param  name    The category name as a {@link String} or {@link InternationalString} object.
411:             * @param  colors  A set of colors for this category. This array may have any length;
412:             *                 colors will be interpolated as needed. An array of length 1 means
413:             *                 that an uniform color should be used for all sample values. An array
414:             *                 of length 0 or a {@code null} array means that some default colors
415:             *                 should be used (usually a gradient from opaque black to opaque white).
416:             * @param  sampleValueRange The range of sample values for this category. Element class
417:             *                 is usually {@link Integer}, but {@link Float} and {@link Double} are
418:             *                 accepted as well.
419:             * @param  sampleToGeophysics A transform from sample values to geophysics values,
420:             *                 or {@code null} if this category is not a quantitative one.
421:             *
422:             * @throws ClassCastException if the range element class is not a {@link Number} subclass.
423:             * @throws IllegalArgumentException if the range is invalid.
424:             */
425:            public Category(final CharSequence name, final Color[] colors,
426:                    final NumberRange sampleValueRange,
427:                    final MathTransform1D sampleToGeophysics)
428:                    throws IllegalArgumentException {
429:                this (name, toARGB(colors), sampleValueRange, sampleToGeophysics);
430:            }
431:
432:            /**
433:             * Constructs a category with the specified math transform.  This private constructor is
434:             * used for both qualitative and quantitative category constructors.    It also used by
435:             * {@link #recolor} in order to construct a new category similar to this one except for
436:             * ARGB codes.
437:             */
438:            private Category(final CharSequence name, final int[] ARGB,
439:                    final NumberRange range, MathTransform1D sampleToGeophysics)
440:                    throws IllegalArgumentException {
441:                ensureNonNull("name", name);
442:                this .name = SimpleInternationalString.wrap(name);
443:                this .ARGB = ARGB;
444:                this .range = range;
445:                Class type = range.getElementClass();
446:                boolean minInc = range.isMinIncluded();
447:                boolean maxInc = range.isMaxIncluded();
448:                this .minimum = doubleValue(type, range.getMinValue(),
449:                        minInc ? 0 : +1);
450:                this .maximum = doubleValue(type, range.getMaxValue(),
451:                        maxInc ? 0 : -1);
452:                /*
453:                 * If we are constructing a qualitative category for a single NaN value,
454:                 * accepts it as a valid one.
455:                 */
456:                if (sampleToGeophysics == null
457:                        && minInc
458:                        && maxInc
459:                        && Double.isNaN(minimum)
460:                        && Double.doubleToRawLongBits(minimum) == Double
461:                                .doubleToRawLongBits(maximum)) {
462:                    inverse = this ;
463:                    transform = createLinearTransform(0, minimum);
464:                    return;
465:                }
466:                /*
467:                 * Checks the arguments. Use '!' in comparaison in order to reject NaN values,
468:                 * except for the legal case catched by the "if" block just above.
469:                 */
470:                if (!(minimum <= maximum) || Double.isInfinite(minimum)
471:                        || Double.isInfinite(maximum)) {
472:                    throw new IllegalArgumentException(Errors.format(
473:                            ErrorKeys.BAD_RANGE_$2, range.getMinValue(), range
474:                                    .getMaxValue()));
475:                }
476:                /*
477:                 * Now initialize the geophysics category.
478:                 */
479:                TransformException cause = null;
480:                try {
481:                    if (sampleToGeophysics == null) {
482:                        inverse = new GeophysicsCategory(this , false);
483:                        transform = createLinearTransform(0, inverse.minimum); // sample to geophysics
484:                        return;
485:                    }
486:                    transform = sampleToGeophysics; // Must be set before GeophysicsCategory construction!
487:                    if (sampleToGeophysics.isIdentity()) {
488:                        inverse = this ;
489:                    } else {
490:                        inverse = new GeophysicsCategory(this , true);
491:                    }
492:                    if (inverse.minimum <= inverse.maximum) {
493:                        return;
494:                    }
495:                    // If we reach this point, geophysics range is NaN. This is an illegal argument.
496:                } catch (TransformException exception) {
497:                    cause = exception;
498:                }
499:                IllegalArgumentException exception = new IllegalArgumentException(
500:                        Errors.format(ErrorKeys.BAD_TRANSFORM_$1, Utilities
501:                                .getShortClassName(sampleToGeophysics)));
502:                exception.initCause(cause);
503:                throw exception;
504:            }
505:
506:            /**
507:             * Construct a geophysics category. <strong>This constructor should never
508:             * be invoked outside {@link GeophysicsCategory} constructor.</strong>
509:             *
510:             * @param  inverse The originating {@link Category}.
511:             * @param  isQuantitative {@code true} if the originating category is quantitative.
512:             * @throws TransformException if a transformation failed.
513:             *
514:             * @todo The algorithm for finding minimum and maximum values is very simple for
515:             *       now and will not work if the transformation has local extremas. We would
516:             *       need some more sophesticated algorithm for the most general cases. Such
517:             *       a general algorithm would be usefull in {@link GeophysicsCategory#getRange}
518:             *       as well.
519:             */
520:            Category(final Category inverse, final boolean isQuantitative)
521:                    throws TransformException {
522:                assert (this  instanceof  GeophysicsCategory);
523:                assert !(inverse instanceof  GeophysicsCategory);
524:                this .inverse = inverse;
525:                this .name = inverse.name;
526:                this .ARGB = inverse.ARGB;
527:                if (!isQuantitative) {
528:                    minimum = maximum = XMath.toNaN((int) Math
529:                            .round((inverse.minimum + inverse.maximum) / 2));
530:                    transform = createLinearTransform(0, inverse.minimum); // geophysics to sample
531:                    return;
532:                }
533:                /*
534:                 * Compute 'minimum' and 'maximum' (which must be real numbers) using the transformation
535:                 * from sample to geophysics values. To be strict, we should use some numerical algorithm
536:                 * for finding a function's minimum and maximum. For linear and logarithmic functions,
537:                 * minimum and maximum are always at the bounding input values, so we are using a very
538:                 * simple algorithm for now.
539:                 */
540:                transform = (MathTransform1D) inverse.transform.inverse();
541:                final double min = inverse.transform.transform(inverse.minimum);
542:                final double max = inverse.transform.transform(inverse.maximum);
543:                if (min > max) {
544:                    minimum = max;
545:                    maximum = min;
546:                } else {
547:                    minimum = min;
548:                    maximum = max;
549:                }
550:            }
551:
552:            /**
553:             * Returns a linear transform with the supplied scale and offset values.
554:             *
555:             * @param scale  The scale factor. May be 0 for a constant transform.
556:             * @param offset The offset value. May be NaN if this method is invoked from a constructor
557:             *               for initializing {@link #transform} for a qualitative category.
558:             */
559:            static MathTransform1D createLinearTransform(final double scale,
560:                    final double offset) {
561:                return LinearTransform1D.create(scale, offset);
562:            }
563:
564:            /**
565:             * Create a linear transform mapping values from {@code sampleValueRange}
566:             * to {@code geophysicsValueRange}.
567:             */
568:            private static MathTransform1D createLinearTransform(
569:                    final NumberRange sampleValueRange,
570:                    final NumberRange geophysicsValueRange) {
571:                final Class sType = sampleValueRange.getElementClass();
572:                final Class gType = geophysicsValueRange.getElementClass();
573:                /*
574:                 * First, find the direction of the adjustment to apply to the ranges if we wanted
575:                 * all values to be inclusives. Then, check if the adjustment is really needed: if
576:                 * the values of both ranges are inclusive or exclusive, then there is no need for
577:                 * an adjustment before computing the coefficient of a linear relation.
578:                 */
579:                int sMinInc = sampleValueRange.isMinIncluded() ? 0 : +1;
580:                int sMaxInc = sampleValueRange.isMaxIncluded() ? 0 : -1;
581:                int gMinInc = geophysicsValueRange.isMinIncluded() ? 0 : +1;
582:                int gMaxInc = geophysicsValueRange.isMaxIncluded() ? 0 : -1;
583:                if (sMinInc == gMinInc)
584:                    sMinInc = gMinInc = 0;
585:                if (sMaxInc == gMaxInc)
586:                    sMaxInc = gMaxInc = 0;
587:                /*
588:                 * If the minimal geophysics value is exclusive while the minimal sample value is inclusive,
589:                 * prepares to substract 1 to the sample value in order to make it exclusive (so that sample
590:                 * and geophysics values have the same "exclusive" state).  Do similar processing on maximal
591:                 * values as well.  Note: the change is usually applied on sample values, but may be applied
592:                 * on geophysics values instead if sample are floats or geophysics values are integers.
593:                 */
594:                final boolean adjustSamples = (XMath.isInteger(sType) && !XMath
595:                        .isInteger(gType));
596:                if ((adjustSamples ? gMinInc : sMinInc) != 0) {
597:                    int swap = sMinInc;
598:                    sMinInc = -gMinInc;
599:                    gMinInc = -swap;
600:                }
601:                if ((adjustSamples ? gMaxInc : sMaxInc) != 0) {
602:                    int swap = sMaxInc;
603:                    sMaxInc = -gMaxInc;
604:                    gMaxInc = -swap;
605:                }
606:                /*
607:                 * Now, extracts the minimal and maximal values and computes the linear coefficients.
608:                 */
609:                final double minSample = doubleValue(sType, sampleValueRange
610:                        .getMinValue(), sMinInc);
611:                final double maxSample = doubleValue(sType, sampleValueRange
612:                        .getMaxValue(), sMaxInc);
613:                final double minValue = doubleValue(gType, geophysicsValueRange
614:                        .getMinValue(), gMinInc);
615:                final double maxValue = doubleValue(gType, geophysicsValueRange
616:                        .getMaxValue(), gMaxInc);
617:                double scale = (maxValue - minValue) / (maxSample - minSample);
618:                if (Double.isNaN(scale) && !Double.isNaN(maxValue - minValue)
619:                        && !Double.isNaN(maxSample - minSample)) {
620:                    scale = 1.0;
621:                }
622:                final double offset = minValue - scale * minSample;
623:                return createLinearTransform(scale, offset);
624:            }
625:
626:            /**
627:             * Returns a {@code double} value for the specified number. If {@code direction}
628:             * is non-zero, then this method will returns the closest representable number of type
629:             * {@code type} before or after the double value.
630:             *
631:             * @param type      The range element class. {@code number} must be
632:             *                  an instance of this class (this will not be checked).
633:             * @param number    The number to transform to a {@code double} value.
634:             * @param direction -1 to return the previous representable number,
635:             *                  +1 to return the next representable number, or
636:             *                   0 to return the number with no change.
637:             */
638:            private static double doubleValue(final Class type,
639:                    final Comparable number, final int direction) {
640:                assert (direction >= -1) && (direction <= +1) : direction;
641:                return XMath.rool(type, ((Number) number).doubleValue(),
642:                        direction);
643:            }
644:
645:            /**
646:             * Convert an array of colors to an array of ARGB values.
647:             * If {@code colors} is null, then a default array
648:             * will be returned.
649:             *
650:             * @param  colors The array of colors to convert (may be null).
651:             * @return The colors as ARGB values. Never null.
652:             */
653:            private static int[] toARGB(final Color[] colors) {
654:                final int[] ARGB;
655:                if (colors != null && colors.length != 0) {
656:                    ARGB = new int[colors.length];
657:                    for (int i = 0; i < ARGB.length; i++) {
658:                        ARGB[i] = colors[i].getRGB();
659:                    }
660:                } else {
661:                    ARGB = DEFAULT;
662:                }
663:                return ARGB;
664:            }
665:
666:            /**
667:             * Returns ARGB values for the specified color. If {@code color}
668:             * is null, a default ARGB code will be returned.
669:             */
670:            private static int[] toARGB(Color color, final int sample) {
671:                if (color == null) {
672:                    color = CYCLE[Math.abs(sample) % CYCLE.length];
673:                }
674:                return toARGB(new Color[] { color });
675:            }
676:
677:            /**
678:             * Returns the category name.
679:             */
680:            public InternationalString getName() {
681:                return name;
682:            }
683:
684:            /**
685:             * Returns the set of colors for this category.
686:             * Change to the returned array will not affect
687:             * this category.
688:             *
689:             * @see GridSampleDimension#getColorModel
690:             */
691:            public Color[] getColors() {
692:                final Color[] colors = new Color[ARGB.length];
693:                for (int i = 0; i < colors.length; i++) {
694:                    colors[i] = new Color(ARGB[i], true);
695:                }
696:                return colors;
697:            }
698:
699:            /**
700:             * Returns the range of sample values occurring in this category. Sample values can be
701:             * transformed into geophysics values using the {@link #getSampleToGeophysics} transform.
702:             *
703:             * @return The range of sample values.
704:             *
705:             * @see NumberRange#getMinimum(boolean)
706:             * @see NumberRange#getMaximum(boolean)
707:             * @see GridSampleDimension#getMinimumValue()
708:             * @see GridSampleDimension#getMaximumValue()
709:             */
710:            public NumberRange getRange() {
711:                assert range != null;
712:                return range;
713:            }
714:
715:            /**
716:             * Returns a transform from sample values to geophysics values. If this category
717:             * is not a quantitative one, then this method returns {@code null}.
718:             */
719:            public MathTransform1D getSampleToGeophysics() {
720:                return isQuantitative() ? transform : null;
721:            }
722:
723:            /**
724:             * Returns {@code true} if this category is quantitative. A quantitative category
725:             * has a non-null {@link #getSampleToGeophysics() sampleToGeophysics} transform.
726:             *
727:             * @return {@code true} if this category is quantitative, or
728:             *         {@code false} if this category is qualitative.
729:             */
730:            public boolean isQuantitative() {
731:                return !Double.isNaN(inverse.minimum)
732:                        && !Double.isNaN(inverse.maximum);
733:            }
734:
735:            /**
736:             * Returns a category for the same range of sample values but a different color palette.
737:             * The array given in argument may have any length; colors will be interpolated as needed.
738:             * An array of length 1 means that an uniform color should be used for all sample values.
739:             * An array of length 0 or a {@code null} array means that some default colors should be
740:             * used (usually a gradient from opaque black to opaque white).
741:             *
742:             * @param colors A set of colors for the new category. 
743:             * @return A category with the new color palette, or {@code this}
744:             *         if the new colors are identical to the current ones.
745:             *
746:             * @see org.geotools.coverage.processing.ColorMap#recolor
747:             */
748:            public Category recolor(final Color[] colors) {
749:                // GeophysicsCategory overrides this method in such
750:                // a way that the case below should never occurs.
751:                assert !(this  instanceof  GeophysicsCategory) : this ;
752:                final int[] newARGB = toARGB(colors);
753:                if (Arrays.equals(ARGB, newARGB)) {
754:                    return this ;
755:                }
756:                // The range can be null only for GeophysicsCategory cases. Because
757:                // the later override this method, the case below should never occurs.
758:                assert range != null : this ;
759:                final Category newCategory = new Category(name, newARGB, range,
760:                        getSampleToGeophysics());
761:                newCategory.inverse.range = inverse.range; // Share a common instance.
762:                return newCategory;
763:            }
764:
765:            /**
766:             * Changes the mapping from sample to geophysics values. This method returns a category with
767:             * a "{@linkplain #getSampleToGeophysics sample to geophysics}" transformation set to the
768:             * specified one. Other properties like the {@linkplain #getRange sample value range}
769:             * and the {@linkplain #getColors colors} are unchanged.
770:             * <p>
771:             * <strong>Note about geophysics categories:</strong> The above rules are straightforward
772:             * when applied on non-geophysics category, but this method can be invoked on geophysics
773:             * category (as returned by <code>{@linkplain #geophysics geophysics}(true)</code>) as well.
774:             * Since geophysics categories are already the result of some "sample to geophysics"
775:             * transformation, invoking this method on those is equivalent to
776:             * {@linkplain MathTransformFactory#createConcatenatedTransform concatenate}
777:             * this "sample to geophysics" transform with the specified one.
778:             *
779:             * @param  sampleToGeophysics The new {@linkplain #getSampleToGeophysics sample to geophysics}
780:             *         transform.
781:             * @return A category using the specified transform.
782:             *
783:             * @see #getSampleToGeophysics
784:             * @see GridSampleDimension#rescale
785:             */
786:            public Category rescale(final MathTransform1D sampleToGeophysics) {
787:                if (Utilities.equals(sampleToGeophysics, transform)) {
788:                    return this ;
789:                }
790:                return new Category(name, ARGB, range, sampleToGeophysics);
791:            }
792:
793:            /**
794:             * If {@code true}, returns the geophysics companion of this category.   By definition, a
795:             * <cite>geophysics category</cite> is a category with a {@linkplain #getRange range of sample
796:             * values} transformed in such a way that the {@link #getSampleToGeophysics sampleToGeophysics}
797:             * transform is always the identity transform, or {@code null} if no such transform existed
798:             * in the first place. In other words, the range of sample values in a geophysics category maps
799:             * directly the "real world" values without the need for any transformation.
800:             * <p>
801:             * {@code Category} objects live by pair: a <cite>geophysics</cite> one (used for
802:             * computation) and a <cite>non-geophysics</cite> one (used for packing data, usually as
803:             * integers). The {@code geo} argument specifies which object from the pair is wanted,
804:             * regardless if this method is invoked on the geophysics or non-geophysics instance of the
805:             * pair. In other words, the result of {@code geophysics(b1).geophysics(b2).geophysics(b3)}
806:             * depends only on the value in the last call ({@code b3}).
807:             * <p>
808:             * Newly constructed categories are non-geophysics (i.e. a {@linkplain #getSampleToGeophysics
809:             * sample to geophysics} transform must be applied in order to gets geophysics values).
810:             *
811:             * @param  geo {@code true} to get a category with an identity
812:             *         {@linkplain #getSampleToGeophysics transform} and a {@linkplain #getRange range of
813:             *         sample values} matching the geophysics values, or {@code false} to get back the
814:             *         original category (the one constructed with {@code new Category(...)}).
815:             * @return The category. Never {@code null}, but may be {@code this}.
816:             *
817:             * @see GridSampleDimension#geophysics
818:             * @see org.geotools.coverage.grid.GridCoverage2D#geophysics
819:             */
820:            public Category geophysics(final boolean geo) {
821:                return geo ? inverse : this ;
822:            }
823:
824:            /**
825:             * Returns a hash value for this category.
826:             * This value need not remain consistent between
827:             * different implementations of the same class.
828:             */
829:            public int hashCode() {
830:                return name.hashCode();
831:            }
832:
833:            /**
834:             * Compares the specified object with
835:             * this category for equality.
836:             */
837:            public boolean equals(final Object object) {
838:                if (object == this ) {
839:                    // Slight optimization
840:                    return true;
841:                }
842:                if (object != null && object.getClass().equals(getClass())) {
843:                    final Category that = (Category) object;
844:                    if (Double.doubleToRawLongBits(minimum) == Double
845:                            .doubleToRawLongBits(that.minimum)
846:                            && Double.doubleToRawLongBits(maximum) == Double
847:                                    .doubleToRawLongBits(that.maximum)
848:                            && Utilities.equals(this .transform, that.transform)
849:                            && Utilities.equals(this .name, that.name)
850:                            && Arrays.equals(this .ARGB, that.ARGB)) {
851:                        // Special test for 'range', since 'GeophysicsCategory'
852:                        // computes it only when first needed.
853:                        if (this .range != null && that.range != null) {
854:                            if (!Utilities.equals(this .range, that.range)) {
855:                                return false;
856:                            }
857:                            if (inverse instanceof  GeophysicsCategory) {
858:                                assert inverse.equals(that.inverse);
859:                            }
860:                            return true;
861:                        }
862:                        assert (this  instanceof  GeophysicsCategory);
863:                        return true;
864:                    }
865:                }
866:                return false;
867:            }
868:
869:            /**
870:             * Returns a string representation of this category.
871:             * The returned string is implementation dependent.
872:             * It is usually provided for debugging purposes.
873:             */
874:            public String toString() {
875:                final StringBuffer buffer = new StringBuffer(Utilities
876:                        .getShortClassName(this ));
877:                buffer.append("(\"");
878:                buffer.append(name);
879:                buffer.append("\":[");
880:                if (Double.isNaN(minimum) && Double.isNaN(maximum)) {
881:                    buffer.append("NaN(");
882:                    buffer.append(Math.round(inverse.minimum));
883:                    buffer.append("...");
884:                    buffer.append(Math.round(inverse.maximum));
885:                    buffer.append(')');
886:                } else {
887:                    if (XMath.isInteger(getRange().getElementClass())) {
888:                        buffer.append(Math.round(minimum));
889:                        buffer.append("...");
890:                        buffer.append(Math.round(maximum)); // Inclusive
891:                    } else {
892:                        buffer.append(minimum);
893:                        buffer.append(" ... ");
894:                        buffer.append(maximum); // Inclusive
895:                    }
896:                }
897:                buffer.append("])");
898:                return buffer.toString();
899:            }
900:
901:            /**
902:             * Makes sure that an argument is non-null.
903:             *
904:             * @param  name   Argument name.
905:             * @param  object User argument.
906:             * @throws IllegalArgumentException if {@code object} is null.
907:             */
908:            static void ensureNonNull(final String name, final Object object)
909:                    throws IllegalArgumentException {
910:                if (object == null) {
911:                    throw new IllegalArgumentException(Errors.format(
912:                            ErrorKeys.NULL_ARGUMENT_$1, name));
913:                }
914:            }
915:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.