Newer
Older
adjustgpx-core / src / osm / jp / gpx / ImportPicture.java
package osm.jp.gpx;

import java.io.*;
import java.nio.channels.FileChannel;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
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.ResourceBundle;
import java.util.TimeZone;
import java.util.logging.LogManager;
import java.util.logging.Logger;

import javax.xml.parsers.*;
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.w3c.dom.*;
import org.xml.sax.SAXException;

public class ImportPicture extends Thread {
    
    /**
     * 実行中に発生したExceptionを保持する場所
     */
    public Exception ex = null;
	
    /**
     * ログ設定プロパティファイルのファイル内容
     */
    protected static final String LOGGING_PROPERTIES_DATA
        = "handlers=java.util.logging.ConsoleHandler\n"
        + ".level=FINEST\n"
        + "java.util.logging.ConsoleHandler.level=INFO\n"
        + "java.util.logging.ConsoleHandler.formatter=osm.jp.gpx.YuuLogFormatter";

    /**
     * static initializer によるログ設定の初期化
     */
    public static final Logger LOGGER = Logger.getLogger("CommandLogging");
    static {
        InputStream inStream = null;
        try {
            inStream = new ByteArrayInputStream(LOGGING_PROPERTIES_DATA.getBytes("UTF-8"));
            try {
                LogManager.getLogManager().readConfiguration(inStream);
                // "ログ設定: LogManagerを設定しました。"
                LOGGER.config("LoggerSettings: LogManager setuped.");
            }
            catch (IOException e) {
                // LogManager設定の際に例外が発生しました.
                String str = "LoggerSettings: Exception occered:" + e.toString();
                LOGGER.warning(str);
            }
        }
        catch (UnsupportedEncodingException e) {
            String str = "LoggerSettings: Not supported 'UTF-8' encoding: " + e.toString();
            LOGGER.severe(str);
        }
        finally {
            try {
                if (inStream != null) {
                    inStream.close();
                }
            } catch (IOException e) {
                String str = "LoggerSettings: Exception occored: "+ e.toString();
                LOGGER.warning(str);
            }
        }
    }
    
    /** メイン
     * 画像ファイルをGPXファイルに取り込みます。
     * 
     * ・画像ファイルの更新日付をその画像の撮影日時とします。(Exi情報は無視します)
     *    ※ 対象とするファイルは'*.jpg'のみ
     * ・精確な時刻との時差を入力することで、撮影日時を補正します。
     * ・画像ファイルの更新日付リストをCSV形式のファイルとして出力する。
     * ・・結果は、取り込み元のGPXファイルとは別に、元ファイル名にアンダーバー「_」を付加した.ファイルに出力します。
     * 
     *  exp) $ java -cp .:AdjustTime.jar:commons-imaging-1.0-SNAPSHOT.jar [AdjustTime.ini]
     *  exp) > java -cp .;AdjustTime.jar;commons-imaging-1.0-SNAPSHOT.jar [AdjustTime.ini]
     *
     * @param argv
     * argv[0] = INIファイルのパス名
     * 
     * argv[-] = dummy
     * argv[0] = 画像ファイルが格納されているディレクトリ		--> imgDir
     * argv[1] = 時刻補正の基準とする画像ファイル				--> baseFile
     * argv[2] = 基準画像ファイルの精確な撮影日時 "yyyy-MM-dd'T'HH:mm:ss"	--> timeStr
     * argv[3] = 出力先フォルダ								--> outDir
     * argv[4] = 撮影位置をロギングしたGPXファイル				--> gpxDir
     * 
     * @throws IOException
     * @throws ImageReadException 
     */
    public static void main(String[] argv) throws Exception
    {
        ImportPicture obj = new ImportPicture();
        obj.setUp(((argv.length < 1) ? AppParameters.FILE_PATH : argv[0]));
    }
    
