001: //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/io/geotiff/GeoTiffReader.java $
002: /*---------------- FILE HEADER ------------------------------------------
003:
004: This file is part of deegree.
005: Copyright (C) 2001-2008 by:
006: EXSE, Department of Geography, University of Bonn
007: http://www.giub.uni-bonn.de/deegree/
008: lat/lon GmbH
009: http://www.lat-lon.de
010:
011: This library is free software; you can redistribute it and/or
012: modify it under the terms of the GNU Lesser General Public
013: License as published by the Free Software Foundation; either
014: version 2.1 of the License, or (at your option) any later version.
015:
016: This library is distributed in the hope that it will be useful,
017: but WITHOUT ANY WARRANTY; without even the implied warranty of
018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: Lesser General Public License for more details.
020:
021: You should have received a copy of the GNU Lesser General Public
022: License along with this library; if not, write to the Free Software
023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024:
025: Contact:
026:
027: Andreas Poth
028: lat/lon GmbH
029: Aennchenstr. 19
030: 53115 Bonn
031: Germany
032: E-Mail: poth@lat-lon.de
033:
034: Prof. Dr. Klaus Greve
035: Department of Geography
036: University of Bonn
037: Meckenheimer Allee 166
038: 53115 Bonn
039: Germany
040: E-Mail: greve@giub.uni-bonn.de
041:
042:
043: ---------------------------------------------------------------------------*/
044: package org.deegree.io.geotiff;
045:
046: import java.io.File;
047: import java.io.FileInputStream;
048: import java.io.FileNotFoundException;
049: import java.io.IOException;
050: import java.util.HashMap;
051: import java.util.StringTokenizer;
052:
053: import org.apache.batik.ext.awt.image.codec.FileCacheSeekableStream;
054: import org.apache.batik.ext.awt.image.codec.tiff.TIFFDecodeParam;
055: import org.apache.batik.ext.awt.image.codec.tiff.TIFFDirectory;
056: import org.apache.batik.ext.awt.image.codec.tiff.TIFFField;
057: import org.apache.batik.ext.awt.image.codec.tiff.TIFFImage;
058: import org.deegree.framework.log.ILogger;
059: import org.deegree.framework.log.LoggerFactory;
060: import org.deegree.model.spatialschema.Envelope;
061: import org.deegree.model.spatialschema.GeometryFactory;
062:
063: /**
064: * <p>
065: * <tt>
066: * TIFF type :: Java type<br>
067: * TIFF_BYTE :: byte<br>
068: * TIFF_ASCII :: String<br>
069: * TIFF_SHORT :: char<br>
070: * TIFF_LONG :: long<br>
071: * TIFF_RATIONAL :: long[2]<br>
072: * TIFF_SBYTE :: byte<br>
073: * TIFF_UNDEFINED :: byte<br>
074: * TIFF_SSHORT :: short<br>
075: * TIFF_SLONG :: int<br>
076: * TIFF_SRATIONAL :: int[2]<br>
077: * TIFF_FLOAT :: float<br>
078: * TIFF_DOUBLE :: double<br>
079: * </tt>
080: * <p>
081: *
082: * @author <a href="mailto:schaefer@lat-lon.de">Axel Schaefer </A>
083: * @author last edited by: $Author: apoth $
084: * @version 2.0. $Revision: 9342 $, $Date: 2007-12-27 04:32:57 -0800 (Thu, 27 Dec 2007) $
085: * @since
086: */
087: public class GeoTiffReader {
088:
089: private static final ILogger LOG = LoggerFactory
090: .getLogger(GeoTiffReader.class);
091:
092: TIFFImage image = null;
093:
094: TIFFDirectory tifdir = null;
095:
096: HashMap<Integer, int[]> geoKeyDirectoryTag = null;
097:
098: boolean hasGeoKeyDirectoryTag = false;
099:
100: /**
101: * @param file
102: * @throws FileNotFoundException
103: * @throws IOException
104: */
105: public GeoTiffReader(File file) throws FileNotFoundException,
106: IOException, GeoTiffException {
107:
108: TIFFDecodeParam decodeParam = new TIFFDecodeParam();
109: int geodirectory = 0;
110:
111: FileInputStream fis = new FileInputStream(file);
112: FileCacheSeekableStream fcss = new FileCacheSeekableStream(fis);
113: this .image = new TIFFImage(fcss, decodeParam, geodirectory);
114:
115: if (!isGeoTiff(this .image)) {
116: throw new GeoTiffException(
117: "GeoTiffReader: TIFF is no GeoTIFF image!");
118: }
119:
120: this .tifdir = (TIFFDirectory) image
121: .getProperty("tiff_directory");
122:
123: if (this .tifdir.getField(GeoTiffTag.GeoKeyDirectoryTag) != null) {
124: setGeoKeyDirectoryTag();
125: }
126: fcss.close();
127:
128: }
129:
130: // ***********************************************************************
131: // General GeoTIFF tags
132: // ***********************************************************************
133:
134: /**
135: * <p>
136: * GeoKeyDirectoryTag: <br>
137: * Tag = 34735 (87AF.H) <br>
138: * Type = SHORT (2-byte unsigned short) <br>
139: * N = variable, >= 4 <br>
140: * Alias: ProjectionInfoTag, CoordSystemInfoTag <br>
141: * Owner: SPOT Image, Inc.
142: * <p>
143: * This tag may be used to store the GeoKey Directory, which defines and references the
144: * "GeoKeys", as described below.
145: * <p>
146: * The tag is an array of unsigned SHORT values, which are primarily grouped into blocks of 4.
147: * The first 4 values are special, and contain GeoKey directory header information. The header
148: * values consist of the following information, in order:
149: * <p>
150: * <tt>Header={KeyDirectoryVersion, KeyRevision, MinorRevision, NumberOfKeys}</tt>
151: * <p>
152: * and as Keys:
153: * <p>
154: * <tt>KeyEntry = { KeyID, TIFFTagLocation, Count, Value_Offset }</tt>^
155: * <p>
156: * where
157: * <ul>
158: * <li>"KeyID" gives the key-ID value of the Key (identical in function to TIFF tag ID, but
159: * completely independent of TIFF tag-space),
160: * <li>"TIFFTagLocation" indicates which TIFF tag contains the value(s) of the Key: if
161: * TIFFTagLocation is 0, then the value is SHORT, and is contained in the "Value_Offset" entry.
162: * Otherwise, the type (format) of the value is implied by the TIFF-Type of the tag containing
163: * the value.
164: * <li>"Count" indicates the number of values in this key.
165: * <li>"Value_Offset" Value_Offset indicates the index- offset *into* the TagArray indicated by
166: * TIFFTagLocation, if it is nonzero. If TIFFTagLocation=0, then Value_Offset contains the
167: * actual (SHORT) value of the Key, and Count=1 is implied. Note that the offset is not a
168: * byte-offset, but rather an index based on the natural data type of the specified tag array.</li>
169: * </ul>
170: */
171: private void setGeoKeyDirectoryTag() {
172: TIFFField ff = this .tifdir
173: .getField(GeoTiffTag.GeoKeyDirectoryTag);
174:
175: char[] ch = ff.getAsChars();
176:
177: // resulting HashMap, containing the key and the array of values
178: this .geoKeyDirectoryTag = new HashMap<Integer, int[]>(ff
179: .getCount() / 4);
180: // array of values. size is 4-1.
181:
182: int keydirversion, keyrevision, minorrevision, numberofkeys = -99;
183:
184: for (int i = 0; i < ch.length; i = i + 4) {
185: int[] keys = new int[3];
186: keydirversion = ch[i];
187:
188: keyrevision = ch[i + 1];
189: minorrevision = ch[i + 2];
190: numberofkeys = ch[i + 3];
191: keys[0] = keyrevision;
192: keys[1] = minorrevision;
193: keys[2] = numberofkeys;
194:
195: LOG.logDebug("[" + i + "].KEY: " + keydirversion + " \t"
196: + keyrevision + "\t" + minorrevision + "\t"
197: + numberofkeys);
198: this .geoKeyDirectoryTag.put(new Integer(keydirversion),
199: keys);
200: }
201: this .hasGeoKeyDirectoryTag = true;
202: }
203:
204: /**
205: * <p>
206: * GeoDoubleParamsTag: <br>
207: * Tag = 34736 (87BO.H) <br>
208: * Type = DOUBLE (IEEE Double precision) <br>
209: * N = variable <br>
210: * Owner: SPOT Image, Inc.
211: * <p>
212: * This tag is used to store all of the DOUBLE valued GeoKeys, referenced by the
213: * GeoKeyDirectoryTag. The meaning of any value of this double array is determined from the
214: * GeoKeyDirectoryTag reference pointing to it. FLOAT values should first be converted to DOUBLE
215: * and stored here.
216: *
217: * @return
218: */
219: public Object getGeoDoubleParamsTag() {
220: // TIFFField ff = this.tifdir.getField(GeoTiffTag.GeoDoubleParamsTag);
221: // TODO GeoDoubleParamsTag
222: return null;
223: }
224:
225: /**
226: * <p>
227: * GeoAsciiParamsTag: <br>
228: * Tag = 34737 (87B1.H) <br>
229: * Type = ASCII <br>
230: * Owner: SPOT Image, Inc. <br>
231: * N = variable
232: * <p>
233: * This tag is used to store all of the DOUBLE valued GeoKeys, referenced by the
234: * GeoKeyDirectoryTag. The meaning of any value of this double array is determined from the
235: * GeoKeyDirectoryTag reference pointing to it. FLOAT values should first be converted to DOUBLE
236: * and stored here.
237: * <p>
238: * A baseline GeoTIFF-reader must check for and convert the final "|" pipe character of a key
239: * back into a NULL before returning it to the client software.
240: *
241: */
242: public String[] getGeoAsciiParamsTag() {
243:
244: // TODO: getGeoAsciiParamsTag(int count, int value_offset)!!!
245:
246: TIFFField field = this .tifdir
247: .getField(GeoTiffTag.GeoAsciiParamsTag);
248: String gapt = field.getAsString(0);
249:
250: LOG.logDebug(gapt);
251:
252: StringTokenizer st = new StringTokenizer(gapt, "|");
253:
254: LOG.logDebug("countTokens: " + st.countTokens());
255:
256: String[] gapt_fields = new String[st.countTokens()];
257: int i = 0;
258: while (st.hasMoreTokens()) {
259: gapt_fields[i++] = st.nextToken();
260: }
261:
262: for (int j = 0; j < gapt_fields.length; j++) {
263: LOG.logDebug(gapt_fields[j]);
264: }
265:
266: return gapt_fields;
267: }
268:
269: // ***********************************************************************
270: // specific GeoTIFF contents GeoTIFF keys
271: // ***********************************************************************
272: /**
273: *
274: */
275: private int[] getVersionInformation() throws GeoTiffException {
276:
277: int[] content = new int[3];
278: if (this .geoKeyDirectoryTag.containsKey(new Integer(1))) {
279: content = this .geoKeyDirectoryTag.get(new Integer(1));
280: } else {
281: throw new GeoTiffException(
282: "No GeoTIFF Information found at Tag '1'");
283: }
284: return content;
285: }
286:
287: /**
288: *
289: * @return fixed 1
290: * @throws GeoTiffException
291: */
292: public int getGeoKeyDirectoryVersion() throws GeoTiffException {
293: getVersionInformation();
294: return 1;
295: }
296:
297: /**
298: *
299: * @return
300: */
301: public String getKeyRevision() throws GeoTiffException {
302: String key_revision = "";
303: int[] kv = getVersionInformation();
304: key_revision = kv[0] + "." + kv[1];
305: return key_revision;
306: }
307:
308: public int getNumberOfKeysInGeoKeyDirectoryTag()
309: throws GeoTiffException {
310: int[] kv = getVersionInformation();
311: return kv[2];
312: }
313:
314: /**
315: * <p>
316: * Key ID = 1024 <br>
317: * Type: SHORT (code) <br>
318: * <p>
319: * This GeoKey defines the general type of model Coordinate system used, and to which the raster
320: * space will be transformed:unknown, Geocentric (rarely used), Geographic, Projected Coordinate
321: * System, or user-defined. If the coordinate system is a PCS, then only the PCS code need be
322: * specified. If the coordinate system does not fit into one of the standard registered PCS'S,
323: * but it uses one of the standard projections and datums, then its should be documented as a
324: * PCS model with "user-defined" type, requiring the specification of projection parameters,
325: * etc.
326: * <p>
327: * GeoKey requirements for User-Defined Model Type (not advisable): GTCitationGeoKey
328: *
329: * @return (0) unknown, <br>
330: * (1) ModelTypeProjected (Projection Coordinate System), <br>
331: * (2) ModelTypeGeographic (Geographic latitude-longitude System), <br>
332: * (3) ModelTypeGeocentric (Geocentric (X,Y,Z) Coordinate System) (rarely used), <br>
333: * (4?) user-defined
334: *
335: * @throws GeoTiffException
336: */
337: public int getGTModelTypeGeoKey() throws GeoTiffException {
338: int[] content = new int[3];
339: int key = -99;
340:
341: if (this .geoKeyDirectoryTag.containsKey(new Integer(
342: GeoTiffKey.GTModelTypeGeoKey))) {
343: content = this .geoKeyDirectoryTag.get(new Integer(
344: GeoTiffKey.GTModelTypeGeoKey));
345:
346: // TIFFTagLocation
347: if (content[0] == 0) {
348: // return Value_Offset
349: key = content[2];
350: } else {
351: // TODO other TIFFTagLocation that GeoKeyDirectoryTag
352: }
353: } else {
354: throw new GeoTiffException(
355: "No GeoTIFF Information found at Tag '"
356: + GeoTiffKey.GTModelTypeGeoKey + "'");
357: }
358: return key;
359: }
360:
361: /**
362: *
363: * @throws GeoTiffException
364: */
365: public void getCoordinateSystem() throws GeoTiffException {
366:
367: if (getGTModelTypeGeoKey() == 1) {
368: // getModelTypeProjected();
369: } else if (getGTModelTypeGeoKey() == 2) {
370: // getModelTypeGeographic();
371: } else if (getGTModelTypeGeoKey() == 3) {
372: // getModelTypeGeocentric();
373: } else {
374: // user-defined?
375: }
376:
377: }
378:
379: /**
380: *
381: * @throws GeoTiffException
382: */
383: public Envelope getBoundingBox() throws GeoTiffException {
384:
385: TIFFField modelPixelScaleTag = this .tifdir
386: .getField(GeoTiffTag.ModelPixelScaleTag);
387: double resx = modelPixelScaleTag.getAsDouble(0);
388: double resy = modelPixelScaleTag.getAsDouble(1);
389:
390: TIFFField modelTiepointTag = this .tifdir
391: .getField(GeoTiffTag.ModelTiepointTag);
392: double val1 = 0.0;
393: val1 = modelTiepointTag.getAsDouble(0);
394: double val2 = 0.0;
395: val2 = modelTiepointTag.getAsDouble(1);
396: double val4 = 0.0;
397: val4 = modelTiepointTag.getAsDouble(3);
398: double val5 = 0.0;
399: val5 = modelTiepointTag.getAsDouble(4);
400:
401: if ((resx == 0.0 || resy == 0.0)
402: || (val1 == 0.0 && val2 == 0.0 && val4 == 0.0 && val5 == 0.0)) {
403: throw new GeoTiffException(
404: "The image/coverage hasn't a bounding box");
405: // set the geoparams derived by geoTiffTags
406: }
407:
408: // upper/left pixel
409: double xOrigin = val4 - (val1 * resx);
410: double yOrigin = val5 - (val2 * resy);
411:
412: // lower/right pixel
413: double xRight = xOrigin + image.getWidth() * resx;
414: double yBottom = yOrigin - image.getHeight() * resy;
415:
416: double xmin = xOrigin;
417: double ymin = yBottom;
418: double xmax = xRight;
419: double ymax = yOrigin;
420:
421: Envelope envelope = GeometryFactory.createEnvelope(xmin, ymin,
422: xmax, ymax, null);
423:
424: return envelope;
425: }
426:
427: /**
428: *
429: * @return
430: */
431: public String getHumanReadableCoordinateSystem() {
432:
433: String ret = "";
434:
435: if (this .geoKeyDirectoryTag.containsKey(new Integer(
436: GeoTiffKey.PCSCitationGeoKey))) {
437:
438: int[] key_entry = this .geoKeyDirectoryTag.get(new Integer(
439: GeoTiffKey.PCSCitationGeoKey));
440:
441: // check if value of field is located in GeoAsciiParamsTag (34737)
442: if (key_entry[0] == GeoTiffTag.GeoAsciiParamsTag) {
443: TIFFField field = this .tifdir
444: .getField(GeoTiffTag.GeoAsciiParamsTag);
445:
446: int ascii_length = key_entry[1];
447: int ascii_start = key_entry[2];
448:
449: // return the string between the two byte-locations - 1 (the
450: // last '|')
451: ret = "Projected CS: "
452: + field.getAsString(0).substring(ascii_start,
453: ascii_length - 1);
454:
455: } else {
456: ret = "value of field is NOT located in GeoAsciiParamsTag (34737).";
457: }
458: } else {
459: ret = "<empty>";
460: }
461:
462: // GeogCitationGeoKey
463:
464: return ret;
465: }
466:
467: // ***********************************************************************
468: // various GeoTiffReader methods
469: // ***********************************************************************
470:
471: /**
472: * <p>
473: * description: the following TIFFKeys count as indicator if a TIFF-File carries GeoTIFF
474: * information: <br>
475: * ModelPixelScaleTag = 33550 (SoftDesk) <br>
476: * ModelTransformationTag = 34264 (JPL Carto Group) <br>
477: * ModelTiepointTag = 33922 (Intergraph) <br>
478: * GeoKeyDirectoryTag = 34735 (SPOT) <br>
479: * GeoDoubleParamsTag = 34736 (SPOT) <br>
480: * GeoAsciiParamsTag = 34737 (SPOT)
481: */
482: private boolean isGeoTiff(TIFFImage image) {
483: TIFFDirectory directory = (TIFFDirectory) image
484: .getProperty("tiff_directory");
485:
486: if (directory.getField(GeoTiffTag.ModelPixelScaleTag) == null
487: && directory
488: .getField(GeoTiffTag.ModelTransformationTag) == null
489: && directory.getField(GeoTiffTag.ModelTiepointTag) == null
490: && directory.getField(GeoTiffTag.GeoKeyDirectoryTag) == null
491: && directory.getField(GeoTiffTag.GeoDoubleParamsTag) == null
492: && directory.getField(GeoTiffTag.GeoAsciiParamsTag) == null) {
493: return false;
494: }
495: return true;
496: }
497:
498: /**
499: *
500: * @return
501: */
502: public TIFFImage getTIFFImage() throws GeoTiffException {
503: if (this .image != null) {
504: return this .image;
505: }
506: throw new GeoTiffException("no image");
507: }
508:
509: /**
510: *
511: */
512: public String toString() {
513: String ret = "GeoTIFF Information:\n";
514:
515: if (hasGeoKeyDirectoryTag) {
516:
517: // Version Information
518: try {
519: ret += " Version: " + getGeoKeyDirectoryVersion()
520: + "\n";
521: ret += " Key_Revision: " + getKeyRevision() + "\n";
522: ret += " Number Of Keys in GeoKeyDirectoryTag: "
523: + getNumberOfKeysInGeoKeyDirectoryTag() + "\n";
524: ret += " GTModelTypeGeoKey: " + getGTModelTypeGeoKey()
525: + "\n";
526:
527: } catch (GeoTiffException e) {
528: ret += "GeoTiffException occured when requesting GeoTIFF Version Information:\n"
529: + e.getMessage();
530: }
531:
532: ret += "\n";
533: ret += "Coordinate System (human readable): "
534: + getHumanReadableCoordinateSystem() + "\n";
535: ret += "\n";
536: } else {
537: ret += "\nNo GeoKeyDirectoryTag (34735) specified.\n";
538: }
539:
540: if (this .tifdir.getField(GeoTiffTag.ModelPixelScaleTag) != null
541: || this .tifdir.getField(GeoTiffTag.ModelTiepointTag) != null) {
542:
543: ret += "Corner Coordinates:\n";
544: try {
545: Envelope envelope = getBoundingBox();
546: ret += " Upper Left ( " + envelope.getMin().getX()
547: + ", " + envelope.getMax().getY() + " )\n"
548: + " Lower Left ( " + envelope.getMin().getX()
549: + ", " + envelope.getMin().getY() + " )\n"
550: + " Upper Right ( " + envelope.getMax().getX()
551: + ", " + envelope.getMax().getY() + " )\n"
552: + " Lower Right ( " + envelope.getMax().getX()
553: + ", " + envelope.getMin().getY() + " )\n";
554: } catch (GeoTiffException e) {
555: ret += "GeoTiffException occured when calculation the BoundingBox:\n"
556: + e.getMessage();
557: }
558: } else {
559: ret += "\nNo BoundingBox Information in ModelPixelScaleTag (33550) and ModelTiepointTag (33922).\n"
560: + "ModelTransformationTag (34264) not implemented. \n"
561: + "Here is a list of the available tags:\n";
562:
563: for (int i = 0; i < this .tifdir.getFields().length; i++) {
564: ret += " tag: " + this .tifdir.getFields()[i].getTag()
565: + " \t type: "
566: + this .tifdir.getFields()[i].getType()
567: + " \t count: "
568: + this .tifdir.getFields()[i].getCount() + "\n";
569: }
570:
571: }
572:
573: return ret;
574: }
575:
576: }
|