001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.coverage.grid.io;
017:
018: import java.awt.Color;
019: import java.awt.Rectangle;
020: import java.awt.color.ColorSpace;
021: import java.awt.geom.AffineTransform;
022: import java.awt.geom.Rectangle2D;
023: import java.awt.image.ColorModel;
024: import java.awt.image.DataBuffer;
025: import java.awt.image.IndexColorModel;
026: import java.io.IOException;
027: import java.text.ParseException;
028: import java.util.HashMap;
029: import java.util.logging.Level;
030: import java.util.logging.Logger;
031:
032: import javax.imageio.ImageReadParam;
033: import javax.imageio.ImageReader;
034: import javax.imageio.stream.ImageInputStream;
035: import javax.media.jai.IHSColorSpace;
036: import javax.media.jai.PlanarImage;
037: import javax.units.Unit;
038: import javax.units.UnitFormat;
039:
040: import org.geotools.coverage.Category;
041: import org.geotools.coverage.FactoryFinder;
042: import org.geotools.coverage.GridSampleDimension;
043: import org.geotools.coverage.grid.GeneralGridRange;
044: import org.geotools.coverage.grid.GridCoverage2D;
045: import org.geotools.coverage.grid.GridCoverageFactory;
046: import org.geotools.data.DataSourceException;
047: import org.geotools.factory.Hints;
048: import org.geotools.geometry.GeneralEnvelope;
049: import org.geotools.referencing.operation.BufferedCoordinateOperationFactory;
050: import org.geotools.referencing.operation.transform.LinearTransform1D;
051: import org.geotools.resources.CRSUtilities;
052: import org.geotools.util.NumberRange;
053: import org.geotools.util.logging.Logging;
054: import org.opengis.coverage.MetadataNameNotFoundException;
055: import org.opengis.coverage.grid.GridCoverage;
056: import org.opengis.coverage.grid.GridCoverageReader;
057: import org.opengis.coverage.grid.GridRange;
058: import org.opengis.referencing.FactoryException;
059: import org.opengis.referencing.crs.CoordinateReferenceSystem;
060: import org.opengis.referencing.operation.CoordinateOperationFactory;
061: import org.opengis.referencing.operation.MathTransform;
062: import org.opengis.referencing.operation.TransformException;
063:
064: /**
065: * This class is a first attempt for providing a way to get more informations
066: * out of a single 2D raster datasets (x,y). It is worth to remark that for the
067: * moment this is thought for 2D rasters not for 3D or 4D rasters (x,y,z,t).
068: *
069: * <p>
070: * The main drawback I see with the current GeoApi GridCoverageReader interface
071: * is that there is no way to get real information about a raster source unless
072: * you instantiate a GridCoverage. As an instance it is impossible to know the
073: * envelope, the number of overviews, the tile size. This information is needed
074: * in order to perform decimation on reading or to use built-in overviews<br>
075: * This really impacts the ability to exploit raster datasets in a desktop
076: * environment where caching is crucial.
077: *
078: * @author Simone Giannecchini
079: * @since 2.3
080: * @version 0.2
081: */
082: public abstract class AbstractGridCoverage2DReader implements
083: GridCoverageReader {
084:
085: /** The {@link Logger} for this {@link AbstractGridCoverage2DReader}. */
086: private final static Logger LOGGER = Logging
087: .getLogger("org.geotools.data.coverage.grid");
088:
089: /** Caches a default GridCoverageFactory for usage in plugins. */
090: protected final static GridCoverageFactory coverageFactory = FactoryFinder
091: .getGridCoverageFactory(null);
092:
093: protected static final double EPS = 1E-6;
094:
095: /** Buffered factory for coordinate operations. */
096: protected final static CoordinateOperationFactory operationFactory = new BufferedCoordinateOperationFactory(
097: new Hints(Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE));
098:
099: /**
100: * Default color ramp. Preset colors used to generate an Image from the raw
101: * data
102: */
103: protected final static Color[] demColors = new Color[] {
104: new Color(5, 90, 5), new Color(150, 200, 150),
105: new Color(190, 150, 20), new Color(100, 100, 50),
106: new Color(200, 210, 220), Color.WHITE, Color.WHITE,
107: Color.WHITE, Color.WHITE };
108:
109: /**
110: * This contains the maximum number of grid coverages in the file/stream.
111: * Until multi-image files are supported, this is going to be 0 or 1.
112: */
113: protected volatile int numOverviews = 0;
114:
115: /** 2DGridToWorld math transform. */
116: protected MathTransform raster2Model = null;
117:
118: /** crs for this coverage */
119: protected CoordinateReferenceSystem crs = null;
120:
121: /** Envelope read from file */
122: protected GeneralEnvelope originalEnvelope = null;
123:
124: /** Coverage name */
125: protected String coverageName = "geotools_coverage";
126:
127: /** Source to read from */
128: protected Object source = null;
129:
130: /** Hints used by the {@link AbstractGridCoverage2DReader} subclasses. */
131: protected Hints hints = new Hints(new HashMap(5));
132:
133: /**
134: * Highest resolution availaible for this reader.
135: */
136: protected double[] highestRes = null;
137:
138: /** Temp variable used in many readers. */
139: protected boolean closeMe;
140:
141: /**
142: * In case we are trying to read from a GZipped file this will be set to
143: * true.
144: */
145: protected boolean gzipped;
146:
147: /**
148: * The original {@link GridRange} for the {@link GridCoverage2D} of this
149: * reader.
150: */
151: protected GeneralGridRange originalGridRange = null;
152:
153: /**
154: * Input stream that can be used to initialize subclasses of
155: * {@link AbstractGridCoverage2DReader}.
156: */
157: protected ImageInputStream inStream = null;
158:
159: /** Resolutions avialaible through an overviews based mechanism. */
160: protected double[][] overViewResolutions = null;
161:
162: // -------------------------------------------------------------------------
163: //
164: // old support methods
165: //
166: // -------------------------------------------------------------------------
167: /**
168: * This method is responsible for preparing the read param for doing an
169: * {@link ImageReader#read(int, ImageReadParam)}.
170: *
171: *
172: * <p>
173: * This method is responsible for preparing the read param for doing an
174: * {@link ImageReader#read(int, ImageReadParam)}. It sets the passed
175: * {@link ImageReadParam} in terms of decimation on reading using the
176: * provided requestedEnvelope and requestedDim to evaluate the needed
177: * resolution. It also returns and {@link Integer} representing the index of
178: * the raster to be read when dealing with multipage raster.
179: *
180: * @param readP
181: * an instance of {@link ImageReadParam} for setting the
182: * subsampling factors.
183: * @param requestedEnvelope
184: * the {@link GeneralEnvelope} we are requesting.
185: * @param requestedDim
186: * the requested dimensions.
187: * @return the index of the raster to read in the underlying data source.
188: * @throws IOException
189: * @throws TransformException
190: */
191: protected Integer setReadParams(ImageReadParam readP,
192: GeneralEnvelope requestedEnvelope, Rectangle requestedDim)
193: throws IOException, TransformException {
194:
195: readP.setSourceSubsampling(1, 1, 0, 0);// default values for
196: // subsampling
197: // //
198: //
199: // Default image index 0
200: //
201: // //
202: Integer imageChoice = new Integer(0);
203:
204: // we are able to handle overviews properly only if the transformation
205: // is
206: // an affine transform with pure scale and translation, no rotational
207: // components
208: if (raster2Model != null && !isScaleTranslate(raster2Model))
209: return imageChoice;
210:
211: // //
212: //
213: // Check Hint to ignore overviews
214: //
215: // //
216: Object o = hints.get(Hints.IGNORE_COVERAGE_OVERVIEW);
217: if (o != null && ((Boolean) o).booleanValue()) {
218: return imageChoice;
219:
220: }
221:
222: // //
223: //
224: // Am I going to decimate or to use overviews? If this geotiff has only
225: // one page we use decimation, otherwise we use the best page avalabile.
226: // Future versions should use both.
227: //
228: // //
229: final boolean decimate = (numOverviews <= 0) ? true : false;
230:
231: // //
232: //
233: // Resolution requested. I am here computing the resolution required by
234: // the user.
235: //
236: // //
237: double[] requestedRes = getResolution(requestedEnvelope,
238: requestedDim, crs);
239: if (requestedRes == null)
240: return imageChoice;
241:
242: // //
243: //
244: // overviews or decimation
245: //
246: // //
247: if (!decimate) {
248: // /////////////////////////////////////////////////////////////////////
249: // OVERVIEWS
250: // /////////////////////////////////////////////////////////////////////
251: // Should we leave now? In case the resolution of the first level is
252: // already lower than the requested one we should use the first
253: // level and leave.
254: if (highestRes[0] - requestedRes[0] > EPS
255: && highestRes[1] - requestedRes[1] > EPS)
256: return imageChoice;
257:
258: // Should we leave now? In case the resolution of the first level is
259: // already lower than the requested one we should use the first
260: // level and leave.
261: int axis = 0;
262: if (requestedRes[0] - requestedRes[1] > EPS)
263: axis = 1;
264:
265: // //
266: //
267: // looking for the overview with the highest lower resolution
268: // compared
269: // to the requested one.
270: // This ensure more speed but less quality. In the future we should
271: // provide a hint to control this behaviour.
272: //
273: // //
274: double actRes;
275: int i = 0;
276: for (; i < numOverviews; i++) {
277: actRes = (axis == 0) ? overViewResolutions[i][0]
278: : overViewResolutions[i][1];
279: // is actual resolution lower than the requested resolution?
280: if (actRes - requestedRes[axis] > EPS) {
281:
282: i--;
283: break;
284:
285: }
286:
287: }
288: // checking that we did not exceeded the maximum number of pages.
289: if (i == numOverviews) {
290: // int subsamplingFactor=
291: imageChoice = new Integer(numOverviews);
292: } else
293: // keeping the first image at highest resolution into account in
294: // order to get the overview wit
295: imageChoice = new Integer(i + 1);
296: }
297: // /////////////////////////////////////////////////////////////////////
298: // DECIMATION ON READING
299: // /////////////////////////////////////////////////////////////////////
300: decimationOnReadingControl(imageChoice, readP, requestedRes);
301: return imageChoice;
302: }
303:
304: /**
305: * Checks the transformation is a pure scale/translate instance (using a
306: * tolerance)
307: *
308: * @param transform
309: * @return
310: */
311: protected final boolean isScaleTranslate(MathTransform transform) {
312: if (!(transform instanceof AffineTransform))
313: return false;
314: AffineTransform at = (AffineTransform) transform;
315: return at.getShearX() < EPS && at.getShearY() < EPS;
316: }
317:
318: /**
319: * This method is responsible for evaluating possible subsampling factors
320: * once the best resolution level has been found, in case we have support
321: * for overviews, or starting from the original coverage in case there are
322: * no overviews availaible.
323: *
324: * Anyhow this methof should not be called directly but subclasses should
325: * make use of the setReadParams method instead in order to transparently
326: * look for overviews.
327: *
328: * @param imageChoice
329: * @param readP
330: * @param requestedRes
331: */
332: protected final void decimationOnReadingControl(
333: Integer imageChoice, ImageReadParam readP,
334: double[] requestedRes) {
335: {
336:
337: int w, h;
338: double selectedRes[] = new double[2];
339: final int choice = imageChoice.intValue();
340: if (choice == 0) {
341: // highest resolution
342: w = originalGridRange.getLength(0);
343: h = originalGridRange.getLength(1);
344: selectedRes[0] = highestRes[0];
345: selectedRes[1] = highestRes[1];
346: } else {
347: // some overview
348: selectedRes[0] = overViewResolutions[choice - 1][0];
349: selectedRes[1] = overViewResolutions[choice - 1][1];
350: w = (int) Math.round(originalEnvelope.getLength(0)
351: / selectedRes[0]);
352: h = (int) Math.round(originalEnvelope.getLength(1)
353: / selectedRes[1]);
354:
355: }
356: // /////////////////////////////////////////////////////////////////////
357: // DECIMATION ON READING
358: // Setting subsampling factors with some checkings
359: // 1) the subsampling factors cannot be zero
360: // 2) the subsampling factors cannot be such that the w or h are
361: // zero
362: // /////////////////////////////////////////////////////////////////////
363: if (requestedRes == null) {
364: readP.setSourceSubsampling(1, 1, 0, 0);
365:
366: } else {
367: int subSamplingFactorX = (int) Math
368: .floor(requestedRes[0] / selectedRes[0]);
369: subSamplingFactorX = subSamplingFactorX == 0 ? 1
370: : subSamplingFactorX;
371:
372: while (w / subSamplingFactorX <= 0
373: && subSamplingFactorX >= 0)
374: subSamplingFactorX--;
375: subSamplingFactorX = subSamplingFactorX == 0 ? 1
376: : subSamplingFactorX;
377:
378: int subSamplingFactorY = (int) Math
379: .floor(requestedRes[1] / selectedRes[1]);
380: subSamplingFactorY = subSamplingFactorY == 0 ? 1
381: : subSamplingFactorY;
382:
383: while (h / subSamplingFactorY <= 0
384: && subSamplingFactorY >= 0)
385: subSamplingFactorY--;
386: subSamplingFactorY = subSamplingFactorY == 0 ? 1
387: : subSamplingFactorY;
388:
389: readP.setSourceSubsampling(subSamplingFactorX,
390: subSamplingFactorY, 0, 0);
391: }
392:
393: }
394: }
395:
396: /**
397: * Creates a {@link GridCoverage} for the provided {@link PlanarImage} using
398: * the {@link #originalEnvelope} that was provided for this coverage.
399: *
400: * @param image
401: * contains the data for the coverage to create.
402: * @return a {@link GridCoverage}
403: * @throws IOException
404: */
405: protected final GridCoverage createImageCoverage(PlanarImage image)
406: throws IOException {
407: return createImageCoverage(image, null);
408:
409: }
410:
411: /**
412: * Creates a {@link GridCoverage} for the provided {@link PlanarImage} using
413: * the {@link #raster2Model} that was provided for this coverage.
414: *
415: * <p>
416: * This method is vital when working with coverages that have a raster to
417: * model transformation that is not a simple scale and translate.
418: *
419: * @param image
420: * contains the data for the coverage to create.
421: * @param raster2Model
422: * is the {@link MathTransform} that maps from the raster space
423: * to the model space.
424: * @return a {@link GridCoverage}
425: * @throws IOException
426: */
427: protected final GridCoverage createImageCoverage(PlanarImage image,
428: MathTransform raster2Model) throws IOException {
429:
430: // deciding the number range
431: NumberRange geophysicRange = null;
432:
433: switch (image.getSampleModel().getTransferType()) {
434: case DataBuffer.TYPE_BYTE:
435: geophysicRange = new NumberRange(0, 255);
436:
437: break;
438:
439: case DataBuffer.TYPE_USHORT:
440: geophysicRange = new NumberRange(0, 65535);
441:
442: break;
443: // going to treat following cases as DEM
444: case DataBuffer.TYPE_INT:
445: geophysicRange = new NumberRange(Integer.MIN_VALUE,
446: Integer.MAX_VALUE);
447: break;
448: case DataBuffer.TYPE_SHORT:
449: geophysicRange = new NumberRange(Short.MIN_VALUE,
450: Short.MAX_VALUE);
451: break;
452: case DataBuffer.TYPE_DOUBLE:
453:
454: geophysicRange = new NumberRange(Double.MIN_VALUE,
455: Double.MAX_VALUE);
456: break;
457: case DataBuffer.TYPE_FLOAT:
458: geophysicRange = new NumberRange(Float.MIN_VALUE,
459: Float.MAX_VALUE);
460: return createDEMCoverage(image);
461: default:
462: throw new DataSourceException(
463: "createImageCoverage:Data buffer type not supported by this world image reader! Use byte, ushort or int");
464: }
465:
466: /**
467: * Now we shuld be able to create the sample dimensions and the
468: * categories for this coverage in a much better and meaningful way
469: * using the color model and the color space type.
470: *
471: * @todo How do we handle the NoData when it is 0?
472: *
473: */
474: // convenieience category in order to
475: final Category values = new Category("values",
476: new Color[] { Color.BLACK }, geophysicRange,
477: LinearTransform1D.IDENTITY);
478:
479: // creating bands
480: final int numBands = image.getSampleModel().getNumBands();
481: final GridSampleDimension[] bands = new GridSampleDimension[numBands];
482: // checking the names
483: final ColorModel cm = image.getColorModel();
484: final String names[] = new String[numBands];
485: // in case of index color model we are already done.
486: if (cm instanceof IndexColorModel) {
487: names[0] = "index band";
488: } else {
489: // in case of multiband image we are not done yet.
490: final ColorSpace cs = cm.getColorSpace();
491:
492: if (cs instanceof IHSColorSpace) {
493: names[0] = "Intensity band";
494: names[1] = "Hue band";
495: names[2] = "Saturation band";
496:
497: } else {
498: /**
499: *
500: *
501: * @TODO we need to support more types than the ones we have
502: * here.
503: *
504: *
505: */
506: // not IHS, let's take the type
507: final int type = cs.getType();
508: switch (type) {
509: case ColorSpace.CS_GRAY:
510: case ColorSpace.TYPE_GRAY:
511: names[0] = "grayscale band";
512: break;
513: case ColorSpace.CS_sRGB:
514: case ColorSpace.CS_LINEAR_RGB:
515: case ColorSpace.TYPE_RGB:
516: names[0] = "Red band";
517: names[1] = "Green band";
518: names[2] = "Blue band";
519: break;
520: case ColorSpace.TYPE_CMY:
521: names[0] = "Cyan band";
522: names[1] = "Magenta band";
523: names[2] = "Yellow band";
524: break;
525: case ColorSpace.TYPE_CMYK:
526: names[0] = "Cyan band";
527: names[1] = "Magenta band";
528: names[2] = "Yellow band";
529: names[3] = "K band";
530: break;
531:
532: }
533: }
534: }
535: // setting bands names.
536: for (int i = 0; i < numBands; i++) {
537:
538: bands[i] = new GridSampleDimension(names[i],
539: new Category[] { values }, null).geophysics(true);
540: }
541:
542: // creating coverage
543: if (raster2Model != null) {
544: return coverageFactory.create(coverageName, image, crs,
545: raster2Model, bands, null, null);
546: }
547:
548: return coverageFactory.create(coverageName, image,
549: new GeneralEnvelope(originalEnvelope), bands, null,
550: null);
551:
552: }
553:
554: /**
555: * Creates a {@link GridCoverage} for a coverage that is not a simple image
556: * but that contains complex dadta from measurements.
557: *
558: *
559: * <p>
560: * This usually means that the original {@link DataBuffer#getDataType()} is
561: * of one of the following types:
562: *
563: * <ul>
564: * <li>{@link DataBuffer#TYPE_FLOAT}</li>
565: * <li>{@link DataBuffer#TYPE_DOUBLE}</li>
566: * <li>{@link DataBuffer#TYPE_INT}</li>
567: * <li>{@link DataBuffer#TYPE_SHORT}</li>
568: * </ul>
569: *
570: * and it implies that we have to prepare a transformation from geophysics
571: * values to non-geophysics values.
572: *
573: * @param coverage
574: * a {@link PlanarImage} containing the source coverage.
575: * @return a {@link GridCoverage}.
576: */
577: private GridCoverage createDEMCoverage(PlanarImage coverage) {
578: // Create the SampleDimension, with colors and byte transformation
579: // needed for visualization
580: final UnitFormat unitFormat = UnitFormat.getStandardInstance();
581: Unit uom = null;
582:
583: // unit of measure is meter usually, is this a good guess?
584: try {
585: uom = unitFormat.parseUnit("m");
586: } catch (ParseException e) {
587: uom = null;
588: }
589:
590: final Category values = new Category("elevation", demColors,
591: new NumberRange(2, 10), new NumberRange(-1, 8849));
592:
593: final GridSampleDimension band = new GridSampleDimension(
594: "digital elevation", new Category[] { values }, uom)
595: .geophysics(true);
596:
597: return coverageFactory.create(coverageName, coverage,
598: originalEnvelope, new GridSampleDimension[] { band },
599: null, null);
600:
601: }
602:
603: /**
604: * This method is responsible for computing the resolutions in for the
605: * provided grid geometry in the provided crs.
606: *
607: * <P>
608: * It is worth to note that the returned resolution array is of length of 2
609: * and it always is lon, lat for the moment.<br>
610: * It might be worth to remove the axes reordering code when we are
611: * confident enough with the code to handle the north-up crs.
612: * <p>
613: * TODO use orthodromic distance?
614: *
615: * @param envelope
616: * the GeneralEnvelope
617: * @param dim
618: * @param crs
619: * @throws DataSourceException
620: */
621: protected final double[] getResolution(GeneralEnvelope envelope,
622: Rectangle2D dim, CoordinateReferenceSystem crs)
623: throws DataSourceException {
624: double[] requestedRes = null;
625: try {
626: if (dim != null && envelope != null) {
627: // do we need to transform the originalEnvelope?
628: final CoordinateReferenceSystem crs2D = CRSUtilities
629: .getCRS2D(envelope
630: .getCoordinateReferenceSystem());
631:
632: if (crs != null
633: && !CRSUtilities.equalsIgnoreMetadata(crs,
634: crs2D)) {
635: final MathTransform tr = operationFactory
636: .createOperation(crs2D, crs)
637: .getMathTransform();
638: if (!tr.isIdentity())
639: envelope = CRSUtilities.transform(tr, envelope);
640: }
641: requestedRes = new double[2];
642: requestedRes[0] = envelope.getLength(0)
643: / dim.getWidth();
644: requestedRes[1] = envelope.getLength(1)
645: / dim.getHeight();
646: }
647: return requestedRes;
648: } catch (TransformException e) {
649: throw new DataSourceException(
650: "Unable to get the resolution", e);
651: } catch (FactoryException e) {
652: throw new DataSourceException(
653: "Unable to get the resolution", e);
654: }
655: }
656:
657: /**
658: * Retrieves the {@link CoordinateReferenceSystem} for dataset pointed by
659: * this {@link AbstractGridCoverage2DReader}.
660: *
661: * @return the {@link CoordinateReferenceSystem} for dataset pointed by this
662: * {@link AbstractGridCoverage2DReader}.
663: */
664: public final CoordinateReferenceSystem getCrs() {
665: return crs;
666: }
667:
668: /**
669: * Retrieves the {@link GeneralGridRange} that represents the raster grid
670: * dimensions of the highest resolution level in this dataset.
671: *
672: * @return the {@link GeneralGridRange} that represents the raster grid
673: * dimensions of the highest resolution level in this dataset.
674: */
675: public final GeneralGridRange getOriginalGridRange() {
676: return originalGridRange;
677: }
678:
679: /**
680: * Retrieves the {@link GeneralEnvelope} for this
681: * {@link AbstractGridCoverage2DReader}.
682: *
683: * @return the {@link GeneralEnvelope} for this
684: * {@link AbstractGridCoverage2DReader}.
685: */
686: public final GeneralEnvelope getOriginalEnvelope() {
687: return originalEnvelope;
688: }
689:
690: /**
691: * Retrieves the source for this {@link AbstractGridCoverage2DReader}.
692: *
693: * @return the source for this {@link AbstractGridCoverage2DReader}.
694: */
695: public final Object getSource() {
696: return source;
697: }
698:
699: /**
700: * Disposes this reader.
701: *
702: * <p>
703: * This method just tries to close the underlying {@link ImageInputStream}.
704: */
705: public void dispose() {
706: if (inStream != null) {
707: try {
708: inStream.close();
709: } catch (IOException e) {
710: if (LOGGER.isLoggable(Level.FINE))
711: LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
712: }
713: }
714:
715: }
716:
717: /**
718: * @see org.opengis.coverage.grid.GridCoverageReader#skip()
719: */
720: public void skip() {
721: throw new UnsupportedOperationException("Unsupported opertion.");
722: }
723:
724: /**
725: * @see org.opengis.coverage.grid.GridCoverageReader#hasMoreGridCoverages()
726: */
727: public boolean hasMoreGridCoverages() {
728: throw new UnsupportedOperationException("Unsupported opertion.");
729: }
730:
731: /**
732: * @see org.opengis.coverage.grid.GridCoverageReader#listSubNames()
733: */
734: public String[] listSubNames() {
735: throw new UnsupportedOperationException("Unsupported opertion.");
736: }
737:
738: /**
739: * @see org.opengis.coverage.grid.GridCoverageReader#getCurrentSubname()
740: */
741: public String getCurrentSubname() {
742: throw new UnsupportedOperationException("Unsupported opertion.");
743: }
744:
745: /**
746: * @see org.opengis.coverage.grid.GridCoverageReader#getMetadataNames()
747: */
748: public String[] getMetadataNames() {
749: throw new UnsupportedOperationException("Unsupported opertion.");
750: }
751:
752: /**
753: * @see org.opengis.coverage.grid.GridCoverageReader#getMetadataValue(java.lang.String)
754: */
755: public String getMetadataValue(final String name)
756: throws MetadataNameNotFoundException {
757: throw new UnsupportedOperationException("Unsupported opertion.");
758: }
759:
760: /**
761: * @see org.opengis.coverage.grid.GridCoverageReader#getGridCoverageCount()
762: */
763: public int getGridCoverageCount() {
764: throw new UnsupportedOperationException("Unsupported opertion.");
765: }
766: }
|