package net.sourceforge.gpstools.exif;
/* gpsdings
* Copyright (C) 2006-2009 Moritz Ringler
* $Id: MediaUtilExifReader.java 441 2010-12-13 20:04:20Z 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 mediautil.gen.*;
import mediautil.image.jpeg.*;
import net.sourceforge.gpstools.gpx.Wpt;
final class MediaUtilExifReader extends AbstractExifReader{
public MediaUtilExifReader(){
// explicit default constructor.
}
@Override
public Date readOriginalTime(File jpeg) throws IOException, ParseException{
int oldDebugLevel = mediautil.gen.Log.debugLevel;
mediautil.gen.Log.debugLevel = mediautil.gen.Log.LEVEL_NONE;
String dateString = null;
InputStream fip = null;
LLJTran llj = null;
try {
fip = new BufferedInputStream(new FileInputStream(jpeg)); // No need to buffer
llj = new LLJTran(fip);
llj.read(LLJTran.READ_INFO, true);
} catch (LLJTranException e) {
throw toIOException(e, jpeg);
} finally {
mediautil.gen.Log.debugLevel = oldDebugLevel;
if(fip != null){
fip.close();
}
}
AbstractImageInfo<?> imageInfo = llj.getImageInfo();
if(! (imageInfo instanceof Exif))
{
throw new IOException("Image " + jpeg + " does not have an Exif header.");
}
Exif exif = (Exif) imageInfo;
dateString = exif.getDataTimeOriginalString();
if(dateString == null)
{
throw new IOException(jpeg + " : OriginalDateTime Exif tag is missing or invalid.");
}
//interpret the dateString as an UTC dateTime value
return parseExifDate(dateString);
}
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{
Dimension result = null;
InputStream fip = null;
int oldDebugLevel = mediautil.gen.Log.debugLevel;
mediautil.gen.Log.debugLevel = mediautil.gen.Log.LEVEL_NONE;
try{
fip = new BufferedInputStream(new FileInputStream(jpeg)); // No need to buffer
LLJTran llj = new LLJTran(fip);
try {
llj.read(LLJTran.READ_HEADER, true);
} catch (LLJTranException e) {
throw toIOException(e, jpeg);
}
int height = llj.getHeight();
int width = llj.getWidth();
result = new Dimension(width, height);
} finally {
mediautil.gen.Log.debugLevel = oldDebugLevel;
if(fip != null){
fip.close();
}
}
return result;
}
@Override
public Wpt readGPSTag(File jpeg) throws IOException, ParseException{
Wpt result = null;
InputStream fip = null;
int oldDebugLevel = mediautil.gen.Log.debugLevel;
mediautil.gen.Log.debugLevel = mediautil.gen.Log.LEVEL_NONE;
try{
fip = new BufferedInputStream(new FileInputStream(jpeg)); // No need to buffer
LLJTran llj = new LLJTran(fip);
try {
llj.read(LLJTran.READ_INFO, true);
} catch (LLJTranException e) {
throw toIOException(e, jpeg);
}
AbstractImageInfo<?> imageInfo = llj.getImageInfo();
if(! (imageInfo instanceof Exif))
{
throw new IOException("Image " + jpeg + "does not have Exif.");
}
Exif exif = (Exif) imageInfo;
IFD mainIfd = exif.getIFDs()[0];
IFD gpsIfd = mainIfd.getIFD(Exif.GPSINFO);
result = wptFromGpsTag(gpsIfd);
} catch (MetadataException ex){
throw toIOException(ex, jpeg);
} finally {
mediautil.gen.Log.debugLevel = oldDebugLevel;
if(fip != null){
fip.close();
}
}
if(result == null){
throw new ParseException("No GPS information found in " + jpeg.getPath(),0);
}
return result;
}
private static BigDecimal asBigDecimalDegrees(Rational[] 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(Rational[] latlon, double maxFactor){
double factor = maxFactor;
double result = 0;
for(Rational r : latlon){
result += r.floatValue() * factor;
factor /= 60.;
}
return result;
}
private static Rational[] getRationalTripleValue(Entry e, Rational[] r){
Rational[] result = (r == null)? new Rational[3] : r;
for(int i=0; i<3; i++){
result[i] = (Rational) e.getValue(i);
}
return result;
}
private static Wpt wptFromGpsTag(IFD gps) throws MetadataException, ParseException{
if(gps == null){
return null;
}
Wpt result = new Wpt();
Rational[] r3 = new Rational[3];
/* map datum */
Entry e = gps.getEntry(Exif.GPSMapDatum, 0);
if(e == null){
warn("GPS datum missing. Assuming WGS84.");
} else {
Object value = e.getValue(0);
String svalue = String.valueOf(value);
if(value == null || 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 " + value.toString() +
". Currently only coordinates in the WGS84 "+
"map datum can be handled.");
}
}
/* latitude */
e = gps.getEntry(Exif.GPSLatitude, 0);
if(e == null){
throw new MetadataException("GPS latitude missing.");
}
r3 = getRationalTripleValue(e, r3);
e = gps.getEntry(Exif.GPSLatitudeRef, 0);
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.valueOf(e.getValue(0))));
}
/* longitude */
e = gps.getEntry(Exif.GPSLongitude, 0);
if(e == null){
throw new MetadataException("GPS longtude missing.");
}
r3 = getRationalTripleValue(e, r3);
e = gps.getEntry(Exif.GPSLongitudeRef, 0);
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.valueOf(e.getValue(0))));
}
/* elevation */
if(gps.getEntry(Exif.GPSAltitude, 0) != null){
double alti = 1;
if (gps.getEntry(Exif.GPSAltitudeRef, 0) == null){
warn("GPSAltitudeRef missing. Assuming above sea level.");
} else {
e = gps.getEntry(Exif.GPSAltitudeRef, 0);
alti *= ((Integer) e.getValue(0)).intValue() * (-2) + 1; // 0 -> 1 && 1 -> -1
if (Math.abs(alti) != 1 ){
alti = 1;
System.err.printf("WARNING: illegal GPSAltitudeRef %d. Assuming above sea level.\n",
((Integer) e.getValue(0)).intValue());
}
}
e = gps.getEntry(Exif.GPSAltitude, 0);
alti *= ((Rational) e.getValue(0)).floatValue();
result.setEle(new BigDecimal(alti));
}
/* dateTime */
if(gps.getEntry(Exif.GPSTimeStamp, 0) != null &&
gps.getEntry(Exif.GPSDateStamp, 0) != null){
e = gps.getEntry(Exif.GPSTimeStamp, 0);
r3 = getRationalTripleValue(e, r3);
long millis = Math.round(asDouble(r3, 3.6e6));
e = gps.getEntry(Exif.GPSDateStamp, 0);
Date d = null;
if(e.getType() == Exif.ASCII){
try{
d = AbstractExecExifWriter.parseExifDate(String.valueOf(e.getValue(0)));
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);
}
}