package osm.jp.gpx;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
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.ExifTagConstants;
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;
import org.xml.sax.SAXException;
public class ImgFolder extends ArrayList<File> {
File[] imgfiles;
AppParameters params;
File imgDir;
File outDir;
public ImgFolder(AppParameters params) {
this.params = params;
imgDir = params.getImgSourceFolder();
imgfiles = imgDir.listFiles(new JpegFileFilter());
Arrays.sort(imgfiles, new FileSort());
}
public ImgFolder setParams(AppParameters params) {
this.params = params;
return this;
}
public void setOutDir(File outDir) {
this.outDir = outDir;
}
public File getImgDir() {
return this.imgDir;
}
public File getImgBaseFile() {
return new File(imgDir, params.getProperty(AppParameters.IMG_BASE_FILE));
}
/**
* 個別のGPXファイルを処理する
*
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
* @throws ParseException
* @throws ImageWriteException
* @throws ImageReadException
* @throws TransformerException
*/
void procGPXfile(GpxFile gpxFile) throws ParserConfigurationException, SAXException, IOException, ParseException, ImageReadException, ImageWriteException, TransformerException {
System.gc();
ElementMapTRKSEG seg = gpxFile.parse();
long delta = 0;
String timeStr = params.getProperty(AppParameters.IMG_TIME);
try {
Date t = ImportPicture.toUTCDate(timeStr);
// 基準時刻ファイルの「更新日時」を使って時刻合わせを行う。
// argv[1] --> AppParameters.IMG_BASE_FILE に置き換え
Date imgtime = adjustTime(getImgBaseFile());
delta = t.getTime() - imgtime.getTime();
}
catch (ParseException e) {
// "'%s'の書式が違います(%s)"
System.out.println(
String.format(
ImportPicture.i18n.getString("msg.130"),
timeStr,
ImportPicture.TIME_FORMAT_STRING
)
);
return;
}
System.out.println("time difference: "+ (delta / 1000) +"(sec)");
System.out.println(" Target GPX: ["+ gpxFile.getAbsolutePath() +"]");
System.out.println(" EXIF: "+ (params.isImgOutputExif() ? ("convert to '" + outDir.getAbsolutePath() +"'") : "off"));
System.out.println();
// imgDir内の画像ファイルを処理する
System.out.println("|--------------------------------|--------------------|--------------------|--------------|--------------|--------|------|------|");
System.out.println("| name | Camera Time | GPStime | Latitude | Longitude | ele |magvar| km/h |");
System.out.println("|--------------------------------|--------------------|--------------------|--------------|--------------|--------|------|------|");
proc(delta, seg, params.isImgOutputExif(), gpxFile);
System.out.println("|--------------------------------|--------------------|--------------------|--------------|--------------|--------|------|------|");
}
/**
* 再帰メソッド
* @throws ParseException
* @throws IOException
* @throws ImageReadException
* @throws ImageWriteException
*/
boolean proc(long delta, ElementMapTRKSEG mapTRKSEG, boolean exifWrite, GpxFile gpxFile) throws ParseException, ImageReadException, IOException, ImageWriteException {
boolean ret = false;
for (File image : imgfiles) {
System.out.print(String.format("|%-32s|", image.getName()));
if (image.isDirectory()) {
ret = (new ImgFolder(params)).proc(delta, mapTRKSEG, exifWrite, gpxFile);
continue;
}
String imageName = image.getName();
if (!checkFile(imageName)) {
System.out.println(String.format("%20s ", "it is not image file."));
continue;
}
Discripter result = procImageFile(image, delta, mapTRKSEG, exifWrite, gpxFile);
ret |= result.ret;
switch (result.control) {
case Discripter.CONTINUE:
continue;
case Discripter.BREAK:
break;
}
}
return ret;
}
Discripter procImageFile(File imageFile, long delta, ElementMapTRKSEG mapTRKSEG, boolean exifWrite, GpxFile gpxFile) throws ParseException, ImageReadException, IOException, ImageWriteException {
Discripter result = new Discripter(false);
// itime <-- 画像ファイルの撮影時刻
// ファイルの更新日時/EXIFの撮影日時
Date itime = new Date(imageFile.lastModified());
if (params.isExifBase()) {
// 基準時刻(EXIF撮影日時)
ImageMetadata meta = Imaging.getMetadata(imageFile);
JpegImageMetadata jpegMetadata = (JpegImageMetadata)meta;
if (jpegMetadata == null) {
// "'%s'にEXIF情報がありません"
System.out.println(
String.format(
ImportPicture.i18n.getString("msg.140"),
imageFile.getAbsolutePath()
)
);
result.control = Discripter.CONTINUE;
return result;
}
TiffImageMetadata exif = jpegMetadata.getExif();
if (exif == null) {
// "'%s'にEXIF情報がありません"
System.out.println(
String.format(
ImportPicture.i18n.getString("msg.140"),
imageFile.getAbsolutePath()
)
);
result.control = Discripter.CONTINUE;
return result;
}
String dateTimeOriginal = exif.getFieldValue(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL)[0];
itime = ImportPicture.toEXIFDate(dateTimeOriginal);
}
System.out.print(String.format("%20s|", ImportPicture.toUTCString(itime)));
// uktime <-- 画像撮影時刻に対応するGPX時刻(補正日時)
Date correctedtime = new Date(itime.getTime() + delta);
System.out.print(String.format("%20s|", ImportPicture.toUTCString(correctedtime)));
// 時刻uktimeにおける<magver>をtrkptに追加する
String eleStr = "-";
String magvarStr = "-";
String speedStr = "-";
TagTrkpt trkptT = null;
for (Map.Entry<Date,ElementMapTRKPT> map : mapTRKSEG.entrySet()) {
ElementMapTRKPT mapTRKPT = map.getValue();
trkptT = mapTRKPT.getValue(correctedtime);
if (trkptT != null) {
break;
}
}
if (trkptT == null) {
System.out.print(String.format("%-14s|%-14s|", "", ""));
System.out.println(String.format("%8s|%6s|%6s|", "", "", ""));
if (!params.isImgOutputAll()) {
result.control = Discripter.CONTINUE;
return result;
}
}
else {
double latitude = trkptT.lat;
double longitude = trkptT.lon;
if (trkptT.eleStr != null) {
eleStr = trkptT.eleStr;
}
if (trkptT.magvarStr != null) {
magvarStr = trkptT.magvarStr;
}
if (trkptT.speedStr != null) {
speedStr = trkptT.speedStr;
}
System.out.print(String.format("%14.10f|%14.10f|", latitude, longitude));
System.out.println(String.format("%8s|%6s|%6s|", eleStr, magvarStr, speedStr));
}
result.ret = true;
outDir.mkdir();
if (exifWrite) {
exifWrite(imageFile, correctedtime, trkptT);
}
else {
if (params.isImgOutputAll()) {
// EXIFの変換を伴わない単純なファイルコピー
FileInputStream sStream = new FileInputStream(imageFile);
FileInputStream dStream = new FileInputStream(new File(outDir, imageFile.getName()));
FileChannel srcChannel = sStream.getChannel();
FileChannel destChannel = dStream.getChannel();
try {
srcChannel.transferTo(0, srcChannel.size(), destChannel);
}
finally {
srcChannel.close();
destChannel.close();
sStream.close();
dStream.close();
}
}
}
result.control = Discripter.NEXT;
return result;
}
void exifWrite(File imageFile, Date correctedtime, TagTrkpt trkptT) throws ImageReadException, IOException, ImageWriteException {
DecimalFormat yearFormatter = new DecimalFormat("0000");
DecimalFormat monthFormatter = new DecimalFormat("00");
DecimalFormat dayFormatter = new DecimalFormat("00");
TiffOutputSet outputSet = null;
ImageMetadata meta = Imaging.getMetadata(imageFile);
JpegImageMetadata jpegMetadata = (JpegImageMetadata)meta;
if (jpegMetadata != null) {
TiffImageMetadata exif = jpegMetadata.getExif();
if (exif != null) {
outputSet = exif.getOutputSet();
}
}
if (outputSet == null) {
outputSet = new TiffOutputSet();
}
//---- EXIF_TAG_DATE_TIME_ORIGINAL / 「撮影日時/オリジナル画像の生成日時」----
TiffOutputDirectory exifDir = outputSet.getOrCreateExifDirectory();
{
Calendar cal = Calendar.getInstance();
cal.setTimeZone(TimeZone.getTimeZone("UTC"));
cal.setTime(correctedtime);
exifDir.removeField(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL);
exifDir.add(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL, ImportPicture.toEXIFString(cal.getTime()));
}
//---- EXIF GPS_TIME_STAMP ----
TiffOutputDirectory gpsDir = outputSet.getOrCreateGPSDirectory();
{
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
cal.setTimeZone(TimeZone.getTimeZone("GMT+00"));
cal.setTime(correctedtime);
final String yearStr = yearFormatter.format(cal.get(Calendar.YEAR));
final String monthStr = monthFormatter.format(cal.get(Calendar.MONTH) + 1);
final String dayStr = dayFormatter.format(cal.get(Calendar.DAY_OF_MONTH));
final String dateStamp = yearStr +":"+ monthStr +":"+ dayStr;
gpsDir.removeField(GpsTagConstants.GPS_TAG_GPS_TIME_STAMP);
gpsDir.add(
GpsTagConstants.GPS_TAG_GPS_TIME_STAMP,
RationalNumber.valueOf(cal.get(Calendar.HOUR_OF_DAY)),
RationalNumber.valueOf(cal.get(Calendar.MINUTE)),
RationalNumber.valueOf(cal.get(Calendar.SECOND))
);
gpsDir.removeField(GpsTagConstants.GPS_TAG_GPS_DATE_STAMP);
gpsDir.add(GpsTagConstants.GPS_TAG_GPS_DATE_STAMP, dateStamp);
}
if (trkptT != null) {
//---- EXIF GPS elevation/ALTITUDE ----
if (trkptT.eleStr != null) {
final double altitude = Double.parseDouble(trkptT.eleStr);
gpsDir.removeField(GpsTagConstants.GPS_TAG_GPS_ALTITUDE);
gpsDir.add(GpsTagConstants.GPS_TAG_GPS_ALTITUDE, RationalNumber.valueOf(altitude));
}
//---- EXIF GPS magvar/IMG_DIRECTION ----
if (trkptT.magvarStr != null) {
final double magvar = Double.parseDouble(trkptT.magvarStr);
gpsDir.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION);
gpsDir.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION, RationalNumber.valueOf(magvar));
}
//---- EXIF GPS_ ----
outputSet.setGPSInDegrees(trkptT.lon, trkptT.lat);
}
ExifRewriter rewriter = new ExifRewriter();
try (FileOutputStream fos = new FileOutputStream(new File(outDir, imageFile.getName()))) {
rewriter.updateExifMetadataLossy(imageFile, fos, outputSet);
}
}
/**
* 基準時刻ファイルの「更新日時」を使って時刻合わせを行う。
* @param baseFile = new File(this.imgDir, this.params.getProperty(AppParameters.IMG_BASE_FILE));
* @return
* @throws ImageReadException
* @throws IOException
* @throws ParseException
*/
private Date adjustTime(File baseFile) throws ImageReadException, IOException, ParseException {
if (params.isExifBase()) {
// 基準時刻(EXIF撮影日時)
ImageMetadata meta = Imaging.getMetadata(baseFile);
JpegImageMetadata jpegMetadata = (JpegImageMetadata)meta;
if (jpegMetadata == null) {
// "'%s'にEXIF情報がありません"
System.out.println(
String.format(
ImportPicture.i18n.getString("msg.140"),
baseFile.getAbsolutePath()
)
);
return null;
}
TiffImageMetadata exif = jpegMetadata.getExif();
if (exif == null) {
// "'%s'にEXIF情報がありません"
System.out.println(
String.format(
ImportPicture.i18n.getString("msg.140"),
baseFile.getAbsolutePath()
)
);
return null;
}
String dateTimeOriginal = exif.getFieldValue(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL)[0];
return new Date(ImportPicture.toEXIFDate(dateTimeOriginal).getTime());
}
else {
// 基準時刻(ファイル更新日時)
return new Date(baseFile.lastModified());
}
}
/**
* 対象は '*.JPG' のみ対象とする
* @return
* @param name
*/
public static boolean checkFile(String name) {
return ((name != null) && name.toUpperCase().endsWith(".JPG"));
}
private static final long serialVersionUID = -1137199371724546343L;
class Discripter {
static final int NEXT = 0;
static final int CONTINUE = -1;
static final int BREAK = 1;
public boolean ret;
public int control;
public Discripter(boolean ret) {
this.ret = ret;
this.control = Discripter.NEXT;
}
}
/**
* ファイル名の順序に並び替えるためのソートクラス
*
* @author hayashi
*/
static class FileSort implements Comparator<File> {
@Override
public int compare(File src, File target){
int diff = src.getName().compareTo(target.getName());
return diff;
}
}
/**
* JPEGファイルフィルター
* @author yuu
*/
class JpegFileFilter implements FilenameFilter {
@Override
public boolean accept(File dir, String name) {
return name.toUpperCase().matches(".*\\.JPG$");
}
}
}