package camera;
import java.text.FieldPosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.sanselan.ImageWriteException;
import org.apache.sanselan.common.BinaryConstants;
import org.apache.sanselan.formats.tiff.constants.ExifTagConstants;
import org.apache.sanselan.formats.tiff.constants.TagInfo;
import org.apache.sanselan.formats.tiff.constants.TiffDirectoryConstants.ExifDirectoryType;
import org.apache.sanselan.formats.tiff.write.TiffOutputDirectory;
import org.apache.sanselan.formats.tiff.write.TiffOutputField;
import org.apache.sanselan.formats.tiff.write.TiffOutputSet;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.LogService;
import com.buglabs.application.AbstractServiceTracker;
import com.buglabs.bug.module.gps.pub.IPositionProvider;
import com.buglabs.bug.module.gps.pub.LatLon;
public class ExifDataHandler {
private final static String MANUFACTURER = "Bug Labs";
private final static String MODEL = "BUGcam2MP";
// TODO: Image.getWidth() would be nice if it didn't require an observer:
private final static int IMAGE_WIDTH = 1600;
private final static int IMAGE_HEIGHT = 1200;
private final static int BYTE_ORDER = BinaryConstants.BYTE_ORDER_INTEL;
private final static SimpleDateFormat timestampFormat= new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
private final AbstractServiceTracker serviceTracker;
// The GPS ones aren't all predefined by Sanselan, so create them from the EXIF spec.
// version id is 2.2.0.0 but we're doing Intel (aka little-endian) so do it backwards:
private final byte [] GPS_VERSION_ID = new byte[] {(byte)0, (byte)0, (byte)2, (byte)2};
private final TagInfo gpsVerTagInfo;
private final TagInfo gpsLatRefTagInfo;
private final TagInfo gpsLatTagInfo;
private final TagInfo gpsLonRefTagInfo;
private final TagInfo gpsLonTagInfo;
private final LogService log;
public ExifDataHandler(LogService log, AbstractServiceTracker serviceTracker) {
this.serviceTracker = serviceTracker;
this.log = log;
final ExifDirectoryType gpsDirectory = new ExifDirectoryType.Special(-3, "GPS IFD");
gpsVerTagInfo = new TagInfo("GPSVersionID", 0, TiffOutputField.FIELD_TYPE_BYTE, 4,
gpsDirectory);
gpsLatRefTagInfo = new TagInfo("GPSLatitudeRef", 1, TiffOutputField.FIELD_TYPE_ASCII, 1,
gpsDirectory);
gpsLatTagInfo = new TagInfo("GPSLatitude", 2, TiffOutputField.FIELD_TYPE_RATIONAL, 3,
gpsDirectory);
gpsLonRefTagInfo = new TagInfo("GPSLongitudeRef", 3, TiffOutputField.FIELD_TYPE_ASCII, 1,
gpsDirectory);
gpsLonTagInfo = new TagInfo("GPSLongitude", 4, TiffOutputField.FIELD_TYPE_RATIONAL, 3,
gpsDirectory);
}
public TiffOutputSet createExifData(final Date timestamp) {
final TiffOutputSet exifData = new TiffOutputSet();
try {
TiffOutputDirectory rootDirectory = exifData.getOrCreateRootDirectory();
TiffOutputDirectory exifDirectory = exifData.getOrCreateExifDirectory();
// TiffOuputField.create((TagInfo tagInfo, int byteOrder,
// String value) expects it to be FIELD_TYPE_ASCII_DESCRIPTION
// so it throws an exception; so, do it the hard way...
final TiffOutputField manufacturer = new TiffOutputField(
ExifTagConstants.EXIF_TAG_MAKE,
TiffOutputField.FIELD_TYPE_ASCII,
MANUFACTURER.length(),
MANUFACTURER.getBytes());
rootDirectory.add(manufacturer);
final TiffOutputField model = new TiffOutputField(
ExifTagConstants.EXIF_TAG_MODEL,
TiffOutputField.FIELD_TYPE_ASCII,
MODEL.length(),
MODEL.getBytes());
rootDirectory.add(model);
TiffOutputField imageWidth = TiffOutputField.create(
ExifTagConstants.EXIF_TAG_IMAGE_WIDTH_IFD0,
BYTE_ORDER,
new Integer(IMAGE_WIDTH));
rootDirectory.add(imageWidth);
TiffOutputField imageHeight = TiffOutputField.create(
ExifTagConstants.EXIF_TAG_IMAGE_HEIGHT_IFD0,
BYTE_ORDER,
new Integer(IMAGE_HEIGHT));
rootDirectory.add(imageHeight);
final StringBuffer sb = new StringBuffer();
timestampFormat.format(timestamp, sb, new FieldPosition(0));
final String dateTime = sb.toString();
TiffOutputField dateTimeModified = new TiffOutputField(
ExifTagConstants.EXIF_TAG_MODIFY_DATE,
TiffOutputField.FIELD_TYPE_ASCII,
dateTime.length(),
dateTime.getBytes());
rootDirectory.add(dateTimeModified);
TiffOutputField dateTimeOriginal = new TiffOutputField(
ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL,
TiffOutputField.FIELD_TYPE_ASCII,
dateTime.length(),
dateTime.getBytes());
exifDirectory.add(dateTimeOriginal);
TiffOutputField dateTimeDigitized = new TiffOutputField(
// This is DateTimeDigitized in the Exif 2.2 spec
ExifTagConstants.EXIF_TAG_CREATE_DATE,
TiffOutputField.FIELD_TYPE_ASCII,
dateTime.length(),
dateTime.getBytes());
exifDirectory.add(dateTimeDigitized);
addGpsInfo(exifData);
} catch (ImageWriteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return exifData;
}
/**
* Get IPositionProvider if available
*
* @return
*/
private IPositionProvider getPositionProvider() {
ServiceReference sr = serviceTracker.getBundleContext().getServiceReference(
com.buglabs.bug.module.gps.pub.IPositionProvider.class.getName());
if (sr == null) return null;
return (IPositionProvider) serviceTracker.getBundleContext().getService(sr);
}
private void addGpsInfo(TiffOutputSet exifData) {
// GPS Optional
IPositionProvider iPositionProvider = getPositionProvider();
if (iPositionProvider == null) {
log.log(LogService.LOG_WARNING, "No position provider so can't geo tag");
}
else {
try {
final TiffOutputDirectory gpsDirectory = exifData.getOrCreateGPSDirectory();
final LatLon latLon = iPositionProvider.getLatitudeLongitude();
if (latLon == null) {
log.log(LogService.LOG_WARNING, "No LatLon returned, so can't geo tag");
return;
}
// convert lat lon for exif
double lat = latLon.latitude;
String latRef = "N";
if (lat < 0) {
latRef = "S";
lat *= -1;
}
double lon = latLon.longitude;
String lonRef = "E";
if (lon < 0) {
lonRef = "W";
lon *= -1;
}
log.log(LogService.LOG_INFO, "geo tagging: " + lat + latRef + "," + lon + lonRef);
final TiffOutputField verField = new TiffOutputField(
gpsVerTagInfo,
TiffOutputField.FIELD_TYPE_BYTE,
GPS_VERSION_ID.length,
GPS_VERSION_ID);
gpsDirectory.add(verField);
final TiffOutputField latRefField = new TiffOutputField(
gpsLatRefTagInfo,
TiffOutputField.FIELD_TYPE_ASCII,
latRef.length(),
latRef.getBytes());
gpsDirectory.add(latRefField);
final TiffOutputField latField = TiffOutputField.create(
gpsLatTagInfo,
BYTE_ORDER,
decimalToDegMins(lat));
gpsDirectory.add(latField);
final TiffOutputField lonRefField = new TiffOutputField(
gpsLonRefTagInfo,
TiffOutputField.FIELD_TYPE_ASCII,
lonRef.length(),
lonRef.getBytes());
gpsDirectory.add(lonRefField);
final TiffOutputField lonField = TiffOutputField.create(
gpsLonTagInfo,
BYTE_ORDER,
decimalToDegMins(lon));
gpsDirectory.add(lonField);
} catch (NumberFormatException e) { // get this from IPositionProvider sometimes
e.printStackTrace();
} catch (ImageWriteException e) {
e.printStackTrace();
} catch (RuntimeException e) {
e.printStackTrace();
}
}
}
private static Double [] decimalToDegMins(double latOrLon) {
final double floor = Math.floor(latOrLon);
return new Double [] {
new Double(floor), // degrees
new Double((latOrLon - floor) * 60), // minutes
new Double(0.0) }; // seconds
}
}