001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment 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 and extensions
023: import java.util.Map;
024: import java.util.HashMap;
025: import java.util.Locale;
026: import java.awt.color.ColorSpace;
027: import java.awt.image.ColorModel;
028: import java.awt.image.DataBuffer;
029: import java.awt.image.IndexColorModel;
030: import java.awt.image.SampleModel;
031: import javax.media.jai.util.Range;
032:
033: // OpenGIS dependencies
034: import org.opengis.coverage.ColorInterpretation;
035: import org.opengis.coverage.SampleDimensionType;
036: import org.opengis.util.InternationalString;
037:
038: // Geotools dependencies
039: import org.geotools.resources.i18n.Errors;
040: import org.geotools.resources.i18n.ErrorKeys;
041: import org.geotools.resources.i18n.Vocabulary;
042: import org.geotools.resources.i18n.VocabularyKeys;
043: import org.geotools.resources.image.ColorUtilities;
044: import org.geotools.util.AbstractInternationalString;
045: import org.geotools.util.SimpleInternationalString;
046: import org.geotools.util.NumberRange;
047:
048: /**
049: * Utility methods for choosing a {@linkplain SampleModel sample model} or a
050: * {@linkplain ColorModel color model} on the basis of a range of values.
051: * This class provides also some methods for mapping {@link SampleDimensionType}
052: * to {@link DataBuffer} types.
053: *
054: * @since 2.1
055: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/TypeMap.java $
056: * @version $Id: TypeMap.java 25972 2007-06-21 13:38:35Z desruisseaux $
057: * @author Martin Desruisseaux
058: */
059: public final class TypeMap {
060: /**
061: * The mapping of {@link SampleDimensionType} to {@link DataBuffer} types.
062: *
063: * @todo Simplify when we will be allowed to compile with J2SE 1.5
064: * (using auto-boxing and static imports).
065: */
066: private static final TypeMap[] MAP = new TypeMap[SampleDimensionType
067: .values().length];
068: static {
069: final Map pool = new HashMap(32);
070: final Float M1 = new Float(-Float.MAX_VALUE);
071: final Float P1 = new Float(Float.MAX_VALUE);
072: final Double M2 = new Double(-Double.MAX_VALUE);
073: final Double P2 = new Double(Double.MAX_VALUE);
074: // The constructor will register automatically those objects in the above array.
075: new TypeMap(SampleDimensionType.UNSIGNED_1BIT,
076: DataBuffer.TYPE_BYTE, (byte) 1, false, false, pool);
077: new TypeMap(SampleDimensionType.UNSIGNED_2BITS,
078: DataBuffer.TYPE_BYTE, (byte) 2, false, false, pool);
079: new TypeMap(SampleDimensionType.UNSIGNED_4BITS,
080: DataBuffer.TYPE_BYTE, (byte) 4, false, false, pool);
081: new TypeMap(SampleDimensionType.UNSIGNED_8BITS,
082: DataBuffer.TYPE_BYTE, (byte) 8, false, false, pool);
083: new TypeMap(SampleDimensionType.SIGNED_8BITS,
084: DataBuffer.TYPE_BYTE, (byte) 8, true, false, pool);
085: new TypeMap(SampleDimensionType.UNSIGNED_16BITS,
086: DataBuffer.TYPE_USHORT, (byte) 16, false, false, pool);
087: new TypeMap(SampleDimensionType.SIGNED_16BITS,
088: DataBuffer.TYPE_SHORT, (byte) 16, true, false, pool);
089: new TypeMap(SampleDimensionType.UNSIGNED_32BITS,
090: DataBuffer.TYPE_INT, (byte) 32, false, false, pool);
091: new TypeMap(SampleDimensionType.SIGNED_32BITS,
092: DataBuffer.TYPE_INT, (byte) 32, true, false, pool);
093: new TypeMap(SampleDimensionType.REAL_32BITS,
094: DataBuffer.TYPE_FLOAT, (byte) 32, true, true, M1, P1,
095: pool);
096: new TypeMap(SampleDimensionType.REAL_64BITS,
097: DataBuffer.TYPE_DOUBLE, (byte) 64, true, true, M2, P2,
098: pool);
099: };
100:
101: /**
102: * One of {@link SampleDimensionType} code list.
103: */
104: private final SampleDimensionType code;
105:
106: /**
107: * The {@link DataBuffer} type. Must be one of the following constants:
108: * {@link DataBuffer#TYPE_BYTE}, {@link DataBuffer#TYPE_USHORT},
109: * {@link DataBuffer#TYPE_SHORT}, {@link DataBuffer#TYPE_INT},
110: * {@link DataBuffer#TYPE_FLOAT}, {@link DataBuffer#TYPE_DOUBLE}.
111: */
112: private final int type;
113:
114: /**
115: * The size in bits. The value range from 1 to 64. This is different than
116: * {@link DataBuffer#getDataTypeSize}, which have values ranging from 8 to 64.
117: */
118: private final byte size;
119:
120: /**
121: * {@code true} for signed sample type.
122: */
123: private final boolean signed;
124:
125: /**
126: * {@code true} for floating-point data type.
127: */
128: private final boolean real;
129:
130: /**
131: * The full range of sample values.
132: */
133: private final NumberRange range;
134:
135: /**
136: * The range of positive sample values (excluding 0). This range is non-null only for unsigned
137: * type. A range excluding 0 is sometime usefull when the 0 value is reserved for a "no data"
138: * category.
139: */
140: private final NumberRange positiveRange;
141:
142: /**
143: * The name as an international string.
144: */
145: private final InternationalString name = new AbstractInternationalString() {
146: public String toString(final Locale locale) {
147: return Vocabulary.getResources(locale).getString(
148: VocabularyKeys.DATA_TYPE_$2,
149: new Integer(real ? 2 : signed ? 1 : 0),
150: new Integer(size));
151: }
152: };
153:
154: /**
155: * Constructs a new mapping with the specified value.
156: */
157: private TypeMap(final SampleDimensionType code, final int type,
158: final byte size, final boolean signed, final boolean real,
159: final Map pool) {
160: this (code, type, size, signed, real, null, null, pool);
161: }
162:
163: /**
164: * Constructs a new mapping with the specified value.
165: */
166: private TypeMap(final SampleDimensionType code, final int type,
167: final byte size, final boolean signed, final boolean real,
168: Number lower, Number upper, final Map pool) {
169: Number one = null;
170: if (lower == null) {
171: final long max = (1L << (signed ? size - 1 : size)) - 1;
172: final long min = signed ? ~max : 0; // Tild (~), not minus sign (-).
173: if (max <= Byte.MAX_VALUE) {
174: lower = new Byte((byte) min);
175: upper = new Byte((byte) max);
176: one = new Byte((byte) 1);
177: } else if (max <= Short.MAX_VALUE) {
178: lower = new Short((short) min);
179: upper = new Short((short) max);
180: one = new Short((short) 1);
181: } else if (max <= Integer.MAX_VALUE) {
182: lower = new Integer((int) min);
183: upper = new Integer((int) max);
184: one = new Integer((int) 1);
185: } else {
186: lower = new Long(min);
187: upper = new Long(max);
188: one = new Long(1L);
189: }
190: lower = unique(pool, lower);
191: upper = unique(pool, upper);
192: one = unique(pool, one);
193: assert lower.longValue() == min;
194: assert upper.longValue() == max;
195: }
196: assert ((Comparable) lower).compareTo(upper) < 0 : upper;
197: final Class c = upper.getClass();
198: this .code = code;
199: this .type = type;
200: this .size = size;
201: this .signed = signed;
202: this .real = real;
203: this .range = new NumberRange(c, lower, upper);
204: this .positiveRange = signed ? null : new NumberRange(c, one,
205: upper);
206: final int ordinal = code.ordinal();
207: assert MAP[ordinal] == null : code;
208: MAP[ordinal] = this ;
209: assert code.equals(getSampleDimensionType(range)) : code;
210: }
211:
212: /**
213: * Returns a single instance of the specified number.
214: */
215: private static Number unique(final Map pool, final Number n) {
216: final Number candidate = (Number) pool.put(n, n);
217: if (candidate == null) {
218: return n;
219: }
220: pool.put(candidate, candidate);
221: return candidate;
222: }
223:
224: /**
225: * Returns the smallest sample dimension type capable to hold the specified range of values.
226: *
227: * @param range The range of values.
228: * @return The smallest sample dimension type for the specified range.
229: */
230: public static SampleDimensionType getSampleDimensionType(
231: final Range range) {
232: final Class type = range.getElementClass();
233: if (Double.class.isAssignableFrom(type)) {
234: return SampleDimensionType.REAL_64BITS;
235: }
236: if (Float.class.isAssignableFrom(type)) {
237: return SampleDimensionType.REAL_32BITS;
238: }
239: long min = ((Number) range.getMinValue()).longValue();
240: long max = ((Number) range.getMaxValue()).longValue();
241: if (!range.isMinIncluded())
242: min++;
243: if (!range.isMaxIncluded())
244: max--;
245: return getSampleDimensionType(min, max);
246: }
247:
248: /**
249: * Returns the smallest sample dimension type capable to hold the specified range of values.
250: * An heuristic approach is used for non-integer values.
251: *
252: * @param min The lower value, inclusive.
253: * @param max The upper value, <strong>inclusive</strong> as well.
254: * @return The smallest sample dimension type for the specified range.
255: */
256: public static SampleDimensionType getSampleDimensionType(
257: double min, double max) {
258: final long lgMin = (long) min;
259: if (lgMin == min) {
260: final long lgMax = (long) max;
261: if (lgMax == max) {
262: return getSampleDimensionType(lgMin, lgMax);
263: }
264: }
265: min = Math.abs(min);
266: max = Math.abs(max);
267: if (Math.min(min, max) >= Float.MIN_VALUE
268: && Math.max(min, max) <= Float.MAX_VALUE) {
269: return SampleDimensionType.REAL_32BITS;
270: }
271: return SampleDimensionType.REAL_64BITS;
272: }
273:
274: /**
275: * Returns the smallest sample dimension type capable to hold the specified range of values.
276: *
277: * @param min The lower value, inclusive.
278: * @param max The upper value, <strong>inclusive</strong> as well.
279: * @return The smallest sample dimension type for the specified range.
280: */
281: public static SampleDimensionType getSampleDimensionType(
282: final long min, final long max) {
283: if (min >= 0) {
284: if (max < (1L << 1))
285: return SampleDimensionType.UNSIGNED_1BIT;
286: if (max < (1L << 2))
287: return SampleDimensionType.UNSIGNED_2BITS;
288: if (max < (1L << 4))
289: return SampleDimensionType.UNSIGNED_4BITS;
290: if (max < (1L << 8))
291: return SampleDimensionType.UNSIGNED_8BITS;
292: if (max < (1L << 16))
293: return SampleDimensionType.UNSIGNED_16BITS;
294: if (max < (1L << 32))
295: return SampleDimensionType.UNSIGNED_32BITS;
296: } else {
297: if (min >= Byte.MIN_VALUE && max <= Byte.MAX_VALUE)
298: return SampleDimensionType.SIGNED_8BITS;
299: if (min >= Short.MIN_VALUE && max <= Short.MAX_VALUE)
300: return SampleDimensionType.SIGNED_16BITS;
301: if (min >= Integer.MIN_VALUE && max <= Integer.MAX_VALUE)
302: return SampleDimensionType.SIGNED_32BITS;
303: }
304: return SampleDimensionType.REAL_32BITS;
305: }
306:
307: /**
308: * Returns the sample dimension type for the specified sample model and band number. If
309: * the sample model use an undefined data type, then this method returns {@code null}.
310: *
311: * @param model The sample model.
312: * @param band The band to query.
313: * @return The sample dimension type for the specified sample model and band number.
314: * @throws IllegalArgumentException if the band number is not in the valid range.
315: */
316: public static SampleDimensionType getSampleDimensionType(
317: final SampleModel model, final int band)
318: throws IllegalArgumentException {
319: if (band < 0 || band >= model.getNumBands()) {
320: throw new IllegalArgumentException(Errors.format(
321: ErrorKeys.BAD_BAND_NUMBER_$1, new Integer(band)));
322: }
323: boolean signed = true;
324: switch (model.getDataType()) {
325: case DataBuffer.TYPE_DOUBLE:
326: return SampleDimensionType.REAL_64BITS;
327: case DataBuffer.TYPE_FLOAT:
328: return SampleDimensionType.REAL_32BITS;
329: case DataBuffer.TYPE_USHORT: // Fall through
330: case DataBuffer.TYPE_BYTE:
331: signed = false; // Fall through
332: case DataBuffer.TYPE_INT:
333: case DataBuffer.TYPE_SHORT: {
334: switch (model.getSampleSize(band)) {
335: case 1:
336: return SampleDimensionType.UNSIGNED_1BIT;
337: case 2:
338: return SampleDimensionType.UNSIGNED_2BITS;
339: case 4:
340: return SampleDimensionType.UNSIGNED_4BITS;
341: case 8:
342: return signed ? SampleDimensionType.SIGNED_8BITS
343: : SampleDimensionType.UNSIGNED_8BITS;
344: case 16:
345: return signed ? SampleDimensionType.SIGNED_16BITS
346: : SampleDimensionType.UNSIGNED_16BITS;
347: case 32:
348: return signed ? SampleDimensionType.SIGNED_32BITS
349: : SampleDimensionType.UNSIGNED_32BITS;
350: }
351: }
352: }
353: return null;
354: }
355:
356: /**
357: * Returns the sample dimension type name as an international string. For example, the localized
358: * name for {@link SampleDimensionType#UNSIGNED_16BITS} is "<cite>16 bits unsigned integer</cite>"
359: * in English and "<cite>Entier non-signé sur 16 bits</cite>" in French.
360: */
361: public static InternationalString getName(
362: final SampleDimensionType type) {
363: final int ordinal = type.ordinal();
364: if (ordinal >= 0 && ordinal < MAP.length) {
365: return MAP[ordinal].name;
366: }
367: return new SimpleInternationalString(type.name());
368: }
369:
370: /**
371: * Returns the {@link DataBuffer} type. This is one of the following constants:
372: * {@link DataBuffer#TYPE_BYTE TYPE_BYTE},
373: * {@link DataBuffer#TYPE_USHORT TYPE_USHORT},
374: * {@link DataBuffer#TYPE_SHORT TYPE_SHORT},
375: * {@link DataBuffer#TYPE_INT TYPE_INT},
376: * {@link DataBuffer#TYPE_FLOAT TYPE_FLOAT},
377: * {@link DataBuffer#TYPE_DOUBLE TYPE_DOUBLE} or
378: * {@link DataBuffer#TYPE_UNDEFINED} if the type is unrecognized.
379: */
380: public static int getDataBufferType(final SampleDimensionType type) {
381: if (type != null) {
382: final int ordinal = type.ordinal();
383: if (ordinal >= 0 && ordinal < MAP.length) {
384: return MAP[ordinal].type;
385: }
386: }
387: return DataBuffer.TYPE_UNDEFINED;
388: }
389:
390: /**
391: * Returns the size in bits. The value range from 1 to 64. This is similar, but
392: * different than {@link DataBuffer#getDataTypeSize}, which have values ranging
393: * from 8 to 64.
394: */
395: public static int getSize(final SampleDimensionType type) {
396: return map(type).size;
397: }
398:
399: /**
400: * Returns {@code true} for signed sample type.
401: */
402: public static boolean isSigned(final SampleDimensionType type) {
403: return map(type).signed;
404: }
405:
406: /**
407: * Returns {@code true} for floating-point data type.
408: */
409: public static boolean isFloatingPoint(final SampleDimensionType type) {
410: return map(type).real;
411: }
412:
413: /**
414: * Returns the full range of sample values for the specified dimension type.
415: */
416: public static NumberRange getRange(final SampleDimensionType type) {
417: if (type != null) {
418: final int ordinal = type.ordinal();
419: if (ordinal >= 0 && ordinal < MAP.length) {
420: return MAP[ordinal].range;
421: }
422: }
423: return null;
424: }
425:
426: /**
427: * Returns the range of positive sample values (excluding 0). This range is non-null only for
428: * unsigned type. A range excluding 0 is sometime usefull when the 0 value is reserved for a
429: * "no data" category.
430: */
431: public static NumberRange getPositiveRange(
432: final SampleDimensionType type) {
433: if (type != null) {
434: final int ordinal = type.ordinal();
435: if (ordinal >= 0 && ordinal < MAP.length) {
436: return MAP[ordinal].positiveRange;
437: }
438: }
439: return null;
440: }
441:
442: /**
443: * Returns the mapper for the specified sample dimension type. If no map is found for the
444: * specified sample dimension type, then an exception is thrown.
445: */
446: private static TypeMap map(final SampleDimensionType type)
447: throws IllegalArgumentException {
448: if (type != null) {
449: final int ordinal = type.ordinal();
450: if (ordinal >= 0 && ordinal < MAP.length) {
451: final TypeMap map = MAP[ordinal];
452: if (map != null) {
453: return map;
454: }
455: }
456: }
457: throw new IllegalArgumentException(Errors.format(
458: ErrorKeys.ILLEGAL_ARGUMENT_$2, "type", type));
459: }
460:
461: /**
462: * Wraps the specified value into a number of the specified data type. If the
463: * value can't fit in the specified type, then a wider type is choosen unless
464: * {@code allowWidening} is {@code false}.
465: *
466: * @param value The value to wrap in a {@link Number} object.
467: * @param type A constant from the {@link SampleDimensionType} code list.
468: * @param allowWidening {@code true} if this method is allowed to returns
469: * a wider type than the usual one for the specified {@code type}.
470: * @return The value as a {@link Number}.
471: * @throws IllegalArgumentException if {@code type} is not a recognized constant.
472: * @throws IllegalArgumentException if {@code allowWidening} is {@code false}
473: * and the specified {@code value} can't fit in the specified sample type.
474: */
475: public static Number wrapSample(final double value,
476: final SampleDimensionType type, final boolean allowWidening)
477: throws IllegalArgumentException {
478: /*
479: * Note about 'ordinal' computation: We would like to switch on SampleDimensionType
480: * ordinal values. But the compiler requires constant values, and doesn't recognize
481: * SampleDimensionType ordinal as such. As a workaround, we use the sample size (in
482: * bits) with the following convention: negative value if signed, and offset by 16
483: * bits if floating point numbers.
484: */
485: final TypeMap map = map(type);
486: int ordinal = map.size;
487: if (map.real) {
488: ordinal <<= 16;
489: } else if (map.signed) {
490: ordinal = -ordinal;
491: }
492: switch (ordinal) {
493: case 1: // Fall through
494: case 2: // Fall through
495: case 4: // Fall through
496: case -8: {
497: final byte candidate = (byte) value;
498: if (candidate == value) {
499: return new Byte(candidate);
500: }
501: if (!allowWidening)
502: break;
503: // Fall through
504: }
505: case 8: // Fall through
506: case -16: {
507: final short candidate = (short) value;
508: if (candidate == value) {
509: return new Short(candidate);
510: }
511: if (!allowWidening)
512: break;
513: // Fall through
514: }
515: case 16: // Fall through
516: case -32: {
517: final int candidate = (int) value;
518: if (candidate == value) {
519: return new Integer(candidate);
520: }
521: if (!allowWidening)
522: break;
523: // Fall through
524: }
525: case 32: {
526: final long candidate = (long) value;
527: if (candidate == value) {
528: return new Long(candidate);
529: }
530: if (!allowWidening)
531: break;
532: // Fall through
533: }
534: case (32 << 16): {
535: if (!allowWidening || Math.abs(value) <= Float.MAX_VALUE) {
536: return new Float((float) value);
537: }
538: // Fall through
539: }
540: case (64 << 16): {
541: return new Double(value);
542: }
543: default: {
544: throw new IllegalArgumentException(Errors.format(
545: ErrorKeys.ILLEGAL_ARGUMENT_$2, "type", type));
546: }
547: }
548: throw new IllegalArgumentException(Errors.format(
549: ErrorKeys.ILLEGAL_ARGUMENT_$2, "value", new Double(
550: value)));
551: }
552:
553: /**
554: * Returns the color interpretation code for the specified color model and band number.
555: *
556: * @param model The color model.
557: * @param band The band to query.
558: * @return The code for the specified color model and band number.
559: * @throws IllegalArgumentException if the band number is not in the valid range.
560: */
561: public static ColorInterpretation getColorInterpretation(
562: final ColorModel model, final int band)
563: throws IllegalArgumentException {
564: if (band < 0 || band >= ColorUtilities.getNumBands(model)) {
565: throw new IllegalArgumentException(Errors.format(
566: ErrorKeys.BAD_BAND_NUMBER_$1, new Integer(band)));
567: }
568: if (model instanceof IndexColorModel) {
569: return ColorInterpretation.PALETTE_INDEX;
570: }
571: switch (model.getColorSpace().getType()) {
572: case ColorSpace.TYPE_GRAY: {
573: switch (band) {
574: case 0:
575: return ColorInterpretation.GRAY_INDEX;
576: default:
577: return ColorInterpretation.UNDEFINED;
578: }
579: }
580: case ColorSpace.TYPE_RGB: {
581: switch (band) {
582: case 0:
583: return ColorInterpretation.RED_BAND;
584: case 1:
585: return ColorInterpretation.GREEN_BAND;
586: case 2:
587: return ColorInterpretation.BLUE_BAND;
588: case 3:
589: return ColorInterpretation.ALPHA_BAND;
590: default:
591: return ColorInterpretation.UNDEFINED;
592: }
593: }
594: case ColorSpace.TYPE_HSV: {
595: switch (band) {
596: case 0:
597: return ColorInterpretation.HUE_BAND;
598: case 1:
599: return ColorInterpretation.SATURATION_BAND;
600: case 2:
601: return ColorInterpretation.LIGHTNESS_BAND;
602: default:
603: return ColorInterpretation.UNDEFINED;
604: }
605: }
606: case ColorSpace.TYPE_CMY:
607: case ColorSpace.TYPE_CMYK: {
608: switch (band) {
609: case 0:
610: return ColorInterpretation.CYAN_BAND;
611: case 1:
612: return ColorInterpretation.MAGENTA_BAND;
613: case 2:
614: return ColorInterpretation.YELLOW_BAND;
615: case 3:
616: return ColorInterpretation.BLACK_BAND;
617: default:
618: return ColorInterpretation.UNDEFINED;
619: }
620: }
621: default:
622: return ColorInterpretation.UNDEFINED;
623: }
624: }
625: }
|