    public File gpxDir;
    public File imgDir;
    public File outDir;
    public long delta = 0;
    public boolean exif = false;
    public boolean exifBase = false;
    public ArrayList<File> gpxFiles = new ArrayList<>();
    public AppParameters params;
    public boolean param_GpxSplit = false;
    public static boolean param_GpxNoFirstNode = false;
    public boolean param_GpxReuse = false;
    public boolean param_GpxOutputWpt = true;
    public boolean param_ImgOutputAll = false;
    public String param_GpxSourceFolder = ".";
    
    public static final String TIME_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ss'Z'";
    private static final String EXIF_DATE_TIME_FORMAT_STRING = "yyyy:MM:dd HH:mm:ss";
    public ResourceBundle i18n = ResourceBundle.getBundle("i18n");
    
    public void setUp(String paramFilePath) throws Exception {
        System.out.println("Param File = '"+ paramFilePath +"'");
        this.params = new AppParameters(paramFilePath);

        Date imgtime;

        System.out.println(" - param: "+ AppParameters.IMG_TIME +"="+ this.params.getProperty(AppParameters.IMG_TIME) );
        System.out.println(" - param: "+ AppParameters.IMG_BASE_FILE +"="+ this.params.getProperty(AppParameters.IMG_BASE_FILE) );
        System.out.println(" - param: "+ AppParameters.GPX_BASETIME +"="+ this.params.getProperty(AppParameters.GPX_BASETIME) );
        System.out.println(" - param: "+ AppParameters.IMG_SOURCE_FOLDER +"="+ this.params.getProperty(AppParameters.IMG_SOURCE_FOLDER) );
        System.out.println(" - param: "+ AppParameters.IMG_OUTPUT_FOLDER +"="+ this.params.getProperty(AppParameters.IMG_OUTPUT_FOLDER) );
        System.out.println(" - param: "+ AppParameters.IMG_OUTPUT +"="+ this.params.getProperty(AppParameters.IMG_OUTPUT));     
        System.out.println(" - param: "+ AppParameters.IMG_OUTPUT_ALL +"="+ this.param_ImgOutputAll);
        System.out.println(" - param: "+ AppParameters.IMG_OUTPUT_EXIF +"= "+ String.valueOf(this.exif));
        System.out.println(" - param: "+ AppParameters.GPX_SOURCE_FOLDER +"="+ this.param_GpxSourceFolder);
        System.out.println(" - param: "+ AppParameters.GPX_OUTPUT_WPT +"="+ this.param_GpxOutputWpt);
        System.out.println(" - param: "+ AppParameters.GPX_OVERWRITE_MAGVAR +"="+ Complementation.param_GpxOverwriteMagvar);
        System.out.println(" - param: "+ AppParameters.GPX_OUTPUT_SPEED +"="+ Complementation.param_GpxOutputSpeed);
        System.out.println(" - param: "+ AppParameters.GPX_GPXSPLIT +"="+ this.param_GpxSplit);
        System.out.println(" - param: "+ AppParameters.GPX_NO_FIRST_NODE +"="+ ImportPicture.param_GpxNoFirstNode);        
        System.out.println(" - param: "+ AppParameters.GPX_REUSE +"="+ this.param_GpxReuse);

        this.ex = null;
        // argv[0] --> AppParameters.IMG_SOURCE_FOLDER に置き換え
        this.imgDir = new File(this.params.getProperty(AppParameters.IMG_SOURCE_FOLDER));

        // 基準時刻(ファイル更新日時 | EXIF撮影日時)
    	this.exifBase = (this.params.getProperty(AppParameters.GPX_BASETIME).equals("EXIF_TIME"));

        // 基準時刻ファイルの「更新日時」を使って時刻合わせを行う。
        // argv[1] --> AppParameters.IMG_BASE_FILE に置き換え
    	imgtime = this.adjustTime(new File(this.imgDir, this.params.getProperty(AppParameters.IMG_BASE_FILE)));

        // 出力ファイル
        // argv[3] --> AppParameters.IMG_OUTPUT に置き換え
        this.outDir = new File(this.params.getProperty(AppParameters.IMG_OUTPUT_FOLDER));

        // その他のパラメータを読み取る
    	String paramStr = this.params.getProperty(AppParameters.GPX_GPXSPLIT);
    	if ((paramStr != null) && (paramStr.equals(Boolean.toString(true)))) {
            this.param_GpxSplit = true;
    	}
        
    	paramStr = this.params.getProperty(AppParameters.GPX_NO_FIRST_NODE);
    	if ((paramStr != null) && (paramStr.equals(Boolean.toString(true)))) {
            ImportPicture.param_GpxNoFirstNode = true;
    	}
    	
    	paramStr = this.params.getProperty(AppParameters.GPX_REUSE);
    	if ((paramStr != null) && (paramStr.equals(Boolean.toString(true)))) {
            this.param_GpxReuse = true;
    	}
        
    	paramStr = this.params.getProperty(AppParameters.IMG_OUTPUT_ALL);
    	if ((paramStr != null) && (paramStr.equals(Boolean.toString(true)))) {
            this.param_ImgOutputAll = true;
    	}

    	paramStr = this.params.getProperty(AppParameters.GPX_OUTPUT_WPT);
    	if ((paramStr != null) && (paramStr.equals(Boolean.toString(true)))) {
            this.param_GpxOutputWpt = true;
    	}
    	
    	paramStr = this.params.getProperty(AppParameters.GPX_OVERWRITE_MAGVAR);
    	if ((paramStr != null) && (paramStr.equals(Boolean.toString(true)))) {
            Complementation.param_GpxOverwriteMagvar = true;
    	}

    	paramStr = this.params.getProperty(AppParameters.GPX_OUTPUT_SPEED);
    	if ((paramStr != null) && (paramStr.equals(Boolean.toString(true)))) {
            Complementation.param_GpxOutputSpeed = true;
    	}

    	paramStr = this.params.getProperty(AppParameters.GPX_SOURCE_FOLDER);
    	if (paramStr != null) {
            this.param_GpxSourceFolder = paramStr;
            this.gpxDir = new File(this.param_GpxSourceFolder);
            if (!this.gpxDir.exists()) {
            	// GPXファイルまたはディレクトリが存在しません。('%s')
                System.out.println(
                    String.format(i18n.getString("msg.100"), paramStr)
                );
            	return;
            }
    	}
        else {
            this.gpxDir = this.imgDir;
        }

    	// 指定されたディレクトリ内のGPXファイルすべてを対象とする
        if (this.gpxDir.isDirectory()) {
            File[] files = this.gpxDir.listFiles();
            if (files == null) {
            	// 対象となるGPXファイルがありませんでした。('%s')
            	System.out.println(
                    String.format(i18n.getString("msg.110"), this.gpxDir.getAbsolutePath())
                );
            	return;
            }
            if (this.param_ImgOutputAll && (files.length > 1)) {
                // "複数のGPXファイルがあるときには、'IMG.OUTPUT_ALL'オプションは指定できません。"
            	System.out.println(
                    i18n.getString("msg.120")
                );
            	return;
            }
            
            java.util.Arrays.sort(
                files, new java.util.Comparator<File>() {
                    @Override
                    public int compare(File file1, File file2){
                        return file1.getName().compareTo(file2.getName());
                    }
                }
            );
            for (File file : files) {
                if (file.isFile()) {
                    String filename = file.getName().toUpperCase();
                    if (filename.toUpperCase().endsWith(".GPX")) {
                        if (!filename.toUpperCase().endsWith("_.GPX") || this.param_GpxReuse) {
                            this.gpxFiles.add(file);
                        }
                    }
                }
            }
        }
        else {
            this.gpxFiles.add(this.gpxDir);
        }

    	paramStr = this.params.getProperty(AppParameters.IMG_OUTPUT_EXIF);
    	if ((paramStr != null) && (paramStr.equals(Boolean.toString(true)))) {
            this.exif = true;
    	}
    	
        String timeStr = this.params.getProperty(AppParameters.IMG_TIME);
        try {
            Date t = toUTCDate(timeStr);
            this.delta = t.getTime() - imgtime.getTime();
        }
        catch (ParseException e) {
            // "'%s'の書式が違います(%s)"
            System.out.println(
                String.format(
                    i18n.getString("msg.130"),
                    timeStr,
                    TIME_FORMAT_STRING
                )
            );
            return;
        }

        this.start();
        try {
            this.join();
        } catch(InterruptedException end) {}
        if (this.ex != null) {
            throw this.ex;
        }
    }
    
