package net.sourceforge.gpstools.exif;
/* gpsdings
* Copyright (C) 2006-2009 Moritz Ringler
* $Id: MediaUtilExifReader.java 382 2009-01-12 17:59:56Z ringler $
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import java.awt.Dimension;
import java.io.*;
import java.text.ParseException;
import java.util.Date;
import java.math.BigDecimal;
import net.sourceforge.gpstools.gpx.Wpt;
import org.apache.sanselan.ImageReadException;
import org.apache.sanselan.ImageWriteException;
import org.apache.sanselan.Sanselan;
import org.apache.sanselan.formats.jpeg.JpegImageMetadata;
import org.apache.sanselan.formats.tiff.TiffImageMetadata;
import org.apache.sanselan.formats.tiff.TiffField;
import org.apache.sanselan.formats.tiff.constants.TiffConstants;
import org.apache.sanselan.formats.tiff.TiffDirectory;
final class SanselanExifReader extends AbstractExifReader{
static TiffImageMetadata getExif(File jpegImageFile)
throws IOException, ImageReadException, ImageWriteException{
TiffImageMetadata exif = null;
JpegImageMetadata jpegMetadata = (JpegImageMetadata) Sanselan.getMetadata(jpegImageFile);
if (null != jpegMetadata) {
// note that exif might be null if no Exif metadata is found.
exif = jpegMetadata.getExif();
}
return exif;
}
public SanselanExifReader(){
// explicit default constructor.
}
@Override
public Date readOriginalTime(File jpeg) throws IOException, ParseException{
try{
final TiffImageMetadata exif = SanselanExifReader.getExif(jpeg);
String dateString = (String) exif.findField(TiffConstants.EXIF_TAG_DATE_TIME_ORIGINAL).getValue();
//interpret the dateString as an UTC dateTime value
return parseExifDate(dateString);
} catch (ImageReadException irx){
throw toIOException(irx, jpeg);
} catch (ImageWriteException iwx){
throw toIOException(iwx, jpeg);
}
}
private static IOException toIOException(Throwable t, File f){
IOException ex = new IOException("Error reading exif tag from file " + f.getPath());
ex.initCause(t);
return ex;
}
@Override
public Dimension readJPEGDimension(File jpeg) throws IOException{
try{
return Sanselan.getImageSize(jpeg);
} catch (Exception ex){
throw toIOException(ex, jpeg);
}
}
@Override
public Wpt readGPSTag(File jpeg) throws IOException, ParseException{
Wpt result = null;
try{
final TiffDirectory gpsIfd =
SanselanExifReader.getExif(jpeg).findDirectory(
TiffConstants.DIRECTORY_TYPE_GPS);
result = wptFromGpsTag(gpsIfd);
} catch (MetadataException ex){
throw toIOException(ex, jpeg);
} catch (ImageReadException irx){
throw toIOException(irx, jpeg);
} catch (ImageWriteException iwx){
throw toIOException(iwx, jpeg);
}
if(result == null){
throw new ParseException("No GPS information found in " + jpeg.getPath(),0);
}
return result;
}
private static Wpt wptFromGpsTag(TiffDirectory gpsIfd)
throws MetadataException, ParseException,
ImageReadException, ImageWriteException{
if(gpsIfd == null){
return null;
}
Wpt result = new Wpt();
Number[] r3;
TiffField e;
/* map datum */
e = gpsIfd.findField(TiffConstants.GPS_TAG_GPS_MAP_DATUM);
if(e == null){
warn("GPS datum missing. Assuming WGS84.");
} else {
String svalue = (String) e.getValue();
if(svalue.equals("")){
System.err.println("Warning: GPS datum missing. Assuming WGS84.");
} else if(!svalue.toUpperCase().replaceAll("[^0-9A-Z]", "").equals("WGS84")){
throw new MetadataException("GPS datum is " + svalue +
". Currently only coordinates in the WGS84 "+
"map datum can be handled.");
}
}
/* latitude */
e = gpsIfd.findField(TiffConstants.GPS_TAG_GPS_LATITUDE);
if(e == null){
throw new MetadataException("GPS latitude missing.");
}
r3 = (Number[]) e.getValue();
e = gpsIfd.findField(TiffConstants.GPS_TAG_GPS_LATITUDE_REF);
if(e == null){
String sysprop = System.getProperty("DefaultGPSLatitudeRef");
char s0 = (sysprop == null || sysprop.length() == 0)
? 'X'
: sysprop.toUpperCase().charAt(0);
if(s0 != 'N' && s0 != 'S'){
throw new MetadataException("GPS latitude found but GPS latitude reference missing or invalid.\n"+
" To use the GPS latitude information set the DefaultGPSLatitudeRef system property to N or S, e. g.\n"+
" java -DDefaultGPSLatitudeRef=N -jar gpsdings.jar ...");
}
result.setLat(asBigDecimalDegrees(r3, String.valueOf(s0)));
} else {
result.setLat(asBigDecimalDegrees(r3, (String) e.getValue()));
}
/* longitude */
e = gpsIfd.findField(TiffConstants.GPS_TAG_GPS_LONGITUDE);
if(e == null){
throw new MetadataException("GPS longitude missing.");
}
r3 = (Number[]) e.getValue();
e = gpsIfd.findField(TiffConstants.GPS_TAG_GPS_LONGITUDE_REF);
if(e == null){
String sysprop = System.getProperty("DefaultGPSLongitudeRef");
final char s0 = (sysprop == null || sysprop.length() == 0)
? 'X'
: sysprop.toUpperCase().charAt(0);
if(s0 != 'E' && s0 != 'W'){
throw new MetadataException("GPS longitude found but GPS longitude reference missing or invalid.\n"+
" To use the GPS longitude information set the DefaultGPSLongitudeRef system property to W or E, e. g.\n"+
" java -DDefaultGPSLongitudeRef=E -jar gpsdings.jar ...");
}
result.setLon(asBigDecimalDegrees(r3, String.valueOf(s0)));
} else {
result.setLon(asBigDecimalDegrees(r3, (String) e.getValue()));
}
/* elevation */
e = gpsIfd.findField(TiffConstants.GPS_TAG_GPS_ALTITUDE);
if(e != null){
double alti = ((Number) e.getValue()).doubleValue();
e = gpsIfd.findField(TiffConstants.GPS_TAG_GPS_ALTITUDE_REF);
if (e == null){
warn("GPSAltitudeRef missing. Assuming above sea level.");
} else {
int altref = ((Number) e.getValue()).intValue() * (-2) + 1; // 0 -> 1 && 1 -> -1
if (Math.abs(altref) != 1 ){
System.err.printf("WARNING: illegal GPSAltitudeRef %d. Assuming above sea level.\n",
altref);
altref = 1;
}
alti *= altref;
}
result.setEle(new BigDecimal(alti));
}
/* dateTime */
e = gpsIfd.findField(TiffConstants.GPS_TAG_GPS_TIME_STAMP);
if(e != null){
r3 = (Number[]) e.getValue();
long millis = Math.round(asDouble(r3, 3.6e6));
e = gpsIfd.findField(TiffConstants.GPS_TAG_GPS_DATE_STAMP);
if (e != null){
Date d = null;
try{
d = AbstractExecExifWriter.parseExifDate((String) e.getValue());
d = new Date(d.getTime() + millis);
result.setTime(d);
} catch (ParseException ex){
System.err.println(ex);
}
if(d == null) {
warn("Exif GPSDateStamp is not in ASCII YYYY:MM:DD format.\n" +
"Ignoring Exif GPSDateStamp and Exif GPSTimeStamp.");
}
}
}
return result;
}
private static void warn(String str){
System.err.println("WARNING: " + str);
}
private static BigDecimal asBigDecimalDegrees(Number[] latlon,
String ref){
double result = asDouble(latlon, 1.0);
if("W".equals(ref) || "S".equals(ref)){
result = -result;
}
return new BigDecimal(result);
}
private static double asDouble(Number[] latlon, double maxFactor){
double factor = maxFactor;
double result = 0;
for(Number r : latlon){
result += r.floatValue() * factor;
factor /= 60.;
}
return result;
}
}