// Wrapper class for sanselan library package org.openstreetmap.josm.plugins.photo_geotagging; import static org.openstreetmap.josm.tools.I18n.tr; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.text.DecimalFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.TimeZone; 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.TiffImageMetadata; import org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants; import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory; import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet; public class ExifGPSTagger { /** * Set the GPS values in JPEG EXIF metadata. * This is taken from one of the examples of the sanselan project. * * @param jpegImageFile A source image file. * @param dst The output file. * @param lat latitude * @param lon longitude * @param gpsTime time - can be null if not available * @param speed speed in km/h - can be null if not available * @param ele elevation - can be null if not available * @param imgDir image direction in degrees (0..360) - can be null if not available */ public static void setExifGPSTag(File jpegImageFile, File dst, double lat, double lon, Date gpsTime, Double speed, Double ele, Double imgDir) throws IOException { try { setExifGPSTagWorker(jpegImageFile, dst, lat, lon, gpsTime, speed, ele, imgDir); } catch (ImageReadException ire) { throw new IOException(tr("Read error: "+ire), ire); } catch (ImageWriteException ire2) { throw new IOException(tr("Write error: "+ire2), ire2); } } public static void setExifGPSTagWorker(File jpegImageFile, File dst, double lat, double lon, Date gpsTime, Double speed, Double ele, Double imgDir) throws IOException, ImageReadException, ImageWriteException { TiffOutputSet outputSet = null; ImageMetadata metadata = Imaging.getMetadata(jpegImageFile); JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata; if (null != jpegMetadata) { TiffImageMetadata exif = jpegMetadata.getExif(); if (null != exif) { outputSet = exif.getOutputSet(); } } if (null == outputSet) { outputSet = new TiffOutputSet(); } TiffOutputDirectory gpsDirectory = outputSet.getOrCreateGPSDirectory(); gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_VERSION_ID); gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_VERSION_ID, (byte)2, (byte)3, (byte)0, (byte)0); if (gpsTime != null) { Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); calendar.setTime(gpsTime); final int year = calendar.get(Calendar.YEAR); final int month = calendar.get(Calendar.MONTH) + 1; final int day = calendar.get(Calendar.DAY_OF_MONTH); final int hour = calendar.get(Calendar.HOUR_OF_DAY); final int minute = calendar.get(Calendar.MINUTE); final int second = calendar.get(Calendar.SECOND); DecimalFormat yearFormatter = new DecimalFormat("0000"); DecimalFormat monthFormatter = new DecimalFormat("00"); DecimalFormat dayFormatter = new DecimalFormat("00"); final String yearStr = yearFormatter.format(year); final String monthStr = monthFormatter.format(month); final String dayStr = dayFormatter.format(day); final String dateStamp = yearStr+":"+monthStr+":"+dayStr; //System.err.println("date: "+dateStamp+" h/m/s: "+hour+"/"+minute+"/"+second); // make sure to remove old value if present (this method will // not fail if the tag does not exist). gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_TIME_STAMP); gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_TIME_STAMP, RationalNumber.valueOf(hour), RationalNumber.valueOf(minute), RationalNumber.valueOf(second)); // make sure to remove old value if present (this method will // not fail if the tag does not exist). gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_DATE_STAMP); gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_DATE_STAMP, dateStamp); } outputSet.setGPSInDegrees(lon, lat); if (speed != null) { gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_SPEED_REF); gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_SPEED_REF, GpsTagConstants.GPS_TAG_GPS_SPEED_REF_VALUE_KMPH); gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_SPEED); gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_SPEED, RationalNumber.valueOf(speed)); } if (ele != null) { byte eleRef = ele >= 0 ? (byte) 0 : (byte) 1; gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_ALTITUDE_REF); gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_ALTITUDE_REF, eleRef); gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_ALTITUDE); gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_ALTITUDE, RationalNumber.valueOf(Math.abs(ele))); } if (imgDir != null) { gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF); gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF, GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF_VALUE_TRUE_NORTH); // make sure the value is in the range 0.0...<360.0 if (imgDir < 0.0) { imgDir %= 360.0; // >-360.0...-0.0 imgDir += 360.0; // >0.0...360.0 } if (imgDir >= 360.0) { imgDir %= 360.0; } gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION); gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION, RationalNumber.valueOf(imgDir)); } try (BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(dst))) { new ExifRewriter().updateExifMetadataLossless(jpegImageFile, os, outputSet); } } }