    /**
     * @code{
        <wpt lat="35.25714922" lon="139.15490497">
            <ele>62.099998474121094</ele>
            <time>2012-06-11T00:44:38Z</time>
            <hdop>0.75</hdop>
            <name><![CDATA[写真]]></name>
            <cmt><![CDATA[精度: 3.0m]]></cmt>
            <link href="2012-06-11_09-44-38.jpg">
                <text>2012-06-11_09-44-38.jpg</text>
            </link>
            <sat>9</sat>
        </wpt>
     * }
     */
    @Override
    public void run() {
        try {
            if (params.getProperty(AppParameters.IMG_OUTPUT).equals(Boolean.toString(true))) {
                outDir = new File(outDir, imgDir.getName());
            }
            else {
                outDir = gpxDir;
            }
            for (File gpxFile : this.gpxFiles) {
            	procGPXfile(new GpxFile(gpxFile));
            }
        }
        catch(ParserConfigurationException | DOMException | SAXException | IOException | ParseException | ImageReadException | ImageWriteException | IllegalArgumentException | TransformerException e) {
            e.printStackTrace();
            this.ex = new Exception(e);
        }
    }
    
    /**
     * 個別の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();

        System.out.println("time difference: "+ (delta / 1000) +"(sec)");
        System.out.println("     Target GPX: ["+ gpxFile.getAbsolutePath() +"]");
        System.out.println("           EXIF: "+ (exif ? ("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("|--------------------------------|--------------------|--------------------|--------------|--------------|--------|------|------|");
        boolean out = proc(imgDir, delta, gpxFile.mapTRKSEG, exif, gpxFile);
        System.out.println("|--------------------------------|--------------------|--------------------|--------------|--------------|--------|------|------|");
        if (out) {
            // GPX出力
            gpxFile.output(outDir);
        }
    }
	
    /**
     * 再帰メソッド
     * @throws ParseException 
     * @throws IOException 
     * @throws ImageReadException 
     * @throws ImageWriteException 
     */
    boolean proc(File dir, long delta, ElementMapTRKSEG mapTRKSEG, boolean exifWrite, GpxFile gpxFile) throws ParseException, ImageReadException, IOException, ImageWriteException {
        boolean ret = false;
        File[] files = dir.listFiles(new JpegFileFilter());
        Arrays.sort(files, new FileSort());
        for (File image : files) {
            System.out.print(String.format("|%-32s|", image.getName()));
            if (image.isDirectory()) {
                ret = proc(image, 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;
    }
    
    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;
    	}
    }
    
    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 (this.exifBase) {
            ImageMetadata meta = Imaging.getMetadata(imageFile);
            JpegImageMetadata jpegMetadata = (JpegImageMetadata)meta;
            if (jpegMetadata == null) {
                // "'%s'にEXIF情報がありません"
                System.out.println(
                    String.format(
                        i18n.getString("msg.140"), 
                        imageFile.getAbsolutePath()
                    )
                );
                result.control = Discripter.CONTINUE;
                return result;
            }
            @SuppressWarnings("LocalVariableHidesMemberVariable")
            TiffImageMetadata exif = jpegMetadata.getExif();
            if (exif == null) {
                // "'%s'にEXIF情報がありません"
                System.out.println(
                    String.format(
                        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|", toUTCString(itime)));

        // uktime <-- 画像撮影時刻に対応するGPX時刻(補正日時)
        Date correctedtime = new Date(itime.getTime() + delta);
        System.out.print(String.format("%20s|", 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 (!this.param_ImgOutputAll) {
                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);

            if (Boolean.parseBoolean(params.getProperty(AppParameters.GPX_OUTPUT_WPT))) {
            	if (trkptT != null) {
                	Element temp = gpxFile.createWptTag(imageFile, imgDir, itime.getTime(), trkptT.trkpt);
                    gpxFile.gpx.appendChild(temp);
            	}
            }
        }
        else {
            if (this.param_ImgOutputAll) {
                // 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) {
            @SuppressWarnings("LocalVariableHidesMemberVariable")
            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);
        }
    }
    
    // 基準時刻ファイルの「更新日時」を使って時刻合わせを行う。
    // argv[1] --> AppParameters.IMG_BASE_FILE に置き換え
    // File baseFile = new File(this.imgDir, this.params.getProperty(AppParameters.IMG_BASE_FILE));
    private Date adjustTime(File baseFile) throws ImageReadException, IOException, ParseException {
        if (exifBase) {
            ImageMetadata meta = Imaging.getMetadata(baseFile);
            JpegImageMetadata jpegMetadata = (JpegImageMetadata)meta;
            if (jpegMetadata == null) {
                // "'%s'にEXIF情報がありません"
                System.out.println(
                    String.format(
                        i18n.getString("msg.140"), 
                        baseFile.getAbsolutePath()
                    )
                );
                return null;
            }
            @SuppressWarnings("LocalVariableHidesMemberVariable")
            TiffImageMetadata exif = jpegMetadata.getExif();
            if (exif == null) {
                // "'%s'にEXIF情報がありません"
                System.out.println(
                    String.format(
                        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"));
    }

    /**
     * DateをEXIFの文字列に変換する。
     * 注意:EXiFの撮影時刻はUTC時間ではない
     * @param localdate
     * @return
     */
    public static String toEXIFString(Date localdate) {
    	DateFormat dfUTC = new SimpleDateFormat(EXIF_DATE_TIME_FORMAT_STRING);
    	return dfUTC.format(localdate);
    }
    
    /**
     * EXIFの文字列をDateに変換する。
     * 注意:EXiFの撮影時刻はUTC時間ではない
     * @param timeStr
     * @return
     * @throws ParseException
     */
    public static Date toEXIFDate(String timeStr) throws ParseException {
    	DateFormat dfUTC = new SimpleDateFormat(EXIF_DATE_TIME_FORMAT_STRING);
    	//dfUTC.setTimeZone(TimeZone.getTimeZone("UTC"));
    	return dfUTC.parse(timeStr);
    }
	
    public static String toUTCString(Date localdate) {
    	DateFormat dfUTC = new SimpleDateFormat(TIME_FORMAT_STRING);
    	dfUTC.setTimeZone(TimeZone.getTimeZone("UTC"));
    	return dfUTC.format(localdate);
    }
	
    public static Date toUTCDate(String timeStr) throws ParseException {
    	DateFormat dfUTC = new SimpleDateFormat(TIME_FORMAT_STRING);
    	dfUTC.setTimeZone(TimeZone.getTimeZone("UTC"));
    	return dfUTC.parse(timeStr);
    }
	
    static String getShortPathName(File dir, File iFile) {
        String dirPath = dir.getAbsolutePath();
        String filePath = iFile.getAbsolutePath();
        if (filePath.startsWith(dirPath)) {
            return filePath.substring(dirPath.length()+1);
        }
        else {
            return filePath;
        }
    }
    
    /**
     * ファイル名の順序に並び替えるためのソートクラス
     * 
     * @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$");
    	}
    }
}