/*
This file is part of RouteConverter.
RouteConverter 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 2 of the License, or
(at your option) any later version.
RouteConverter 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 RouteConverter; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Copyright (C) 2007 Christian Pesch. All Rights Reserved.
*/
package slash.navigation.photo;
import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.ImageWriteException;
import org.apache.commons.imaging.Imaging;
import org.apache.commons.imaging.common.ImageMetadata;
import org.apache.commons.imaging.common.RationalNumber;
import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
import org.apache.commons.imaging.formats.jpeg.exif.ExifRewriter;
import org.apache.commons.imaging.formats.tiff.TiffDirectory;
import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
import org.apache.commons.imaging.formats.tiff.TiffImageMetadata.Directory;
import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoRational;
import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory;
import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
import slash.common.type.CompactCalendar;
import slash.navigation.base.ParserContext;
import slash.navigation.base.RouteCharacteristics;
import slash.navigation.base.SimpleFormat;
import slash.navigation.base.Wgs84Position;
import slash.navigation.base.Wgs84Route;
import slash.navigation.common.NavigationPosition;
import java.awt.*;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.logging.Logger;
import static java.io.File.createTempFile;
import static java.lang.Math.abs;
import static java.util.Calendar.DAY_OF_MONTH;
import static java.util.Calendar.HOUR;
import static java.util.Calendar.HOUR_OF_DAY;
import static java.util.Calendar.MINUTE;
import static java.util.Calendar.MONTH;
import static java.util.Calendar.SECOND;
import static java.util.Calendar.YEAR;
import static java.util.Collections.singletonList;
import static org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants.EXIF_TAG_DATE_TIME_DIGITIZED;
import static org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL;
import static org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants.EXIF_TAG_EXIF_IMAGE_LENGTH;
import static org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants.EXIF_TAG_EXIF_IMAGE_WIDTH;
import static org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants.EXIF_TAG_EXPOSURE_TIME;
import static org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants.EXIF_TAG_FLASH;
import static org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants.EXIF_TAG_FNUMBER;
import static org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants.EXIF_TAG_FOCAL_LENGTH;
import static org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants.EXIF_TAG_ISO;
import static org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants.EXIF_TAG_USER_COMMENT;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_ALTITUDE;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_ALTITUDE_REF;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_DATE_STAMP;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_DOP;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_LATITUDE;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_LATITUDE_REF;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_LONGITUDE;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_LONGITUDE_REF;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_MEASURE_MODE;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_MEASURE_MODE_VALUE_2_DIMENSIONAL_MEASUREMENT;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_MEASURE_MODE_VALUE_3_DIMENSIONAL_MEASUREMENT;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_SATELLITES;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_SPEED;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_SPEED_REF;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_SPEED_REF_VALUE_KMPH;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_SPEED_REF_VALUE_KNOTS;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_SPEED_REF_VALUE_MPH;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_TIME_STAMP;
import static org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants.GPS_TAG_GPS_VERSION_ID;
import static org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryConstants.DIRECTORY_TYPE_EXIF;
import static org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryConstants.DIRECTORY_TYPE_GPS;
import static org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryConstants.DIRECTORY_TYPE_ROOT;
import static org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants.TIFF_TAG_DATE_TIME;
import static org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants.TIFF_TAG_MAKE;
import static org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants.TIFF_TAG_MODEL;
import static slash.common.helpers.ExceptionHelper.getLocalizedMessage;
import static slash.common.io.Directories.getTemporaryDirectory;
import static slash.common.io.InputOutput.copyAndClose;
import static slash.common.io.Transfer.parseInteger;
import static slash.common.io.Transfer.trim;
import static slash.common.type.CompactCalendar.fromCalendar;
import static slash.common.type.CompactCalendar.parseDate;
import static slash.common.type.ISO8601.formatDate;
import static slash.navigation.base.RouteCharacteristics.Waypoints;
import static slash.navigation.base.WaypointType.Photo;
import static slash.navigation.common.UnitConversion.nauticMilesToKiloMeter;
import static slash.navigation.common.UnitConversion.statuteMilesToKiloMeter;
import static slash.navigation.photo.TagState.NotTaggable;
import static slash.navigation.photo.TagState.Tagged;
/**
* Reads a route with a position from Photo (.jpg) files with embedded EXIF and GPS metadata.
*
* @author Christian Pesch
*/
public class PhotoFormat extends SimpleFormat<Wgs84Route> {
private static final Logger log = Logger.getLogger(PhotoFormat.class.getName());
private static final String DATE_FORMAT = "yyyy:MM:dd";
private static final String DATE_TIME_FORMAT = "yyyy:MM:dd HH:mm:ss";
private static final DecimalFormat XX_FORMAT = new DecimalFormat("00");
private static final DecimalFormat XXXX_FORMAT = new DecimalFormat("0000");
private static final int READ_BUFFER_SIZE = 64 * 1024;
public String getName() {
return "Photo (" + getExtension() + ")";
}
public String getExtension() {
return ".jpg";
}
public int getMaximumPositionCount() {
return 1;
}
public boolean isSupportsMultipleRoutes() {
return false;
}
public boolean isWritingRouteCharacteristics() {
return false;
}
@SuppressWarnings("unchecked")
public <P extends NavigationPosition> Wgs84Route createRoute(RouteCharacteristics characteristics, String name, List<P> positions) {
return new Wgs84Route(this, characteristics, (List<Wgs84Position>) positions);
}
public void read(BufferedReader reader, String encoding, ParserContext<Wgs84Route> context) throws IOException {
// this format parses the InputStream directly but wants to derive from SimpleFormat to use Wgs84Route
throw new UnsupportedOperationException();
}
public void write(Wgs84Route route, PrintWriter writer, int startIndex, int endIndex) {
// this format parses the InputStream directly but wants to derive from SimpleFormat to use Wgs84Route
throw new UnsupportedOperationException();
}
public void read(InputStream source, ParserContext<Wgs84Route> context) throws Exception {
BufferedInputStream bufferedSource = new BufferedInputStream(source, READ_BUFFER_SIZE);
bufferedSource.mark(READ_BUFFER_SIZE);
Dimension size = Imaging.getImageSize(bufferedSource, null);
if (size == null)
return;
PhotoPosition position = new PhotoPosition(NotTaggable, context.getStartDate(), "No EXIF data", null);
bufferedSource.reset();
ImageMetadata metadata = Imaging.getMetadata(bufferedSource, null);
TiffImageMetadata tiffImageMetadata = extractTiffImageMetadata(metadata);
if (tiffImageMetadata != null) {
@SuppressWarnings("unchecked")
List<Directory> directories = (List<Directory>) tiffImageMetadata.getDirectories();
for (Directory directory : directories)
log.info("Reading EXIF directory " + directory);
extendPosition(position, tiffImageMetadata, context.getStartDate());
}
bufferedSource.reset();
File image = context.getFile();
if (image == null)
image = extractToTempFile(bufferedSource);
position.setOrigin(image);
position.setWaypointType(Photo);
context.appendRoute(new Wgs84Route(this, Waypoints, new ArrayList<Wgs84Position>(singletonList(position))));
}
private TiffImageMetadata extractTiffImageMetadata(ImageMetadata metadata) {
TiffImageMetadata result = null;
if (metadata instanceof JpegImageMetadata)
result = ((JpegImageMetadata) metadata).getExif();
else if (metadata instanceof TiffImageMetadata)
result = (TiffImageMetadata) metadata;
return result;
}
private File extractToTempFile(InputStream inputStream) throws IOException {
File temp = createTempFile("photo", ".jpg", getTemporaryDirectory());
temp.deleteOnExit();
copyAndClose(inputStream, new FileOutputStream(temp));
return temp;
}
private String parseRoot(TiffImageMetadata metadata, TagInfo tag) throws ImageReadException {
TiffDirectory rootDirectory = metadata.findDirectory(DIRECTORY_TYPE_ROOT);
return rootDirectory != null ? trim((String) rootDirectory.getFieldValue(tag)) : null;
}
private String parseMake(TiffImageMetadata metadata) throws ImageReadException {
return parseRoot(metadata, TIFF_TAG_MAKE);
}
private String parseModel(TiffImageMetadata metadata) throws ImageReadException {
return parseRoot(metadata, TIFF_TAG_MODEL);
}
private CompactCalendar parseDateTime(TiffImageMetadata metadata) throws ImageReadException {
return parseDate(parseRoot(metadata, TIFF_TAG_DATE_TIME), DATE_TIME_FORMAT);
}
private String parseExif(TiffImageMetadata metadata, TagInfo tag) throws ImageReadException {
TiffDirectory exifDirectory = metadata.findDirectory(DIRECTORY_TYPE_EXIF);
return exifDirectory != null ? trim((String) exifDirectory.getFieldValue(tag)) : null;
}
private Integer parseExifInteger(TiffImageMetadata metadata, TagInfo tag) throws ImageReadException {
TiffDirectory exifDirectory = metadata.findDirectory(DIRECTORY_TYPE_EXIF);
if (exifDirectory == null)
return null;
Object fieldValue = exifDirectory.getFieldValue(tag);
return fieldValue != null ? new Integer(fieldValue.toString()) : null;
}
private RationalNumber parseExifRationalNumber(TiffImageMetadata metadata, TagInfo tag) throws ImageReadException {
TiffDirectory exifDirectory = metadata.findDirectory(DIRECTORY_TYPE_EXIF);
if (exifDirectory == null)
return null;
return (RationalNumber) exifDirectory.getFieldValue(tag);
}
private String parseUserComment(TiffImageMetadata metadata) throws ImageReadException {
TiffDirectory exifDirectory = metadata.findDirectory(DIRECTORY_TYPE_EXIF);
if (exifDirectory != null) {
String userComment = parseExif(metadata, EXIF_TAG_USER_COMMENT);
if (userComment != null)
return userComment;
}
TiffDirectory rootDirectory = metadata.findDirectory(DIRECTORY_TYPE_ROOT);
if (rootDirectory != null) {
String make = parseMake(metadata);
String model = parseModel(metadata);
CompactCalendar dateTime = parseDateTime(metadata);
return trim((make != null ? make : "") + " " + (model != null ? model : "") + " Photo" + (dateTime != null ? " from " + formatDate(dateTime) : ""));
}
return "GPS";
}
private CompactCalendar parseExifTime(TiffImageMetadata metadata, CompactCalendar startDate) throws ImageReadException {
String dateString = null;
TiffDirectory exifDirectory = metadata.findDirectory(DIRECTORY_TYPE_EXIF);
if (exifDirectory != null) {
dateString = parseExif(metadata, EXIF_TAG_DATE_TIME_ORIGINAL);
if (dateString == null)
dateString = parseExif(metadata, EXIF_TAG_DATE_TIME_DIGITIZED);
}
TiffDirectory rootDirectory = metadata.findDirectory(DIRECTORY_TYPE_ROOT);
if (rootDirectory != null && dateString == null)
dateString = (String) rootDirectory.getFieldValue(TIFF_TAG_DATE_TIME);
return dateString != null ? parseDate(dateString, DATE_TIME_FORMAT) : startDate;
}
private RationalNumber getFieldValue(TiffDirectory directory, TagInfoRational tagInfoRational) {
try {
return directory.getFieldValue(tagInfoRational);
} catch (ImageReadException e) {
log.fine("Could not parse " + tagInfoRational + ": " + getLocalizedMessage(e));
return null;
}
}
private Double parseAltitude(TiffDirectory directory) throws ImageReadException {
RationalNumber altitudeNumber = getFieldValue(directory, GPS_TAG_GPS_ALTITUDE);
if (altitudeNumber == null)
return null;
double altitude = altitudeNumber.doubleValue();
Byte altitudeRef = directory.getFieldValue(GPS_TAG_GPS_ALTITUDE_REF);
return altitudeRef == 1 ? -altitude : altitude;
}
private Double parseSpeed(TiffDirectory directory) throws ImageReadException {
RationalNumber speedNumber = getFieldValue(directory, GPS_TAG_GPS_SPEED);
if (speedNumber == null)
return null;
double speed = speedNumber.doubleValue();
String speedRef = (String) directory.getFieldValue(GPS_TAG_GPS_SPEED_REF);
if (speedRef != null) {
if (GPS_TAG_GPS_SPEED_REF_VALUE_MPH.equals(speedRef))
speed = statuteMilesToKiloMeter(speed);
else if (GPS_TAG_GPS_SPEED_REF_VALUE_KNOTS.equals(speedRef))
speed = nauticMilesToKiloMeter(speed);
}
return speed;
}
private CompactCalendar parseGPSTime(TiffDirectory directory, CompactCalendar startDate) throws ImageReadException {
String dateString = (String) directory.getFieldValue(GPS_TAG_GPS_DATE_STAMP);
CompactCalendar date = dateString != null ? parseDate(dateString, DATE_FORMAT) : startDate;
RationalNumber[] timeStamp = (RationalNumber[]) directory.getFieldValue(GPS_TAG_GPS_TIME_STAMP);
if (timeStamp != null) {
Calendar calendar = date.getCalendar();
calendar.set(HOUR, timeStamp[0].intValue());
calendar.set(MINUTE, timeStamp[1].intValue());
calendar.set(SECOND, timeStamp[2].intValue());
date = fromCalendar(calendar);
}
return date;
}
private Double parseDirection(TiffDirectory directory) throws ImageReadException {
RationalNumber direction = getFieldValue(directory, GPS_TAG_GPS_IMG_DIRECTION);
return direction != null ? direction.doubleValue() : null;
}
private Integer parseSatellites(TiffDirectory directory) throws ImageReadException {
String satellites = (String) directory.getFieldValue(GPS_TAG_GPS_SATELLITES);
return satellites != null ? parseInteger(satellites) : null;
}
private boolean is2DimensionalMeasurement(TiffDirectory directory) throws ImageReadException {
String measurementMode = (String) directory.getFieldValue(GPS_TAG_GPS_MEASURE_MODE);
return measurementMode != null && measurementMode.equals(Integer.toString(GPS_TAG_GPS_MEASURE_MODE_VALUE_2_DIMENSIONAL_MEASUREMENT));
}
private Double parseDOP(TiffDirectory directory) throws ImageReadException {
RationalNumber dop = getFieldValue(directory, GPS_TAG_GPS_DOP);
return dop != null ? dop.doubleValue() : null;
}
private void extendPosition(PhotoPosition position, TiffImageMetadata metadata, CompactCalendar startDate) throws ImageReadException {
TiffImageMetadata.GPSInfo gpsInfo = metadata.getGPS();
if (gpsInfo != null) {
position.setLongitude(gpsInfo.getLongitudeAsDegreesEast());
position.setLatitude(gpsInfo.getLatitudeAsDegreesNorth());
position.setTagState(Tagged);
}
CompactCalendar time = parseExifTime(metadata, startDate);
TiffDirectory gpsDirectory = metadata.findDirectory(DIRECTORY_TYPE_GPS);
if (gpsDirectory != null) {
position.setElevation(parseAltitude(gpsDirectory));
position.setSpeed(parseSpeed(gpsDirectory));
time = parseGPSTime(gpsDirectory, time);
position.setHeading(parseDirection(gpsDirectory));
position.setSatellites(parseSatellites(gpsDirectory));
Double dop = parseDOP(gpsDirectory);
if (is2DimensionalMeasurement(gpsDirectory))
position.setHdop(dop);
else
position.setPdop(dop);
}
position.setTime(time);
position.setDescription(parseUserComment(metadata));
position.setMake(parseMake(metadata));
position.setModel(parseModel(metadata));
position.setWidth(parseExifInteger(metadata, EXIF_TAG_EXIF_IMAGE_WIDTH));
position.setHeight(parseExifInteger(metadata, EXIF_TAG_EXIF_IMAGE_LENGTH));
position.setfNumber(parseExifRationalNumber(metadata, EXIF_TAG_FNUMBER));
position.setExposure(parseExifRationalNumber(metadata, EXIF_TAG_EXPOSURE_TIME));
position.setFlash(parseExifInteger(metadata, EXIF_TAG_FLASH));
position.setFocal(parseExifRationalNumber(metadata, EXIF_TAG_FOCAL_LENGTH));
position.setPhotographicSensitivity(parseExifInteger(metadata, EXIF_TAG_ISO));
}
public void write(Wgs84Route route, OutputStream target, int startIndex, int endIndex) throws IOException {
List<Wgs84Position> positions = route.getPositions();
for (int i = startIndex; i < endIndex; i++) {
PhotoPosition position = (PhotoPosition) positions.get(i);
File source = position.getOrigin(File.class);
if (source != null)
write(position, source, target);
}
}
public void write(PhotoPosition position, File source, OutputStream target) throws IOException {
try {
ImageMetadata metadata = Imaging.getMetadata(source);
TiffImageMetadata tiffImageMetadata = extractTiffImageMetadata(metadata);
TiffOutputSet outputSet = null;
if (tiffImageMetadata != null)
outputSet = tiffImageMetadata.getOutputSet();
if (outputSet == null)
outputSet = new TiffOutputSet();
TiffOutputDirectory rootDirectory = outputSet.getOrCreateRootDirectory();
rootDirectory.removeField(TIFF_TAG_MAKE);
rootDirectory.removeField(TIFF_TAG_MODEL);
rootDirectory.add(TIFF_TAG_MAKE, position.getMake());
rootDirectory.add(TIFF_TAG_MODEL, position.getModel());
TiffOutputDirectory exifDirectory = outputSet.getOrCreateExifDirectory();
exifDirectory.removeField(EXIF_TAG_USER_COMMENT);
exifDirectory.removeField(EXIF_TAG_EXIF_IMAGE_WIDTH);
exifDirectory.removeField(EXIF_TAG_EXIF_IMAGE_LENGTH);
exifDirectory.removeField(EXIF_TAG_FNUMBER);
exifDirectory.removeField(EXIF_TAG_EXPOSURE_TIME);
exifDirectory.removeField(EXIF_TAG_FLASH);
exifDirectory.removeField(EXIF_TAG_FOCAL_LENGTH);
exifDirectory.removeField(EXIF_TAG_ISO);
exifDirectory.add(EXIF_TAG_USER_COMMENT, position.getDescription());
if (position.getWidth() != null)
exifDirectory.add(EXIF_TAG_EXIF_IMAGE_WIDTH, position.getWidth().shortValue());
if (position.getHeight() != null)
exifDirectory.add(EXIF_TAG_EXIF_IMAGE_LENGTH, position.getHeight().shortValue());
exifDirectory.add(EXIF_TAG_FNUMBER, position.getfNumber());
exifDirectory.add(EXIF_TAG_EXPOSURE_TIME, position.getExposure());
if (position.getFlash() != null)
exifDirectory.add(EXIF_TAG_FLASH, position.getFlash().shortValue());
exifDirectory.add(EXIF_TAG_FOCAL_LENGTH, position.getFocal());
if (position.getPhotographicSensitivity() != null)
exifDirectory.add(EXIF_TAG_ISO, position.getPhotographicSensitivity().shortValue());
TiffOutputDirectory gpsDirectory = outputSet.getOrCreateGPSDirectory();
gpsDirectory.removeField(GPS_TAG_GPS_VERSION_ID);
gpsDirectory.add(GPS_TAG_GPS_VERSION_ID, (byte) 2, (byte) 3, (byte) 0, (byte) 0);
gpsDirectory.removeField(GPS_TAG_GPS_LONGITUDE);
gpsDirectory.removeField(GPS_TAG_GPS_LONGITUDE_REF);
gpsDirectory.removeField(GPS_TAG_GPS_LATITUDE);
gpsDirectory.removeField(GPS_TAG_GPS_LATITUDE_REF);
gpsDirectory.removeField(GPS_TAG_GPS_ALTITUDE);
gpsDirectory.removeField(GPS_TAG_GPS_ALTITUDE_REF);
gpsDirectory.removeField(GPS_TAG_GPS_SPEED);
gpsDirectory.removeField(GPS_TAG_GPS_SPEED_REF);
gpsDirectory.removeField(GPS_TAG_GPS_DATE_STAMP);
gpsDirectory.removeField(GPS_TAG_GPS_TIME_STAMP);
gpsDirectory.removeField(GPS_TAG_GPS_IMG_DIRECTION);
gpsDirectory.removeField(GPS_TAG_GPS_SATELLITES);
gpsDirectory.removeField(GPS_TAG_GPS_MEASURE_MODE);
gpsDirectory.removeField(GPS_TAG_GPS_DOP);
if (position.getLongitude() != null && position.getLatitude() != null)
outputSet.setGPSInDegrees(position.getLongitude(), position.getLatitude());
if (position.getElevation() != null) {
gpsDirectory.add(GPS_TAG_GPS_ALTITUDE, RationalNumber.valueOf(abs(position.getElevation())));
gpsDirectory.add(GPS_TAG_GPS_ALTITUDE_REF, (byte) (position.getElevation() > 0 ? 0 : 1));
}
if (position.getSpeed() != null) {
gpsDirectory.add(GPS_TAG_GPS_SPEED, RationalNumber.valueOf(position.getSpeed()));
gpsDirectory.add(GPS_TAG_GPS_SPEED_REF, GPS_TAG_GPS_SPEED_REF_VALUE_KMPH);
}
if (position.getTime() != null) {
Calendar calendar = position.getTime().getCalendar();
gpsDirectory.add(GPS_TAG_GPS_TIME_STAMP,
RationalNumber.valueOf(calendar.get(HOUR_OF_DAY)),
RationalNumber.valueOf(calendar.get(MINUTE)),
RationalNumber.valueOf(calendar.get(SECOND)));
String dateStamp = XXXX_FORMAT.format(calendar.get(YEAR)) + ":" +
XX_FORMAT.format(calendar.get(MONTH) + 1) + ":" +
XX_FORMAT.format(calendar.get(DAY_OF_MONTH));
gpsDirectory.add(GPS_TAG_GPS_DATE_STAMP, dateStamp);
}
if (position.getHeading() != null)
gpsDirectory.add(GPS_TAG_GPS_IMG_DIRECTION, RationalNumber.valueOf(position.getHeading()));
if (position.getSatellites() != null)
gpsDirectory.add(GPS_TAG_GPS_SATELLITES, position.getSatellites().toString());
if (position.getPdop() != null) {
gpsDirectory.add(GPS_TAG_GPS_MEASURE_MODE, Integer.toString(GPS_TAG_GPS_MEASURE_MODE_VALUE_3_DIMENSIONAL_MEASUREMENT));
gpsDirectory.add(GPS_TAG_GPS_DOP, RationalNumber.valueOf(position.getPdop()));
} else if (position.getHdop() != null) {
gpsDirectory.add(GPS_TAG_GPS_MEASURE_MODE, Integer.toString(GPS_TAG_GPS_MEASURE_MODE_VALUE_2_DIMENSIONAL_MEASUREMENT));
gpsDirectory.add(GPS_TAG_GPS_DOP, RationalNumber.valueOf(position.getHdop()));
}
new ExifRewriter().updateExifMetadataLossless(source, target, outputSet);
} catch (ImageReadException | ImageWriteException e) {
throw new IOException(e);
}
}
}