diff --git a/nb-configuration.xml b/nb-configuration.xml new file mode 100644 index 0000000..5b0f1f8 --- /dev/null +++ b/nb-configuration.xml @@ -0,0 +1,18 @@ + + + + + + true + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..7529f2d --- /dev/null +++ b/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + osm.surveyor + AdjustTerra + 5.2-SNAPSHOT + jar + + + + hayashi_repository + Hayashi Repository + http://surveyor.mydns.jp/archiva/repository/hayashi_repository/ + + true + + + false + + + + + + + org.apache.commons + commons-imaging + 1.0-alpha1 + jar + + + org.hamcrest + hamcrest-core + 1.3 + test + jar + + + junit + junit + 4.12 + test + jar + + + org.apache.commons + commons-compress + 1.14 + test + jar + + + + + UTF-8 + 11 + 11 + + + + + + + maven-assembly-plugin + 3.2.0 + + + + jar-with-dependencies + + + + osm.jp.gpx.matchtime.gui.AdjustTerra + + + + + + make-assembly + package + + single + + + + + + + \ No newline at end of file diff --git a/src/main/java/osm/jp/gpx/AppParameters.java b/src/main/java/osm/jp/gpx/AppParameters.java new file mode 100644 index 0000000..12a519c --- /dev/null +++ b/src/main/java/osm/jp/gpx/AppParameters.java @@ -0,0 +1,216 @@ +package osm.jp.gpx; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Properties; + +@SuppressWarnings("serial") +public class AppParameters extends Properties { + static final String FILE_PATH = "AdjustTime.ini"; + + // GPX: 時間的に間隔が開いたGPXログを別のセグメントに分割する。 {ON | OFF} + public static String GPX_GPXSPLIT = "GPX.gpxSplit"; + + // GPX: セグメントの最初の1ノードは無視する。 {ON | OFF} + public static String GPX_NO_FIRST_NODE = "GPX.noFirstNode"; + + // GPX: 生成されたGPXファイル(ファイル名が'_.gpx'で終わるもの)も対象にする。 {ON | OFF} + public static String GPX_REUSE = "GPX.REUSE"; + + // GPX: 基準時刻 {FILE_UPDATE | EXIF_TIME} + public static String GPX_BASETIME = "GPX.BASETIME"; + + // GPX: ファイル更新時刻 yyyy:MM:dd HH:mm:ss + public static String IMG_TIME = "IMG.TIME"; + + // 対象IMGフォルダ:(位置情報を付加したい画像ファイルが格納されているフォルダ) + public static String IMG_SOURCE_FOLDER = "IMG.SOURCE_FOLDER"; + + // 基準時刻画像(正確な撮影時刻が判明できる画像) + public static String IMG_BASE_FILE = "IMG.BASE_FILE"; + + // 対象GPXフォルダ:(GPXファイルが格納されているフォルダ) + public static String GPX_SOURCE_FOLDER = "GPX.SOURCE_FOLDER"; + + // 出力フォルダ:(変換した画像ファイルとGPXファイルを出力するフォルダ) + public static String IMG_OUTPUT_FOLDER = "IMG.OUTPUT_FOLDER"; + + // 出力IMG: IMG出力をする {ON | OFF} + public static String IMG_OUTPUT = "IMG.OUTPUT"; + + // 出力IMG: 'out of time'も IMG出力の対象とする {ON | OFF} + //   この場合は、対象IMGフォルダ内のすべてのIMGファイルが出力フォルダに出力される + public static String IMG_OUTPUT_ALL = "IMG.OUTPUT_ALL"; + + // 出力IMG: EXIFを変換する + public static String IMG_OUTPUT_EXIF = "IMG.OUTPUT_EXIF"; + + // 出力GPX: を上書き出力する {ON | OFF} + public static String GPX_OUTPUT_SPEED = "GPX.OUTPUT_SPEED"; + + // 出力GPX: ソースGPXのを無視する {ON | OFF} + public static String GPX_OVERWRITE_MAGVAR = "GPX.OVERWRITE_MAGVAR"; + + // 出力GPX: マーカーを出力する {ON | OFF} + public static String GPX_OUTPUT_WPT = "GPX.OUTPUT_WPT"; + + File file; + + public AppParameters() throws FileNotFoundException, IOException { + super(); + this.file = new File(FILE_PATH); + syncFile(); + } + + public AppParameters(Properties defaults) throws FileNotFoundException, IOException { + super(defaults); + this.file = new File(FILE_PATH); + syncFile(); + } + + public AppParameters(String iniFileName) throws FileNotFoundException, IOException { + super(); + this.file = new File(iniFileName); + syncFile(); + } + + private void syncFile() throws FileNotFoundException, IOException { + boolean update = false; + + if (this.file.exists()) { + // ファイルが存在すれば、その内容をロードする。 + this.load(new FileInputStream(file)); + } + else { + update = true; + } + + //------------------------------------------------ + // 対象フォルダ:(位置情報を付加したい画像ファイルが格納されているフォルダ) + String valueStr = this.getProperty(IMG_SOURCE_FOLDER); + if (valueStr == null) { + update = true; + this.setProperty(IMG_SOURCE_FOLDER, (new File(".")).getAbsolutePath()); + } + + //------------------------------------------------ + // 対象フォルダ:(GPXファイルが格納されているフォルダ) + valueStr = this.getProperty(GPX_SOURCE_FOLDER); + if (valueStr == null) { + update = true; + this.setProperty(GPX_SOURCE_FOLDER, (new File(".")).getAbsolutePath()); + } + + //------------------------------------------------ + // 基準時刻画像(正確な撮影時刻が判明できる画像) + valueStr = this.getProperty(IMG_BASE_FILE); + if (valueStr == null) { + update = true; + this.setProperty(IMG_BASE_FILE, ""); + } + + //------------------------------------------------ + // 出力フォルダ:(変換した画像ファイルとGPXファイルを出力するフォルダ) + valueStr = this.getProperty(IMG_OUTPUT_FOLDER); + if (valueStr == null) { + update = true; + this.setProperty(IMG_OUTPUT_FOLDER, (new File(".")).getAbsolutePath()); + } + + //------------------------------------------------ + // IMG出力: IMGを出力する + valueStr = this.getProperty(IMG_OUTPUT); + if (valueStr == null) { + update = true; + valueStr = String.valueOf(true); + } + this.setProperty(IMG_OUTPUT, String.valueOf(valueStr)); + + //------------------------------------------------ + // 出力IMG: 'out of time'も IMG出力の対象とする + valueStr = this.getProperty(IMG_OUTPUT_ALL); + if (valueStr == null) { + update = true; + valueStr = String.valueOf(false); + } + this.setProperty(IMG_OUTPUT_ALL, String.valueOf(valueStr)); + + //------------------------------------------------ + // IMG出力: EXIFを変換する + valueStr = this.getProperty(IMG_OUTPUT_EXIF); + if (valueStr == null) { + update = true; + valueStr = String.valueOf(true); + } + this.setProperty(IMG_OUTPUT_EXIF, String.valueOf(valueStr)); + + //------------------------------------------------ + // GPX出力: 時間的に間隔が開いたGPXログを別のセグメントに分割する。 {ON | OFF} + valueStr = this.getProperty(GPX_GPXSPLIT); + if (valueStr == null) { + update = true; + this.setProperty(GPX_GPXSPLIT, String.valueOf(true)); + } + + //------------------------------------------------ + // GPX出力: セグメントの最初の1ノードは無視する。 {ON | OFF} + valueStr = this.getProperty(GPX_NO_FIRST_NODE); + if (valueStr == null) { + update = true; + this.setProperty(GPX_NO_FIRST_NODE, String.valueOf(true)); + } + + //------------------------------------------------ + // GPX出力: ポイントマーカーを出力する {ON | OFF} + valueStr = this.getProperty(GPX_OUTPUT_WPT); + if (valueStr == null) { + update = true; + this.setProperty(GPX_OUTPUT_WPT, String.valueOf(false)); + } + + //------------------------------------------------ + // GPX出力: ソースGPXのを無視する {ON | OFF} + valueStr = this.getProperty(GPX_OVERWRITE_MAGVAR); + if (valueStr == null) { + update = true; + this.setProperty(GPX_OVERWRITE_MAGVAR, String.valueOf(false)); + } + + //------------------------------------------------ + // GPX出力: を上書き出力する {ON | OFF} + valueStr = this.getProperty(GPX_OUTPUT_SPEED); + if (valueStr == null) { + update = true; + this.setProperty(GPX_OUTPUT_SPEED, String.valueOf(false)); + } + + //------------------------------------------------ + // GPX出力: 生成されたGPXファイル(ファイル名が'_.gpx'で終わるもの)も対象にする。 {ON | OFF} + valueStr = this.getProperty(GPX_REUSE); + if (valueStr == null) { + update = true; + this.setProperty(GPX_REUSE, String.valueOf(false)); + } + + //------------------------------------------------ + // GPX: 基準時刻 {FILE_UPDATE | EXIF} + valueStr = this.getProperty(GPX_BASETIME); + if (valueStr == null) { + update = true; + this.setProperty(GPX_BASETIME, "FILE_UPDATE"); + } + + if (update) { + // ・ファイルがなければ新たに作る + // ・項目が足りない時は書き足す。 + this.store(new FileOutputStream(this.file), "defuilt settings"); + } + } + + public void store() throws FileNotFoundException, IOException { + this.store(new FileOutputStream(this.file), "by AdjustTime"); + } +} diff --git a/src/main/java/osm/jp/gpx/Complementation.java b/src/main/java/osm/jp/gpx/Complementation.java new file mode 100644 index 0000000..3eaed7a --- /dev/null +++ b/src/main/java/osm/jp/gpx/Complementation.java @@ -0,0 +1,112 @@ +package osm.jp.gpx; + +import java.text.ParseException; + +public class Complementation { + public static final Double R = (6378137D + 6356752.314D)/2D; // 6367444.657m + + public TagTrkpt imaTag = null; + public TagTrkpt maeTag = null; + public static boolean param_GpxOutputSpeed = false; + public static boolean param_GpxOverwriteMagvar = false; + + /** + * @param imaE + * @param maeE + * @throws java.text.ParseException + * @code{ + * + * 267.291 + * 359 + * + * + * + * } + * + * + * @throws ParseException + */ + public Complementation(TagTrkpt imaE, TagTrkpt maeE) throws ParseException { + this.imaTag = new TagTrkpt(imaE.trkpt); + if (maeE != null) { + this.maeTag = new TagTrkpt(maeE.trkpt); + } + } + + /** + * 緯度・経度と時間差から速度(km/h)を求める + * + */ + public void complementationSpeed() { + if (imaTag.speedStr != null) { + try { + Double.parseDouble(imaTag.speedStr); + } + catch (NumberFormatException e) { + // 数字以外ならエレメントを削除する + imaTag.removeElement("speed"); + imaTag.speedStr = null; + } + } + + if (imaTag.speedStr == null) { + double d = GeoDistance.calcDistHubeny(imaTag.lat, imaTag.lon, maeTag.lat, maeTag.lon); + String str = Double.toString((d * 3600) / (imaTag.time.getTime() - maeTag.time.getTime())); + int iDot = str.indexOf('.'); + if (iDot > 0) { + str = str.substring(0, iDot+2); + } + imaTag.appendElement("speed", str); + imaTag.speedStr = str; + } + } + + /** + * 経度(longitude)と経度から進行方向を求める + * @throws ParseException + */ + public void complementationMagvar() throws ParseException { + if (imaTag.magvarStr != null) { + try { + Double.parseDouble(imaTag.magvarStr); + } + catch (NumberFormatException e) { + // 数字以外ならエレメントを削除する + imaTag.removeElement("magvar"); + imaTag.magvarStr = null; + } + } + + if (imaTag.magvarStr == null) { + Double r = Math.cos(Math.toRadians((imaTag.lat + maeTag.lat) / 2)) * R; + Double x = Math.toRadians(imaTag.lon - maeTag.lon) * r; + Double y = Math.toRadians(imaTag.lat - maeTag.lat) * R; + double rad = Math.toDegrees(Math.atan2(y, x)); + + if (y >= 0) { + if (x >= 0) { + rad = 0 - (rad - 90); + } + else { + rad = 360 - (rad - 90); + } + } + else { + if (x >= 0) { + rad = 90 - rad; + } + else { + rad = 90 - rad; + } + } + + String str = Double.toString(rad); + int iDot = str.indexOf('.'); + if (iDot > 0) { + str = str.substring(0, iDot); + } + imaTag.appendElement("magvar", str); + imaTag.magvarStr = str; + } + } +} diff --git a/src/main/java/osm/jp/gpx/ElementMapTRKPT.java b/src/main/java/osm/jp/gpx/ElementMapTRKPT.java new file mode 100644 index 0000000..7d22a8e --- /dev/null +++ b/src/main/java/osm/jp/gpx/ElementMapTRKPT.java @@ -0,0 +1,151 @@ +package osm.jp.gpx; + +import java.text.ParseException; +import java.util.Date; +import java.util.TreeMap; + +import org.w3c.dom.DOMException; + +@SuppressWarnings("serial") +public class ElementMapTRKPT extends TreeMap { + public static final long DIFF_MAE_TIME = 3000L; // before 3 secound + + public ElementMapTRKPT() { + super(new TimeComparator()); + } + + /** + * 拡張put value:ElementをputするとElement内のtimeを読み取ってkeyとしてthis.put(key,value)する。 + * @param tag + * @return + * @throws java.text.ParseException + * @code{ + * + * 614.90 + * + * 0.5 + * + * } + * @return keyとして登録したtime:Date + * @throws ParseException + * @throws DOMException + */ + public Date put(TagTrkpt tag) throws DOMException, ParseException { + this.put(tag.time, tag); + return tag.time; + } + + /** + * 指定時刻(jptime)のTRKPTエレメントを取り出す。 + * + * @param jptime 指定する日時 + * @return エレメントTRKPT。指定時刻に対応するノードがないときはnullを返す。 + * @throws ParseException + */ + public TagTrkpt getValue(Date jptime) throws ParseException { + TagTrkpt imaE = getTrkpt(jptime); + if (imaE != null) { + TagTrkpt maeE = getMaeTrkpt(imaE.time); + if (maeE != null) { + Complementation comp = new Complementation(imaE, maeE); + + // がなければ、 + // 直前の位置と、現在地から進行方向を求める + // 経度(longitude)と経度から進行方向を求める + if (Complementation.param_GpxOverwriteMagvar) { + comp.complementationMagvar(); + } + + // 緯度・経度と時間差から速度(km/h)を求める + if (Complementation.param_GpxOutputSpeed) { + comp.complementationSpeed(); + } + //return (TagTrkpt)(comp.imaTag.trkpt.cloneNode(true)); + return (TagTrkpt)(comp.imaTag); + } + return imaE; + } + return null; + } + + /** + * [map]から指定した時刻のエレメントを取り出す。 + * 取り出すエレメントは、指定した時刻と同一時刻、もしくは、直近・直前の時刻のエレメントとする。 + * 指定した時刻以前のエレメントが存在しない場合は null を返す。 + * 指定した時刻と直近・直前のエレメントの時刻との乖離が プロパティ[OVER_TIME_LIMIT=3000(ミリ秒)]より大きい場合には null を返す。 + * + * @param jptime + * @return エレメント。対象のエレメントが存在しなかった場合には null。 + * @throws ParseException + */ + private TagTrkpt getTrkpt(Date jptime) throws ParseException { + Date keyTime = null; + for (Date key : this.keySet()) { + int flag = jptime.compareTo(key); + if (flag < 0) { + if (keyTime != null) { + return this.get(keyTime); + } + return null; + } + else if (flag == 0) { + return this.get(key); + } + else if (flag > 0) { + keyTime = new Date(key.getTime()); + } + } + if (keyTime != null) { + if (Math.abs(keyTime.getTime() - jptime.getTime()) <= OVER_TIME_LIMIT) { + return this.get(keyTime); + } + } + return null; + } + + /** + * ロガーの最終取得時刻を超えた場合、どこまでを有効とするかを設定する。 + * この設定がないと、最終取得時刻を超えたものは全て有効になってしまう。 + * OVER_TIME_LIMITは、GPSロガーの位置取得間隔()よりも長くする必要がある。長すぎても良くない。 + */ + public static long OVER_TIME_LIMIT = 3000; // ミリ秒(msec) + + private TagTrkpt getMaeTrkpt(Date time) throws ParseException { + Date maeTime = null; + for (Date key : this.keySet()) { + int flag = time.compareTo(key); + if (flag > 0) { + maeTime = new Date(key.getTime()); + } + else if (flag == 0) { + if (maeTime == null) { + return null; + } + return this.get(maeTime); + } + else { + // time は key より古い + if (maeTime == null) { + return null; + } + if (Math.abs(maeTime.getTime() - time.getTime()) > OVER_TIME_LIMIT) { + return null; + } + return this.get(maeTime); + } + } + return null; + } + + public void printinfo() { + Date firstTime = null; + Date lastTime = null; + for (Date key : this.keySet()) { + if (firstTime == null) { + firstTime = new Date(key.getTime()); + } + lastTime = new Date(key.getTime()); + } + System.out.println(String.format("| |%20s|%20s|", ImportPicture.toUTCString(firstTime), ImportPicture.toUTCString(lastTime))); + } +} diff --git a/src/main/java/osm/jp/gpx/ElementMapTRKSEG.java b/src/main/java/osm/jp/gpx/ElementMapTRKSEG.java new file mode 100644 index 0000000..ca9dbc1 --- /dev/null +++ b/src/main/java/osm/jp/gpx/ElementMapTRKSEG.java @@ -0,0 +1,139 @@ +package osm.jp.gpx; + +import java.io.File; +import java.io.IOException; +import java.text.ParseException; +import java.util.Date; +import java.util.TreeMap; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.*; +import org.xml.sax.SAXException; + +@SuppressWarnings("serial") +public class ElementMapTRKSEG extends TreeMap { + /** + * TESTing + * @param argv + * @throws ParseException + * @throws ParserConfigurationException + * @throws IOException + * @throws SAXException + * @throws DOMException + */ + public static void main(String[] argv) throws DOMException, SAXException, IOException, ParserConfigurationException, ParseException { + ElementMapTRKSEG mapTRKSEG = null; + mapTRKSEG = new ElementMapTRKSEG(); + mapTRKSEG.parse(new File("testdata/cameradata/separate.gpx")); + mapTRKSEG.printinfo(); + } + + public ElementMapTRKSEG() { + super(new TimeComparator()); + } + + /** + * GPXファイルをパースする + * @param gpxFile + * @code{ + * + * + * + * + * 47.20000076293945 + * + * 0.5 + * + * + * + * + * } + * + * @param gpxFile + * @return Document + * @throws SAXException + * @throws IOException + * @throws ParserConfigurationException + * @throws DOMException + * @throws ParseException + */ + public Document parse(File gpxFile) throws SAXException, IOException, ParserConfigurationException, DOMException, ParseException { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + factory.setIgnoringElementContentWhitespace(true); + factory.setIgnoringComments(true); + factory.setValidating(true); + + Node gpx = builder.parse(gpxFile).getFirstChild(); + Document document = gpx.getOwnerDocument(); + NodeList nodes = gpx.getChildNodes(); + for (int i=0; i < nodes.getLength(); i++) { + Node node2 = nodes.item(i); + if (node2.getNodeName().equals("trk")) { + Element trk = (Element) node2; + + NodeList nodes1 = trk.getChildNodes(); + for (int i1=0; i1 < nodes1.getLength(); i1++) { + Node nodeTRKSEG = nodes1.item(i1); + if (nodeTRKSEG.getNodeName().equals("trkseg")) { + this.put(nodeTRKSEG); + } + } + } + } + return document; + } + + /** + * @code{ + * 拡張put value:NodeをputするとNode内のNodeを put(key,value)する。 + * } + * @param nodeTRKSEG + * @throws ParseException + * @throws DOMException + */ + public void put(Node nodeTRKSEG) throws DOMException, ParseException { + if (nodeTRKSEG.getNodeName().equals("trkseg")) { + NodeList nodes2 = nodeTRKSEG.getChildNodes(); + + ElementMapTRKPT mapTRKPT = new ElementMapTRKPT(); + for (int i2 = 0; i2 < nodes2.getLength(); i2++) { + Node nodeTRKPT = nodes2.item(i2); + if (nodeTRKPT.getNodeName().equals("trkpt")) { + if (ImportPicture.param_GpxNoFirstNode && (i2 == 0)) { + continue; + } + mapTRKPT.put(new TagTrkpt((Element)nodeTRKPT)); + } + } + this.put(mapTRKPT); + } + } + + /** + * 拡張put value:ElementMapTRKPTをputするとElementMapTRKPT内の最初のエントリのtimeを読み取ってkeyとしてthis.put(key,value)する。 + * @param value + * @throws DOMException + */ + public void put(ElementMapTRKPT value) { + for (Date key : value.keySet()) { + this.put(key, value); + return; + } + } + + public void printinfo() { + System.out.println(" +--------------------+--------------------|"); + System.out.println(" GPS logging time | First Time | Last Time |"); + System.out.println("|--------------------------------+--------------------+--------------------|"); + for (java.util.Map.Entry map : this.entrySet()) { + ElementMapTRKPT mapTRKPT = map.getValue(); + mapTRKPT.printinfo(); + } + System.out.println("|--------------------------------+--------------------+--------------------|"); + System.out.println(); + } +} diff --git a/src/main/java/osm/jp/gpx/GeoDistance.java b/src/main/java/osm/jp/gpx/GeoDistance.java new file mode 100644 index 0000000..894c67c --- /dev/null +++ b/src/main/java/osm/jp/gpx/GeoDistance.java @@ -0,0 +1,71 @@ +package osm.jp.gpx; + +/** + * The MIT License (MIT) + * Copyright(C) 2007-2012 やまだらけ + * http://yamadarake.jp/trdi/report000001.html + * 「Cords.java」を改変 + * 2016-10-03 + * + * @author やまだらけ yama_darake@yahoo.co.jp + * + */ +public class GeoDistance { + + public static final double GRS80_A = 6378137.000; // 赤道半径(m) + public static final double GRS80_E2 = 0.00669438002301188; + public static final double GRS80_MNUM = 6335439.32708317; // + + public static final double WGS84_A = 6378137.000; + public static final double WGS84_E2 = 0.00669437999019758; + public static final double WGS84_MNUM = 6335439.32729246; + + /** + * 角度(180度)をラジアン(2π)に変換する + * @param deg + * @return + */ + public static double deg2rad(double deg){ + return deg * Math.PI / 180.0; + } + + /** + * 距離(m)を返す + * @param lat1 + * @param lng1 + * @param lat2 + * @param lng2 + * @return + */ + public static double calcDistHubeny(double lat1, double lng1, + double lat2, double lng2){ + double my = deg2rad((lat1 + lat2) / 2.0); // 平均緯度 + double dy = deg2rad(lat1 - lat2); // 2点間の緯度 + double dx = deg2rad(lng1 - lng2); // 2点間の経度 + + double sin = Math.sin(my); + double w = Math.sqrt(1.0 - GRS80_E2 * sin * sin); + double m = GRS80_MNUM / (w * w * w); + double n = GRS80_A / w; + + double dym = dy * m; + double dxncos = dx * n * Math.cos(my); + + return Math.sqrt(dym * dym + dxncos * dxncos); + } + + + public static void main(String[] args){ + System.out.println("Coords Test Program"); + double lat1, lng1, lat2, lng2; + + lat1 = Double.parseDouble(args[0]); + lng1 = Double.parseDouble(args[1]); + lat2 = Double.parseDouble(args[2]); + lng2 = Double.parseDouble(args[3]); + + double d = calcDistHubeny(lat1, lng1, lat2, lng2); + + System.out.println("Distance = " + d + " m"); + } +} \ No newline at end of file diff --git a/src/main/java/osm/jp/gpx/GpxFile.java b/src/main/java/osm/jp/gpx/GpxFile.java new file mode 100644 index 0000000..bc7c306 --- /dev/null +++ b/src/main/java/osm/jp/gpx/GpxFile.java @@ -0,0 +1,197 @@ +package osm.jp.gpx; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.ParseException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +@SuppressWarnings("serial") +public class GpxFile extends File { + Node gpx = null; + ElementMapTRKSEG mapTRKSEG = null; + Document document; + + @SuppressWarnings("LeakingThisInConstructor") + public GpxFile(File file) throws ParserConfigurationException, DOMException, SAXException, IOException, ParseException { + super(file.getParentFile(), file.getName()); + + DocumentBuilderFactory factory = null; + DocumentBuilder builder = null; + + factory = DocumentBuilderFactory.newInstance(); + builder = factory.newDocumentBuilder(); + factory.setIgnoringElementContentWhitespace(true); + factory.setIgnoringComments(true); + factory.setValidating(true); + + // GPXファイルをパースする + mapTRKSEG = new ElementMapTRKSEG(); + document = mapTRKSEG.parse(this); + + // パースされた mapTRKSEG の中身を出力する + mapTRKSEG.printinfo(); + + // GPX file --> Node root + gpx = builder.parse(this).getFirstChild(); + } + + /** + * GPX 変換出力 + * @param outDir + * @throws FileNotFoundException + * @throws TransformerException + */ + public void output(File outDir) throws FileNotFoundException, TransformerException { + String fileName = this.getName(); + String iStr = fileName.substring(0, fileName.length() - 4); + File outputFile = new File(outDir, iStr +"_.gpx"); + System.out.println(this.getAbsolutePath() + " => "+ outputFile.getAbsolutePath()); + + outputFile.getParentFile().mkdirs(); + DOMSource source = new DOMSource(this.gpx); + FileOutputStream os = new FileOutputStream(outputFile); + StreamResult result = new StreamResult(os); + TransformerFactory transFactory = TransformerFactory.newInstance(); + Transformer transformer = transFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.transform(source, result); + + os = new FileOutputStream(outputFile); + result = new StreamResult(os); + transformer.transform(source, result); + } + + /** + * <wpt lat="35.25714922" lon="139.15490497"> + * <ele>62.099998474121094</ele> + * <time>2012-06-11T00:44:38Z</time> + * <name></name> + * <link href="2012-06-11_09-44-38.jpg"> + * <text>2012-06-11_09-44-38.jpg</text> + * </link> + * </wpt> + * + * <trkpt lat="35.32123832" lon="139.56965631"> + * <ele>47.20000076293945</ele> + * <time>2012-06-15T03:00:29Z</time> + * </trkpt> + * + * @param imgDir + * @return + * @param iFile + * @param timestamp + * @param trkpt + */ + public Element createWptTag(File iFile, File imgDir, long timestamp, Element trkpt) { + Element wpt = document.createElement("wpt"); + + NamedNodeMap nodeMap = trkpt.getAttributes(); + if (null != nodeMap) { + for (int j=0; j < nodeMap.getLength(); j++ ) { + switch (nodeMap.item(j).getNodeName()) { + case "lat": + String lat = nodeMap.item(j).getNodeValue(); + wpt.setAttribute("lat", lat); + break; + case "lon": + String lon = nodeMap.item(j).getNodeValue(); + wpt.setAttribute("lon", lon); + break; + } + } + } + + NodeList nodes1 = trkpt.getChildNodes(); + for (int i1=0; i1 < nodes1.getLength(); i1++) { + Node node1 = nodes1.item(i1); + NodeList nodes2 = node1.getChildNodes(); + switch (node1.getNodeName()) { + case "ele": + for (int i2=0; i2 < nodes2.getLength(); i2++) { + Node node2 = nodes2.item(i2); + if (node2 != null) { + if (node2.getNodeType() == Node.TEXT_NODE) { + String eleStr = node2.getNodeValue(); + Element eleE = document.createElement("ele"); + eleE.setTextContent(eleStr); + wpt.appendChild(eleE); + } + } + } + break; + case "time": + for (int i2=0; i2 < nodes2.getLength(); i2++) { + Node node2 = nodes2.item(i2); + if (node2 != null) { + if (node2.getNodeType() == Node.TEXT_NODE) { + String timeStr = node2.getNodeValue(); + Element timeE = document.createElement("time"); + timeE.setTextContent(timeStr); + wpt.appendChild(timeE); + } + } + } + break; + case "magvar": + for (int i2=0; i2 < nodes2.getLength(); i2++) { + Node node2 = nodes2.item(i2); + if (node2 != null) { + if (node2.getNodeType() == Node.TEXT_NODE) { + String magvarStr = node2.getNodeValue(); + Element magvarE = document.createElement("magvar"); + magvarE.setTextContent(magvarStr); + wpt.appendChild(magvarE); + } + } + } + break; + case "speed": + for (int i2=0; i2 < nodes2.getLength(); i2++) { + Node node2 = nodes2.item(i2); + if (node2 != null) { + if (node2.getNodeType() == Node.TEXT_NODE) { + String speedStr = node2.getNodeValue(); + Element speedE = document.createElement("speed"); + speedE.setTextContent(speedStr); + wpt.appendChild(speedE); + } + } + } + break; + } + } + + Element name = document.createElement("name"); + name.appendChild(document.createCDATASection("写真")); + wpt.appendChild(name); + + Element link = document.createElement("link"); + link.setAttribute("href", ImportPicture.getShortPathName(imgDir, iFile)); + Element text = document.createElement("text"); + text.setTextContent(iFile.getName()); + link.appendChild(text); + wpt.appendChild(link); + + return wpt; + } +} diff --git a/src/main/java/osm/jp/gpx/ImportPicture.java b/src/main/java/osm/jp/gpx/ImportPicture.java new file mode 100644 index 0000000..0f66415 --- /dev/null +++ b/src/main/java/osm/jp/gpx/ImportPicture.java @@ -0,0 +1,726 @@ +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 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() { + @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{ + + 62.099998474121094 + + 0.75 + + + + 2012-06-11_09-44-38.jpg + + 9 + + * } + */ + @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におけるをtrkptに追加する + String eleStr = "-"; + String magvarStr = "-"; + String speedStr = "-"; + TagTrkpt trkptT = null; + + for (Map.Entry 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 { + @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$"); + } + } +} \ No newline at end of file diff --git a/src/main/java/osm/jp/gpx/Restamp.java b/src/main/java/osm/jp/gpx/Restamp.java new file mode 100644 index 0000000..96da1ee --- /dev/null +++ b/src/main/java/osm/jp/gpx/Restamp.java @@ -0,0 +1,266 @@ +package osm.jp.gpx; + +import java.io.*; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Comparator; +import java.util.Date; +import java.util.ResourceBundle; +import java.util.logging.LogManager; +import java.util.logging.Logger; +import org.apache.commons.imaging.ImageReadException; + +/** + * 動画から一定間隔で切り出したIMAGEファイルの更新日時を書き換える + * + * @author yuu + */ +public class Restamp extends Thread { + static public final String TIME_PATTERN = "yyyy-MM-dd HH:mm:ss z"; + + /** + * 実行中に発生した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); + } + } + } + + /** + * メイン + * 動画から一定間隔で切り出したIMAGEのファイル更新日時を書き換える + * + * ・画像ファイルの更新日付を書き換えます。(Exi情報は無視します) + * ※ 指定されたディレクトリ内のすべての'*.jpg'ファイルを処理の対象とします + * ・画像は連番形式(名前順に並べられること)の名称となっていること + * + * パラメータ + * ・対象のフォルダ(ディレクトリ内のすべての'*.jpg'ファイルを処理の対象とします) + * ・基準となる画像 + * ・基準画像の正しい日時 + * ・画像ファイルの間隔(秒) + * + * 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] + * + * 1. 予め、動画から画像を切り出す + *   ソースファイル(mp4ファイル); 「-i 20160427_104154.mp4」 + * 出力先: 「-f image2 img/%06d.jpg」 imgフォルダに6桁の連番ファイルを差出力する + *   切り出し開始秒数→ 「-ss 0」 (ファイルの0秒から切り出し開始) + *   切り出し間隔; 「-r 30」 (1秒間隔=30fps間隔) + * ``` + * $ cd /home/yuu/Desktop/OSM/20180325_横浜新道 + * $ ffmpeg -ss 0 -i 20160427_104154.mp4 -f image2 -r 15 img/%06d.jpg + * ``` + * + * 2. ファイルの更新日付を書き換える + * ``` + * $ cd /home/yuu/Desktop/workspace/AdjustTime/importPicture/dist + * $ java -cp .:AdjustTime2.jar osm.jp.gpx.Restamp /home/yuu/Desktop/OSM/20180325_横浜新道/img 000033.jpg 2018-03-25_12:20:32 003600.jpg 2018-03-25_13:20:09 + * ``` + * + * @param argv + * argv[0] = 画像ファイルが格納されているディレクトリ --> imgDir + * argv[1] = 時刻補正の基準とする画像ファイル --> baseFile + * argv[2] = 基準画像ファイルの精確な撮影日時 "yyyy-MM-dd HH:mm:ss z" --> baseTime + * argv[3] = 時刻補正の基準とする画像ファイル --> baseFile + * argv[4] = 基準画像ファイルの精確な撮影日時 "yyyy-MM-dd HH:mm:ss z" --> baseTime + * + * @throws IOException + * @throws ImageReadException + */ + public static void main(String[] argv) throws Exception + { + if (argv.length < 5) { + System.out.println("java Restamp "); + return; + } + + File imgDir = new File(argv[0]); + if (!imgDir.exists()) { + // "[error] が存在しません。" + System.out.println(i18n.getString("msg.200")); + return; + } + if (!imgDir.isDirectory()) { + // "[error] がフォルダじゃない" + System.out.println(i18n.getString("msg.210")); + return; + } + + File baseFile1 = new File(imgDir, argv[1]); + if (!baseFile1.exists()) { + // "[error] が存在しません。" + System.out.println(i18n.getString("msg.220")); + return; + } + if (!baseFile1.isFile()) { + // "[error] がファイルじゃない" + System.out.println(i18n.getString("msg.230")); + return; + } + + DateFormat df1 = new SimpleDateFormat(TIME_PATTERN); + Date baseTime1 = df1.parse(argv[2]); + + File baseFile2 = new File(imgDir, argv[3]); + if (!baseFile2.exists()) { + // "[error] が存在しません。" + System.out.println(i18n.getString("msg.240")); + return; + } + if (!baseFile2.isFile()) { + // "[error] がファイルじゃない" + System.out.println(i18n.getString("msg.250")); + return; + } + + Date baseTime2 = df1.parse(argv[4]); + + Restamp obj = new Restamp(); + obj.setUp(imgDir, baseFile1, baseTime1, baseFile2, baseTime2); + } + + public File imgDir; + public Date baseTime1; + public Date baseTime2; + public int bCount1 = 0; + public int bCount2 = 0; + public long span = 0; + public ArrayList jpgFiles = new ArrayList<>(); + public static ResourceBundle i18n = ResourceBundle.getBundle("i18n"); + + @SuppressWarnings("Convert2Lambda") + public void setUp(File imgDir, File baseFile1, Date baseTime1, File baseFile2, Date baseTime2) throws Exception { + // 指定されたディレクトリ内のGPXファイルすべてを対象とする + File[] files = imgDir.listFiles(); + java.util.Arrays.sort(files, new java.util.Comparator() { + @Override + public int compare(File file1, File file2){ + return file1.getName().compareTo(file2.getName()); + } + }); + bCount1 = 0; + bCount2 = 0; + boolean base1 = false; + boolean base2 = false; + for (File file : files) { + if (file.isFile()) { + String filename = file.getName().toUpperCase(); + if (filename.toUpperCase().endsWith(".JPG")) { + this.jpgFiles.add(file); + bCount1 += (base1 ? 0 : 1); + bCount2 += (base2 ? 0 : 1); + if (file.getName().equals(baseFile1.getName())) { + base1 = true; + } + if (file.getName().equals(baseFile2.getName())) { + base2 = true; + } + } + } + } + + try { + DateFormat df2 = new SimpleDateFormat(TIME_PATTERN); + + // imgDir内の画像ファイルを処理する + @SuppressWarnings("LocalVariableHidesMemberVariable") + long span = baseTime2.getTime() - baseTime1.getTime(); + span = span / (bCount2 - bCount1); + int i = 0; + System.out.println("-------------------------------"); + System.out.println("Update last modified date time."); + for (File jpgFile : this.jpgFiles) { + long deltaMsec = (i - (bCount1 -1)) * span; + i++; + Calendar cal = Calendar.getInstance(); + cal.setTime(baseTime1); + cal.add(Calendar.MILLISECOND, (int) deltaMsec); + + System.out.println(String.format("\t%s --> %s", df2.format(cal.getTime()), jpgFile.getName())); + jpgFile.setLastModified(cal.getTimeInMillis()); + } + System.out.println("-------------------------------"); + } + catch(Exception e) { + this.ex = new Exception(e); + } + } + + /** + * 対象は '*.JPG' のみ対象とする + * @return + * @param name + */ + public static boolean checkFile(String name) { + return ((name != null) && name.toUpperCase().endsWith(".JPG")); + } + + /** + * ファイル名の順序に並び替えるためのソートクラス + * + * @author hayashi + */ + static class FileSort implements Comparator { + @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$"); + } + } +} \ No newline at end of file diff --git a/src/main/java/osm/jp/gpx/TagTrkpt.java b/src/main/java/osm/jp/gpx/TagTrkpt.java new file mode 100644 index 0000000..171827f --- /dev/null +++ b/src/main/java/osm/jp/gpx/TagTrkpt.java @@ -0,0 +1,121 @@ +package osm.jp.gpx; + +import java.text.ParseException; +import java.util.Date; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * @code{ + * <trkpt lat="35.32123832" lon="139.56965631"> + * 47.20000076293945 + * + * + * + * </trkpt> + * } + * + */ +public class TagTrkpt { + public Element trkpt = null; + public Double lat = null; + public Double lon = null; + public String eleStr = null; + public Date time = null; + public String magvarStr = null; + public String speedStr = null; + + public TagTrkpt(Element trkpt) { + this.trkpt = (Element) trkpt.cloneNode(true); + + NamedNodeMap nodeMap = trkpt.getAttributes(); + for (int j=0; j < nodeMap.getLength(); j++ ) { + switch (nodeMap.item(j).getNodeName()) { + case "lat": + String latStr = nodeMap.item(j).getNodeValue(); + this.lat = new Double(latStr); + break; + case "lon": + String lonStr = nodeMap.item(j).getNodeValue(); + this.lon = new Double(lonStr); + break; + } + } + + NodeList nodes1 = trkpt.getChildNodes(); + for (int i1=0; i1 < nodes1.getLength(); i1++) { + Node node1 = nodes1.item(i1); + NodeList nodes2 = node1.getChildNodes(); + switch (node1.getNodeName()) { + case "ele": + for (int i2=0; i2 < nodes2.getLength(); i2++) { + Node node2 = nodes2.item(i2); + if (node2 != null) { + if (node2.getNodeType() == Node.TEXT_NODE) { + this.eleStr = node2.getNodeValue(); + } + } + } + break; + case "time": + for (int i2=0; i2 < nodes2.getLength(); i2++) { + Node node2 = nodes2.item(i2); + if (node2 != null) { + if (node2.getNodeType() == Node.TEXT_NODE) { + try { + this.time = ImportPicture.toUTCDate(node2.getNodeValue()); + } catch (ParseException e) { + this.time = null; + } + } + } + } + break; + case "magvar": + for (int i2=0; i2 < nodes2.getLength(); i2++) { + Node node2 = nodes2.item(i2); + if (node2 != null) { + if (node2.getNodeType() == Node.TEXT_NODE) { + this.magvarStr = node2.getNodeValue(); + } + } + } + break; + case "speed": + for (int i2=0; i2 < nodes2.getLength(); i2++) { + Node node2 = nodes2.item(i2); + if (node2 != null) { + if (node2.getNodeType() == Node.TEXT_NODE) { + this.speedStr = node2.getNodeValue(); + } + } + } + break; + } + } + } + + public void removeElement(String eleName) { + Node child; + for (child = trkpt.getFirstChild(); child != null; child = child.getNextSibling()) { + NodeList nodeList = child.getChildNodes(); + for(int i = 0; i < nodeList.getLength(); i++) { + Node grandChild = child.getChildNodes().item(i); + if (grandChild.getNodeName().equals(eleName)) { + child.removeChild(grandChild); + } + } + } + } + + public void appendElement(String eleName, String valueStr) { + Document doc = trkpt.getOwnerDocument(); + Element newElement = doc.createElement(eleName); + newElement.setTextContent(valueStr); + trkpt.appendChild(newElement); + } +} diff --git a/src/main/java/osm/jp/gpx/TimeComparator.java b/src/main/java/osm/jp/gpx/TimeComparator.java new file mode 100644 index 0000000..484516f --- /dev/null +++ b/src/main/java/osm/jp/gpx/TimeComparator.java @@ -0,0 +1,21 @@ +package osm.jp.gpx; + +import java.util.Comparator; +import java.util.Date; + +/** + * java.util.Date型をコレクションのKEYにした時に、時間順に並べ替える + * + */ +public class TimeComparator implements Comparator +{ + /** + * 日付順にソート + * @param arg0 + * @param arg1 + */ + @Override + public int compare(Date arg0, Date arg1) { + return arg0.compareTo(arg1); + } +} diff --git a/src/main/java/osm/jp/gpx/YuuLogFormatter.java b/src/main/java/osm/jp/gpx/YuuLogFormatter.java new file mode 100644 index 0000000..99ffc0b --- /dev/null +++ b/src/main/java/osm/jp/gpx/YuuLogFormatter.java @@ -0,0 +1,48 @@ +package osm.jp.gpx; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +/** + * シンプルなサンプルログフォーマッタ + */ +public class YuuLogFormatter extends Formatter { + private final SimpleDateFormat sdFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + + @Override + public String format(final LogRecord argLogRecord) { + final StringBuffer buf = new StringBuffer(); + + buf.append(sdFormat.format(new Date(argLogRecord.getMillis()))).append(" "); + + if (argLogRecord.getLevel() == Level.FINEST) { + buf.append("[FINEST]"); + } + else if (argLogRecord.getLevel() == Level.FINER) { + buf.append("[FINER]"); + } + else if (argLogRecord.getLevel() == Level.FINE) { + buf.append("[FINE]"); + } + else if (argLogRecord.getLevel() == Level.CONFIG) { + buf.append("[CONFIG]"); + } + else if (argLogRecord.getLevel() == Level.INFO) { + buf.append("[INFO]"); + } + else if (argLogRecord.getLevel() == Level.WARNING) { + buf.append("[WARN]"); + } + else if (argLogRecord.getLevel() == Level.SEVERE) { + buf.append("[SEVERE]"); + } + else { + buf.append(Integer.toString(argLogRecord.getLevel().intValue())).append(" "); + } + buf.append(" ").append(argLogRecord.getMessage()).append("\n"); + return buf.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/AboutDialog.java b/src/main/java/osm/jp/gpx/matchtime/gui/AboutDialog.java new file mode 100644 index 0000000..07af69c --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/AboutDialog.java @@ -0,0 +1,130 @@ +package osm.jp.gpx.matchtime.gui; +import java.awt.*; + +@SuppressWarnings("serial") +public class AboutDialog extends Dialog +{ + //{{DECLARE_CONTROLS + java.awt.Label label1; + java.awt.Button okButton; + java.awt.Label label2; + //}} + + // Used for addNotify redundency check. + boolean fComponentsAdjusted = false; + + class SymWindow extends java.awt.event.WindowAdapter + { + @Override + public void windowClosing(java.awt.event.WindowEvent event) { + Object object = event.getSource(); + if (object == AboutDialog.this) { + AboutDialog_WindowClosing(event); + } + } + } + + class SymAction implements java.awt.event.ActionListener + { + @Override + public void actionPerformed(java.awt.event.ActionEvent event) { + Object object = event.getSource(); + if (object == okButton) { + okButton_Clicked(event); + } + } + } + + @SuppressWarnings("OverridableMethodCallInConstructor") + public AboutDialog(Frame parent, boolean modal) { + super(parent, modal); + + // This code is automatically generated by Visual Cafe when you add + // components to the visual environment. It instantiates and initializes + // the components. To modify the code, only use code syntax that matches + // what Visual Cafe can generate, or Visual Cafe may be unable to back + // parse your Java file into its visual environment. + + + //{{INIT_CONTROLS + setLayout(null); + setVisible(false); + setSize(360,114); + label1 = new java.awt.Label(AdjustTerra.PROGRAM_NAME +" Version "+ AdjustTerra.PROGRAM_VARSION +" ("+ AdjustTerra.PROGRAM_UPDATE +")", Label.CENTER); + label1.setBounds(10,10,340,20); + add(label1); + okButton = new java.awt.Button(); + okButton.setLabel("OK"); + okButton.setBounds(145,65,66,27); + add(okButton); + label2 = new java.awt.Label("Copyright(C) 2014,2018,2020 yuuhayashi \n The MIT License (MIT).",Label.RIGHT); + label2.setBounds(10,40,340,20); + add(label2); + setTitle("About... "+ AdjustTerra.PROGRAM_NAME); + //}} + + //{{REGISTER_LISTENERS + SymWindow aSymWindow = new SymWindow(); + this.addWindowListener(aSymWindow); + SymAction lSymAction = new SymAction(); + okButton.addActionListener(lSymAction); + //}} + } + + @SuppressWarnings("OverridableMethodCallInConstructor") + public AboutDialog(Frame parent, String title, boolean modal) { + this(parent, modal); + setTitle(title); + } + + @Override + public void addNotify() { + // Record the size of the window prior to calling parents addNotify. + + super.addNotify(); + + // Only do this once. + if (fComponentsAdjusted) { + return; + } + + // Adjust components according to the insets + setSize(getInsets().left + getInsets().right + getSize().width, getInsets().top + getInsets().bottom + getSize().height); + Component components[] = getComponents(); + for (Component component : components) { + Point p = component.getLocation(); + p.translate(getInsets().left, getInsets().top); + component.setLocation(p); + } + + // Used for addNotify check. + fComponentsAdjusted = true; + } + + /** + * Shows or hides the component depending on the boolean flag b. + * @param b if true, show the component; otherwise, hide the component. + * @see java.awt.Component#isVisible + */ + @Override + public void setVisible(boolean b) { + if(b) { + Rectangle bounds = getParent().getBounds(); + Rectangle abounds = getBounds(); + setLocation(bounds.x + (bounds.width - abounds.width)/ 2, + bounds.y + (bounds.height - abounds.height)/2); + } + super.setVisible(b); + } + + void AboutDialog_WindowClosing(java.awt.event.WindowEvent event) { + dispose(); + } + + void okButton_Clicked(java.awt.event.ActionEvent event) { + //{{CONNECTION + // Clicked from okButton Hide the Dialog + dispose(); + //}} + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/AdjustTerra.java b/src/main/java/osm/jp/gpx/matchtime/gui/AdjustTerra.java new file mode 100644 index 0000000..a720a9e --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/AdjustTerra.java @@ -0,0 +1,424 @@ +package osm.jp.gpx.matchtime.gui; + +import osm.jp.gpx.matchtime.gui.restamp.RestampDialog; +import java.awt.*; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ResourceBundle; +import java.util.TimeZone; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import osm.jp.gpx.*; +import osm.jp.gpx.matchtime.gui.restamp.CardImageFile; +import osm.jp.gpx.matchtime.gui.restamp.CardSourceFolder; + +/** + * 本プログラムのメインクラス + */ +@SuppressWarnings("serial") +public class AdjustTerra extends JFrame +{ + public static final String PROGRAM_NAME = "AdjustTerra for JOSM"; + public static final String PROGRAM_VARSION = "5.2"; + public static final String PROGRAM_UPDATE = "2020/02/02"; + + AppParameters params; + public static SimpleDateFormat dfjp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); + + // Used for addNotify check. + boolean fComponentsAdjusted = false; + public static ResourceBundle i18n = ResourceBundle.getBundle("i18n"); + + //{{DECLARE_CONTROLS + JTabbedPane cardPanel; // ウィザード形式パネル(タブ型) + Card[] cards; + //}} + + //---入力フィールド---------------------------------------------------- + ParameterPanelFolder arg1_srcFolder; // 対象フォルダ + ParameterPanelImageFile arg2_baseTimeImg; // 開始画像ファイルパス + ParameterPanelTime arg2_basetime; // 開始画像の基準時刻: + ParameterPanelGpx arg3_gpxFile; // GPX file or Folder + ParameterPanelOutput arg4_output; // EXIF & 書き出しフォルダ + + //{{DECLARE_MENUS + java.awt.MenuBar mainMenuBar; + java.awt.Menu menu1; + java.awt.MenuItem miDoNewFileList; + java.awt.MenuItem miDoDirSize; + java.awt.MenuItem miDoReadXML; + java.awt.MenuItem miExit; + java.awt.Menu menu2; + java.awt.MenuItem miRestamp; + java.awt.Menu menu3; + java.awt.MenuItem miAbout; + //}} + + class SymWindow extends java.awt.event.WindowAdapter { + /** + * このFrameが閉じられるときの動作。 + * このパネルが閉じられたら、このアプリケーションも終了させる。 + */ + @Override + public void windowClosing(java.awt.event.WindowEvent event) { + Object object = event.getSource(); + if (object == AdjustTerra.this) { + DbMang_WindowClosing(event); + } + } + } + + class SymAction implements java.awt.event.ActionListener { + @Override + public void actionPerformed(java.awt.event.ActionEvent event) { + Object object = event.getSource(); + if (object == miAbout) { + miAbout_Action(event); + } + else if (object == miRestamp) { + try { + miRestamp_Action(event); + } catch (IOException ex) { + Logger.getLogger(AdjustTerra.class.getName()).log(Level.SEVERE, null, ex); + } + } + else if (object == miExit) { + miExit_Action(event); + } + } + } + + /** + * データベース内のテーブルを一覧で表示するFrame + * @throws IOException + */ + @SuppressWarnings("OverridableMethodCallInConstructor") + public AdjustTerra() throws IOException + { + dfjp.setTimeZone(TimeZone.getTimeZone("JST")); + + // INIT_CONTROLS + Container container = getContentPane(); + container.setLayout(new BorderLayout()); + setSize( + getInsets().left + getInsets().right + 720, + getInsets().top + getInsets().bottom + 480 + ); + setTitle(AdjustTerra.PROGRAM_NAME +" v"+ AdjustTerra.PROGRAM_VARSION); + + //---- CENTER ----- + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + container.add(mainPanel, BorderLayout.CENTER); + + //---- SOUTH ----- + JPanel southPanel = new JPanel(new BorderLayout()); + southPanel.add(Box.createVerticalStrut(10), BorderLayout.SOUTH); + southPanel.add(Box.createVerticalStrut(10), BorderLayout.NORTH); + container.add(southPanel, BorderLayout.SOUTH); + + //---- SPACE ----- + container.add(Box.createVerticalStrut(30), BorderLayout.NORTH); + container.add(Box.createHorizontalStrut(10), BorderLayout.WEST); + container.add(Box.createHorizontalStrut(10), BorderLayout.EAST); + + params = new AppParameters(); + + //--------------------------------------------------------------------- + cardPanel = new JTabbedPane(JTabbedPane.LEFT); + mainPanel.add(cardPanel, BorderLayout.CENTER); + + cards = new Card[4]; + int cardNo = 0; + + //--------------------------------------------------------------------- + // 1.[対象フォルダ]設定パネル + { + arg1_srcFolder = new ParameterPanelFolder( + i18n.getString("label.110") +": ", + params.getProperty(AppParameters.IMG_SOURCE_FOLDER) + ); + arg1_srcFolder.argField + .getDocument() + .addDocumentListener( + new SimpleDocumentListener() { + @Override + public void update(DocumentEvent e) { + toEnable(0, arg1_srcFolder.isEnable()); + } + } + ); + + Card card = new CardSourceFolder(cardPanel, arg1_srcFolder); + cardPanel.addTab(card.getTitle(), card); + cardPanel.setEnabledAt(cardNo, true); + cards[cardNo] = card; + cardNo++; + } + + //--------------------------------------------------------------------- + // 2.[基準時刻画像]設定パネル + // 2a.基準時刻の入力画面 + { + // 基準時刻画像 + arg2_baseTimeImg = new ParameterPanelImageFile( + i18n.getString("label.210") +": ", + null, + arg1_srcFolder + ); + + // 2a. 基準時刻: + arg2_basetime = new ParameterPanelTime( + i18n.getString("label.310"), + null, + arg2_baseTimeImg + ); + arg2_basetime.argField.getDocument().addDocumentListener( + new SimpleDocumentListener() { + @Override + public void update(DocumentEvent e) { + toEnable(1, arg2_basetime.isEnable()); + } + } + ); + + // EXIFの日時を基準にする + arg2_basetime.addExifBase(i18n.getString("label.220"), params); + + // ファイル更新日時を基準にする + arg2_basetime.addFileUpdate(i18n.getString("label.230"), params); + + CardImageFile card = new CardImageFile( + cardPanel, arg2_basetime, (Window)this, + AdjustTerra.i18n.getString("tab.300"), 0, 2); + cardPanel.addTab(card.getTitle(), card); + cardPanel.setEnabledAt(cardNo, false); + cards[cardNo] = card; + cardNo++; + } + + //--------------------------------------------------------------------- + // 3.GPXファイル設定画面 + { + // 3. GPXファイル選択パラメータ + arg3_gpxFile = new ParameterPanelGpx( + i18n.getString("label.410") + ": ", + params.getProperty(AppParameters.GPX_SOURCE_FOLDER) + ); + arg3_gpxFile.argField.getDocument().addDocumentListener( + new SimpleDocumentListener() { + @Override + public void update(DocumentEvent e) { + toEnable(2, arg3_gpxFile.isEnable()); + } + } + ); + + // "セグメント'trkseg'の最初の1ノードは無視する。" + arg3_gpxFile.addNoFirstNode(i18n.getString("label.420"), params); + + // "生成されたGPXファイル(ファイル名が'_.gpx'で終わるもの)も変換の対象にする" + arg3_gpxFile.addGpxReuse(i18n.getString("label.430"), params); + + // 3. GPXファイルを選択 + CardGpxFile card = new CardGpxFile( + cardPanel, arg3_gpxFile, + AdjustTerra.i18n.getString("tab.400"), 1, 3); + cardPanel.addTab(card.getTitle(), card); + cardPanel.setEnabledAt(cardNo, false); + cards[cardNo] = card; + cardNo++; + } + + //--------------------------------------------------------------------- + // 4.EXIF更新設定画面 & 実行画面 + { + // 4. ファイル変換・実行パラメータ + // "出力フォルダ: " + arg4_output = new ParameterPanelOutput( + i18n.getString("label.530") + ": ", + params.getProperty(AppParameters.IMG_OUTPUT_FOLDER) + ); + arg4_output.argField.getDocument().addDocumentListener( + new SimpleDocumentListener() { + @Override + public void update(DocumentEvent e) { + toEnable(3, arg4_output.isEnable()); + } + } + ); + + // "IMGの変換をする" + arg4_output.addCheckChangeImage(i18n.getString("label.510"), params); + + // "GPXファイル時間外のファイルもコピーする" + arg4_output.addCheckOutofGpxTime(i18n.getString("label.520"), params); + + // "EXIFの変換をする" + arg4_output.addCheckOutputExif(i18n.getString("label.540"), params); + + // "ポイントマーカーをGPXファイルに出力する" + arg4_output.addCheckOutputWpt(i18n.getString("label.550"), params); + + // "ソースGPXのを無視する" + arg4_output.addCheckIgnoreMagvar(i18n.getString("label.560"), params); + + // "出力GPXに[SPEED]を上書きする" + arg4_output.addCheckOutputSpeed(i18n.getString("label.570"), params); + + // パネル表示 + CardExifPerform card = new CardExifPerform( + cardPanel, + arg2_basetime, arg3_gpxFile, arg4_output, + AdjustTerra.i18n.getString("tab.500"), + 2, -1 + ); + cardPanel.addTab(card.getTitle(), card); + cardPanel.setEnabledAt(cardNo, false); + cards[cardNo] = card; + } + + //--------------------------------------------------------------------- + // INIT_MENUS + menu1 = new java.awt.Menu("File"); + miExit = new java.awt.MenuItem(i18n.getString("menu.quit")); + miExit.setFont(new Font("Dialog", Font.PLAIN, 12)); + menu1.add(miExit); + + miRestamp = new java.awt.MenuItem(i18n.getString("menu.restamp") + "..."); + miRestamp.setFont(new Font("Dialog", Font.PLAIN, 12)); + + menu2 = new java.awt.Menu(i18n.getString("menu.tools")); + menu2.setFont(new Font("Dialog", Font.PLAIN, 12)); + menu2.add(miRestamp); + + miAbout = new java.awt.MenuItem("About..."); + miAbout.setFont(new Font("Dialog", Font.PLAIN, 12)); + + menu3 = new java.awt.Menu("Help"); + menu3.setFont(new Font("Dialog", Font.PLAIN, 12)); + menu3.add(miAbout); + + mainMenuBar = new java.awt.MenuBar(); + mainMenuBar.setHelpMenu(menu3); + mainMenuBar.add(menu1); + mainMenuBar.add(menu2); + mainMenuBar.add(menu3); + setMenuBar(mainMenuBar); + + //{{REGISTER_LISTENERS + SymWindow aSymWindow = new SymWindow(); + this.addWindowListener(aSymWindow); + SymAction lSymAction = new SymAction(); + miAbout.addActionListener(lSymAction); + miRestamp.addActionListener(lSymAction); + miExit.addActionListener(lSymAction); + arg2_baseTimeImg.openButton.addActionListener(lSymAction); + //}} + } + + /** + * Shows or hides the component depending on the boolean flag b. + * @param b trueのときコンポーネントを表示; その他のとき, componentを隠す. + * @see java.awt.Component#isVisible + */ + @Override + public void setVisible(boolean b) { + if(b) { + setLocation(50, 50); + } + super.setVisible(b); + } + + /** + * このクラスをインスタンスを生成して表示する。 + * コマンドラインの引数はありません。 + * @param args + */ + @SuppressWarnings("UseSpecificCatch") + static public void main(String args[]) { + SwingUtilities.invokeLater(() -> { + try { + createAndShowGUI(); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + private static void createAndShowGUI() throws IOException { + (new AdjustTerra()).setVisible(true); + } + + @Override + public void addNotify() { + // Record the size of the window prior to calling parents addNotify. + Dimension d = getSize(); + + super.addNotify(); + + if (fComponentsAdjusted) + return; + + // Adjust components according to the insets + setSize(getInsets().left + getInsets().right + d.width, getInsets().top + getInsets().bottom + d.height); + Component components[] = getComponents(); + for (Component component : components) { + Point p = component.getLocation(); + p.translate(getInsets().left, getInsets().top); + component.setLocation(p); + } + fComponentsAdjusted = true; + } + + void DbMang_WindowClosing(java.awt.event.WindowEvent event) { + setVisible(false); // hide the Manager + dispose(); // free the system resources + System.exit(0); // close the application + } + + void miAbout_Action(java.awt.event.ActionEvent event) { + // Action from About Create and show as modal + (new AboutDialog(this, true)).setVisible(true); + } + + void miRestamp_Action(java.awt.event.ActionEvent event) throws IOException { + (new RestampDialog(this, false)).setVisible(true); + } + + void miExit_Action(java.awt.event.ActionEvent event) { + // Action from Exit Create and show as modal + //(new hayashi.yuu.tools.gui.QuitDialog(this, true)).setVisible(true); + (new QuitDialog(this, true)).setVisible(true); + } + + void toEnable(final int cardNo, final boolean enable) { + if ((cardNo >= 0) && (cardNo < cards.length)) { + cardPanel.setEnabledAt(cardNo, enable); + if ((cardNo -1) >= 0) { + cards[cardNo -1].nextButton.setEnabled(enable); + } + if ((cardNo +1) < cards.length) { + cardPanel.setEnabledAt(cardNo+1, enable); + cards[cardNo +1].backButton.setEnabled(enable); + cards[cardNo].nextButton.setEnabled(enable); + } + } + } + + //ImageIcon refImage; + + /** Returns an ImageIcon, or null if the path was invalid. + * @param path + * @return */ + public static ImageIcon createImageIcon(String path) { + java.net.URL imgURL = AdjustTerra.class.getResource(path); + if (imgURL != null) { + return new ImageIcon(imgURL); + } else { + System.err.println("Couldn't find file: " + path); + return null; + } + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/Card.java b/src/main/java/osm/jp/gpx/matchtime/gui/Card.java new file mode 100644 index 0000000..ea503f4 --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/Card.java @@ -0,0 +1,123 @@ +package osm.jp.gpx.matchtime.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import static osm.jp.gpx.matchtime.gui.AdjustTerra.i18n; + +public class Card extends JPanel { + JTabbedPane tabbe; + public JPanel mainPanel; + String title; + int backNumber = -1; + int nextNumber = -1; + public JButton nextButton; // [次へ]ボタン + public JButton backButton; // [戻る]ボタン + + public Card(JTabbedPane tabbe, String title, int backNumber, int nextNumber) { + super(); + this.tabbe = tabbe; + this.title = title; + this.backNumber = backNumber; + this.nextNumber = nextNumber; + + // INIT_CONTROLS + this.setLayout(new BorderLayout()); + + //---- CENTER ----- + mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + this.add(mainPanel, BorderLayout.CENTER); + + //---- SOUTH ----- + JPanel buttonPanel = new JPanel(new BorderLayout()); + buttonPanel.add(Box.createVerticalStrut(10), BorderLayout.SOUTH); + buttonPanel.add(Box.createVerticalStrut(10), BorderLayout.NORTH); + this.add(buttonPanel, BorderLayout.SOUTH); + + //{{REGISTER_LISTENERS + SymAction lSymAction = new SymAction(); + if (nextNumber >= 0) { + nextButton = new JButton(i18n.getString("button.next")); + nextButton.setEnabled(false); + buttonPanel.add(nextButton, BorderLayout.EAST); + nextButton.addActionListener(lSymAction); + } + + if (backNumber >= 0) { + backButton = new JButton(i18n.getString("button.previous")); + backButton.setEnabled(false); + buttonPanel.add(backButton, BorderLayout.WEST); + backButton.addActionListener(lSymAction); + } + //}} + } + + public static JPanel packLine(JComponent[] components, JPanel panel) { + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + int max = 0; + for (JComponent component : components) { + panel.add(component); + Dimension size = component.getMaximumSize(); + if (max < size.height) { + max = size.height; + } + } + Dimension size = new Dimension(); + size.width = Short.MAX_VALUE; + size.height = max; + panel.setMaximumSize(size); + return panel; + } + + public static JPanel packLine(JComponent component, JPanel panel) { + ArrayList array = new ArrayList<>(); + array.add(component); + return packLine(array.toArray(new JComponent[array.size()]), panel); + } + + @Override + public void setEnabled(boolean enabled) { + this.tabbe.setEnabledAt(nextNumber - 1, enabled); + } + + public String getTitle() { + return this.title; + } + + class SymAction implements java.awt.event.ActionListener { + @Override + public void actionPerformed(java.awt.event.ActionEvent event) { + Object object = event.getSource(); + if (object == nextButton) { + nextButton_Action(event); + } + else if (object == backButton) { + backButton_Action(event); + } + } + } + + /** + * [次へ]ボタンをクリックした時の動作 + * @param event + */ + void nextButton_Action(ActionEvent event) { + this.tabbe.setSelectedIndex(this.nextNumber); + } + + /** + * [戻る]ボタンをクリックした時の動作 + * @param event + */ + void backButton_Action(ActionEvent event) { + this.tabbe.setSelectedIndex(this.backNumber); + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/CardExifPerform.java b/src/main/java/osm/jp/gpx/matchtime/gui/CardExifPerform.java new file mode 100644 index 0000000..7beaa8a --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/CardExifPerform.java @@ -0,0 +1,199 @@ +package osm.jp.gpx.matchtime.gui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import osm.jp.gpx.AppParameters; +import osm.jp.gpx.ImportPicture; +import static osm.jp.gpx.matchtime.gui.AdjustTerra.dfjp; +import static osm.jp.gpx.matchtime.gui.AdjustTerra.i18n; + +/** + * 実行パネル + * @author yuu + */ +public class CardExifPerform extends Card implements PanelAction { + ParameterPanelTime arg_basetime; // 画像の基準時刻: + ParameterPanelGpx arg_gpxFile; // GPX file or Folder + ParameterPanelOutput arg_output; // EXIF & 書き出しフォルダ + JButton doButton; // [処理実行]ボタン + + /** + * コンストラクタ + * @param tabbe parent panel + * @param arg_basetime // 開始画像の基準時刻: + * @param arg_gpxFile // GPX file or Folder: + * @param arg_output // EXIF & 書き出しフォルダ + * @param text + * @param pre + * @param next + */ + public CardExifPerform( + JTabbedPane tabbe, + ParameterPanelTime arg_basetime, + ParameterPanelGpx arg_gpxFile, + ParameterPanelOutput arg_output, + String text, + int pre, int next + ) { + super(tabbe, text, pre, next); + this.arg_basetime = arg_basetime; + this.arg_gpxFile = arg_gpxFile; + this.arg_output = arg_output; + + SymAction lSymAction = new SymAction(); + JPanel argsPanel = new JPanel(); + argsPanel.setLayout(new BoxLayout(argsPanel, BoxLayout.PAGE_AXIS)); + + // 5. EXIF変換を行うかどうかを選択してください。 + // - EXIF変換を行う場合には、変換ファイルを出力するフォルダも指定する必要があります。 + // - 出力フォルダには、書き込み権限と、十分な空き容量が必要です。 + JLabel label5 = new JLabel(); + label5.setText( + String.format( + "

5. %s

  • %s
  • %s
", + i18n.getString("label.500"), + i18n.getString("label.501"), + i18n.getString("label.502") + ) + ); + argsPanel.add(packLine(label5, new JPanel())); + + // 出力フォルダ + //argsPanel.add(packLine(new JLabel(i18n.getString("label.530")), new JPanel())); + argsPanel.add(arg_output); + + // チェックボックス "IMGの変換をする" + if (arg_output.outputIMG != null) { + arg_output.outputIMG.addActionListener(lSymAction); + argsPanel.add(arg_output.outputIMG); + } + + // チェックボックス "IMGの変換をする" + if (arg_output.outputIMG_all != null) { + argsPanel.add(arg_output.outputIMG_all); + } + + // チェックボックス "EXIFの変換をする" + if (arg_output.exifON != null) { + argsPanel.add(arg_output.exifON); + } + + // チェックボックス "ポイントマーカーをGPXファイルに出力する" + if (arg_output.gpxOutputWpt != null) { + argsPanel.add(arg_output.gpxOutputWpt); + } + + // チェックボックス "ソースGPXのを無視する" + if (arg_output.gpxOverwriteMagvar != null) { + argsPanel.add(arg_output.gpxOverwriteMagvar); + } + + // チェックボックス "出力GPXに[SPEED]を上書きする" + if (arg_output.gpxOutputSpeed != null) { + argsPanel.add(arg_output.gpxOutputSpeed); + } + + // [処理実行]ボタン + doButton = new JButton( + i18n.getString("button.execute"), + AdjustTerra.createImageIcon("/images/media_playback_start.png") + ); + argsPanel.add(doButton); + + this.mainPanel.add(argsPanel, BorderLayout.CENTER); + + //{{REGISTER_LISTENERS + doButton.addActionListener(lSymAction); + //}} + } + + class SymAction implements java.awt.event.ActionListener { + @Override + public void actionPerformed(java.awt.event.ActionEvent event) { + Object object = event.getSource(); + if (object == doButton) { + doButton_Action(event); + } + else if (object == arg_output.outputIMG) { + outputIMG_Action(event); + } + } + } + + /** + * checkbox[IMG変換]を変更した場合のアクション + * ON ー> IMG出力フォルダのフィールドを有効にする + * OFF -> IMG出力フォルダのフィールドを無効にする + * @param event + */ + void outputIMG_Action (ActionEvent event) { + setEnabled(isEnabled()); + } + + /** + * [実行]ボタンをクリックしたときの動作 + * @param event + */ + @SuppressWarnings("UseSpecificCatch") + void doButton_Action(java.awt.event.ActionEvent event) { + doButton.setEnabled(false); + + ParameterPanelImageFile arg_baseTimeImg = arg_basetime.imageFile; // 基準時刻画像 + ParameterPanelFolder arg_srcFolder = arg_baseTimeImg.paramDir; + + try { + AppParameters params = new AppParameters(); + + String[] argv = new String[0]; + params.setProperty(AppParameters.GPX_NO_FIRST_NODE, String.valueOf(arg_gpxFile.isNoFirstNodeSelected())); + params.setProperty(AppParameters.GPX_REUSE, String.valueOf(arg_gpxFile.isGpxReuseSelected())); + params.setProperty(AppParameters.GPX_SOURCE_FOLDER, arg_gpxFile.getText()); + if ((arg_basetime.exifBase != null) && arg_basetime.exifBase.isSelected()) { + params.setProperty(AppParameters.GPX_BASETIME, "EXIF_TIME"); + } + else { + params.setProperty(AppParameters.GPX_BASETIME, "FILE_UPDATE"); + } + params.setProperty(AppParameters.IMG_SOURCE_FOLDER, arg_srcFolder.getText()); + params.setProperty(AppParameters.IMG_BASE_FILE, arg_baseTimeImg.getText()); + params.setProperty(AppParameters.IMG_TIME, ImportPicture.toUTCString(dfjp.parse(arg_basetime.getText()))); + + params.setProperty(AppParameters.IMG_OUTPUT, String.valueOf(arg_output.outputIMG.isSelected())); + params.setProperty(AppParameters.IMG_OUTPUT_ALL, String.valueOf(arg_output.outputIMG_all.isSelected())); + params.setProperty(AppParameters.IMG_OUTPUT_FOLDER, arg_output.getText()); + + params.setProperty(AppParameters.IMG_OUTPUT_EXIF, String.valueOf(arg_output.exifON.isSelected())); + params.setProperty(AppParameters.GPX_OVERWRITE_MAGVAR, String.valueOf(arg_output.gpxOverwriteMagvar.isSelected())); + params.setProperty(AppParameters.GPX_OUTPUT_SPEED, String.valueOf(arg_output.gpxOutputSpeed.isSelected())); + params.setProperty(AppParameters.GPX_OUTPUT_WPT, String.valueOf(arg_output.gpxOutputWpt.isSelected())); + params.store(); + } + catch(Exception e) { + e.printStackTrace(); + } + + (new DoDialog(new String[0])).setVisible(true); + + doButton.setEnabled(true); + } + + /** + * 入力条件が満たされているかどうか + * @return + */ + @Override + public boolean isEnable() { + return (arg_basetime.isEnable() && arg_gpxFile.isEnable()); + } + + @Override + @SuppressWarnings("empty-statement") + public void openAction() { + ; // 何もしない + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/CardGpxFile.java b/src/main/java/osm/jp/gpx/matchtime/gui/CardGpxFile.java new file mode 100644 index 0000000..67bcd71 --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/CardGpxFile.java @@ -0,0 +1,74 @@ +package osm.jp.gpx.matchtime.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import javax.swing.BoxLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import static osm.jp.gpx.matchtime.gui.AdjustTerra.i18n; + +/** + * [GPXファイル]選択パネル + * @author yuu + */ +public class CardGpxFile extends Card implements PanelAction { + ParameterPanelGpx arg_gpxFile; + + /** + * コンストラクタ + * @param tabbe parent panel + * @param arg_gpxFile // 開始画像の基準時刻: + * @param text + * @param pre + * @param next + */ + public CardGpxFile( + JTabbedPane tabbe, + ParameterPanelGpx arg_gpxFile, + String text, + int pre, int next + ) { + super(tabbe, text, pre, next); + this.arg_gpxFile = arg_gpxFile; + + // 4. ヒモ付を行うGPXファイルを選択してください。 + // - フォルダを指定すると、フォルダ内のすべてのGPXファイルを対象とします。 + JPanel argsPanel = new JPanel(); + argsPanel.setLayout(new BoxLayout(argsPanel, BoxLayout.PAGE_AXIS)); + argsPanel.add(packLine(new JLabel(i18n.getString("label.400")), new JPanel())); + argsPanel.add(arg_gpxFile); + + // "セグメント'trkseg'の最初の1ノードは無視する。" + if (arg_gpxFile.noFirstNode != null) { + argsPanel.add(arg_gpxFile.noFirstNode); + } + + // "生成されたGPXファイル(ファイル名が'_.gpx'で終わるもの)も変換の対象にする" + if (arg_gpxFile.gpxReuse != null) { + argsPanel.add(arg_gpxFile.gpxReuse); + } + + JPanel space = new JPanel(); + space.setMinimumSize(new Dimension(40, 20)); + space.setMaximumSize(new Dimension(40, Short.MAX_VALUE)); + argsPanel.add(space); + + this.mainPanel.add(argsPanel, BorderLayout.CENTER); + } + + /** + * 入力条件が満たされているかどうか + * @return + */ + @Override + public boolean isEnable() { + return (arg_gpxFile.isEnable()); + } + + @Override + @SuppressWarnings("empty-statement") + public void openAction() { + ; // 何もしない + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/Command.java b/src/main/java/osm/jp/gpx/matchtime/gui/Command.java new file mode 100644 index 0000000..cb0f78e --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/Command.java @@ -0,0 +1,70 @@ +package osm.jp.gpx.matchtime.gui; + +import java.lang.reflect.InvocationTargetException; +import java.text.SimpleDateFormat; + +public class Command extends Thread { + String[] args; // コマンドパラメータ + private String commandName = ""; // コマンド名 + @SuppressWarnings({ "rawtypes" }) + private final Class cmd; // 実行対象インスタンス + + /** + * コンストラクタ:実行対象のインスタンスを得る + * @param cmd + */ + public Command(Class cmd) { + super(); + this.cmd = cmd; + this.commandName = cmd.getName(); + this.args = new String[0]; + } + + /** + * コマンドパラメータの設定 + * @param args + */ + public void setArgs(String[] args) { + this.args = args; + } + + public void setCommandName(String name) { + this.commandName = name; + } + public String getCommandName() { + return this.commandName; + } + + @SuppressWarnings("unchecked") + @Override + public void run() { + System.out.println("[START:"+ (new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss")).format(new java.util.Date()) +"]\t"+ this.commandName); + for (int i=0; i < args.length; i++) { + System.out.println(" args["+ i +"]: "+ this.args[i]); + } + System.out.println(); + + try { + try { + java.lang.reflect.Method method = this.cmd.getMethod("main", new Class[] {String[].class}); + method.setAccessible(true); + method.invoke(null, new Object[]{this.args}); + + System.out.println(); + System.out.println("[END:"+ (new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss")).format(new java.util.Date()) +"]\t"+ this.commandName); + } + catch (InvocationTargetException e) { + System.out.println("[ERR!:"+ (new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss")).format(new java.util.Date()) +"]\t"+ this.commandName); + throw e; + } + catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException e) { + System.out.println("[ERR!:"+ (new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss")).format(new java.util.Date()) +"]\t"+ this.commandName); + throw e; + } + } + catch(InvocationTargetException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException e) { + e.printStackTrace(System.out); + } + System.out.println(); + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/DoDialog.java b/src/main/java/osm/jp/gpx/matchtime/gui/DoDialog.java new file mode 100644 index 0000000..8c991e1 --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/DoDialog.java @@ -0,0 +1,230 @@ +package osm.jp.gpx.matchtime.gui; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.io.*; +import javax.swing.*; + +/** + * 処理 + */ +@SuppressWarnings("serial") +public class DoDialog extends JDialog { + public static final String TITLE = "Do Command"; + + // Used for addNotify check. + boolean fComponentsAdjusted = false; + String[] args; + + //{{DECLARE_CONTROLS + JPanel buttonPanel; // ボタン配置パネル (下部) + JButton closeButton; // [クローズ]ボタン + JButton doButton; // [実行]ボタン + JTextArea textArea; // 実行結果を表示するJTextArea (中央) + //}} + + @SuppressWarnings("OverridableMethodCallInConstructor") + public DoDialog(String[] args) { + super(); // モーダルダイアログを基盤にする + this.args = args; + + // INIT_CONTROLS + @SuppressWarnings("OverridableMethodCallInConstructor") + Container container = getContentPane(); + container.setLayout(new BorderLayout()); + //parentFrame.setVisible(false); + setSize(getInsets().left + getInsets().right + 980, getInsets().top + getInsets().bottom + 480); + setTitle(DoDialog.TITLE); + + // コントロールパネル + buttonPanel = new JPanel(); + + doButton = new JButton("実行"); + doButton.setToolTipText("処理を実行します."); + doButton.setEnabled(true); + doButton.addActionListener((ActionEvent event) -> { + // 処理中であることを示すため + // ボタンの文字列を変更し,使用不可にする + doButton.setText("処理中..."); + doButton.setEnabled(false); + + // SwingWorker を生成し,実行する + LongTaskWorker worker = new LongTaskWorker(doButton); + worker.execute(); + }); + buttonPanel.add(doButton); + + closeButton = new JButton("閉じる"); + closeButton.setToolTipText("処理を終了します."); + closeButton.addActionListener((ActionEvent event) -> { + dispose(); + }); + buttonPanel.add(closeButton); + + this.getContentPane().add("South", buttonPanel); + + // 説明文 + textArea = new JTextArea(); + JScrollPane sc=new JScrollPane(textArea); + textArea.setFont(new Font(Font.MONOSPACED,Font.PLAIN,12)); + textArea.setTabSize(4); + this.getContentPane().add("Center", sc); + + try { + textArea.append("> java -cp importPicture.jar osm.jp.gpx.ImportPicture"); + for (String arg : args) { + textArea.append(" '" + arg + "'"); + } + textArea.append("\n\n"); + } + catch (Exception e) { + System.out.println(e.toString()); + } + + // JFrameの表示 + //parentFrame.setVisible(true); + } + + /** + * Shows or hides the component depending on the boolean flag b. + * @param b trueのときコンポーネントを表示; その他のとき, componentを隠す. + * @see java.awt.Component#isVisible + */ + @Override + public void setVisible(boolean b) { + if(b) { + setLocation(80, 80); + } + super.setVisible(b); + } + + @Override + public void addNotify() { + // Record the size of the window prior to calling parents addNotify. + Dimension d = getSize(); + + super.addNotify(); + + if (fComponentsAdjusted) { + return; + } + + // Adjust components according to the insets + setSize(getInsets().left + getInsets().right + d.width, getInsets().top + getInsets().bottom + d.height); + Component components[] = getComponents(); + for (Component component : components) { + Point p = component.getLocation(); + p.translate(getInsets().left, getInsets().top); + component.setLocation(p); + } + fComponentsAdjusted = true; + } + + + /** + * JTextAreaに書き出すOutputStream + */ + public static class JTextAreaOutputStream extends OutputStream { + private final ByteArrayOutputStream os; + + /** 書き出し対象 */ + private final JTextArea textArea; + private final String encode; + + public JTextAreaOutputStream(JTextArea textArea, String encode) { + this.textArea = textArea; + this.encode = encode; + this.os = new ByteArrayOutputStream(); + } + + /** + * OutputStream#write(byte[])のオーバーライド + * @param arg + * @throws java.io.IOException + */ + @Override + public void write(int arg) throws IOException { + this.os.write(arg); + } + + /** + * flush()でJTextAreaに書き出す + * @throws java.io.IOException + */ + @Override + public void flush() throws IOException { + // 文字列のエンコード + final String str = new String(this.os.toByteArray(), this.encode); + // 実際の書き出し処理 + SwingUtilities.invokeLater( + new Runnable(){ + @Override + public void run() { + JTextAreaOutputStream.this.textArea.append(str); + } + } + ); + // 書き出した内容はクリアする + this.os.reset(); + } + } + + // 非同期に行う処理を記述するためのクラス + class LongTaskWorker extends SwingWorker { + private final JButton button; + + public LongTaskWorker(JButton button) { + this.button = button; + } + + // 非同期に行われる処理 + @Override + @SuppressWarnings("SleepWhileInLoop") + public Object doInBackground() { + // ながーい処理 + PrintStream defOut = System.out; + PrintStream defErr = System.err; + + OutputStream os = new JTextAreaOutputStream(textArea, "UTF-8"); + PrintStream stdout = new PrintStream(os, true); // 自動flushをtrueにしておく + + // System.out にJTextAreaOutputStreamに書き出すPrintStreamを設定 + System.setOut(stdout); + System.setErr(stdout); + + try { + Command command = new Command(osm.jp.gpx.ImportPicture.class); + command.setArgs(args); + command.start(); // コマンドを実行 + while (command.isAlive()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) {} + } + } + catch(Exception e) { + e.printStackTrace(stdout); + } + finally { + System.setOut(defOut); + System.setErr(defErr); + doButton.setEnabled(true); + } + + return null; + } + + // 非同期処理後に実行 + @Override + protected void done() { + // 処理が終了したので,文字列を元に戻し + // ボタンを使用可能にする + button.setText("実行"); + button.setEnabled(true); + } + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/GpxAndFolderFilter.java b/src/main/java/osm/jp/gpx/matchtime/gui/GpxAndFolderFilter.java new file mode 100644 index 0000000..3c075cd --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/GpxAndFolderFilter.java @@ -0,0 +1,25 @@ +package osm.jp.gpx.matchtime.gui; + +import java.io.File; +import javax.swing.filechooser.*; + +public class GpxAndFolderFilter extends FileFilter { + + @Override + public boolean accept(File f) { + if (f.isDirectory()) { + return true; + } + + String extension = Utils.getExtension(f); + if (extension != null) { + return extension.equals("gpx"); + } + return false; + } + + @Override + public String getDescription() { + return "Just GPXs"; + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/ImageFileView.java b/src/main/java/osm/jp/gpx/matchtime/gui/ImageFileView.java new file mode 100644 index 0000000..708c25a --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/ImageFileView.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Oracle or the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package osm.jp.gpx.matchtime.gui; + +import java.io.File; +import javax.swing.*; +import javax.swing.filechooser.*; + +/* ImageFileView.java is used by FileChooserDemo2.java. */ +public class ImageFileView extends FileView { + ImageIcon jpgIcon = Utils.createImageIcon("/images/jpgIcon.gif"); + ImageIcon gifIcon = Utils.createImageIcon("/images/gifIcon.gif"); + ImageIcon tiffIcon = Utils.createImageIcon("/images/tiffIcon.gif"); + ImageIcon pngIcon = Utils.createImageIcon("/images/pngIcon.png"); + + @Override + public String getName(File f) { + return null; //let the L&F FileView figure this out + } + + @Override + public String getDescription(File f) { + return null; //let the L&F FileView figure this out + } + + @Override + public Boolean isTraversable(File f) { + return null; //let the L&F FileView figure this out + } + + @Override + public String getTypeDescription(File f) { + String extension = Utils.getExtension(f); + String type = null; + + if (extension != null) { + switch (extension) { + case Utils.JPEG: + case Utils.JPG: + type = "JPEG Image"; + break; + case Utils.GIF: + type = "GIF Image"; + break; + case Utils.TIFF: + case Utils.TIF: + type = "TIFF Image"; + break; + case Utils.PNG: + type = "PNG Image"; + break; + default: + break; + } + } + return type; + } + + @Override + public Icon getIcon(File f) { + String extension = Utils.getExtension(f); + Icon icon = null; + + if (extension != null) { + switch (extension) { + case Utils.JPEG: + case Utils.JPG: + icon = jpgIcon; + break; + case Utils.GIF: + icon = gifIcon; + break; + case Utils.TIFF: + case Utils.TIF: + icon = tiffIcon; + break; + case Utils.PNG: + icon = pngIcon; + break; + default: + break; + } + } + return icon; + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/ImageFilter.java b/src/main/java/osm/jp/gpx/matchtime/gui/ImageFilter.java new file mode 100644 index 0000000..012eeb6 --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/ImageFilter.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Oracle or the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package osm.jp.gpx.matchtime.gui; + +import java.io.File; +import javax.swing.filechooser.*; + +/* ImageFilter.java is used by FileChooserDemo2.java. */ +public class ImageFilter extends FileFilter { + + //Accept all directories and all gif, jpg, tiff, or png files. + @Override + public boolean accept(File f) { + if (f.isDirectory()) { + return true; + } + + String extension = Utils.getExtension(f); + if (extension != null) { + return extension.equals(Utils.TIFF) || + extension.equals(Utils.TIF) || + extension.equals(Utils.GIF) || + extension.equals(Utils.JPEG) || + extension.equals(Utils.JPG) || + extension.equals(Utils.PNG); + } + return false; + } + + //The description of this filter + @Override + public String getDescription() { + return "Just Images"; + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/ImagePreview.java b/src/main/java/osm/jp/gpx/matchtime/gui/ImagePreview.java new file mode 100644 index 0000000..203d3a2 --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/ImagePreview.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Oracle or the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package osm.jp.gpx.matchtime.gui; + +import javax.swing.*; + +import java.beans.*; +import java.awt.*; +import java.io.File; + +/* ImagePreview.java by FileChooserDemo2.java. */ +@SuppressWarnings("serial") +public class ImagePreview extends JComponent implements PropertyChangeListener { + ImageIcon thumbnail = null; + File file = null; + static int IMAGE_SIZE_X = 320; + static int IMAGE_SIZE_Y = 240; + + @SuppressWarnings({"LeakingThisInConstructor", "OverridableMethodCallInConstructor"}) + public ImagePreview(JFileChooser fc) { + setPreferredSize(new Dimension(IMAGE_SIZE_X + 10, IMAGE_SIZE_Y + 10)); + fc.addPropertyChangeListener(this); + } + + public void loadImage() { + if (file == null) { + thumbnail = null; + return; + } + + //Don't use createImageIcon (which is a wrapper for getResource) + //because the image we're trying to load is probably not one + //of this program's own resources. + ImageIcon tmpIcon = new ImageIcon(file.getPath()); + if (tmpIcon.getIconWidth() > IMAGE_SIZE_X) { + thumbnail = new ImageIcon(tmpIcon.getImage(). + getScaledInstance(IMAGE_SIZE_X, -1, Image.SCALE_DEFAULT)); + } else { //no need to miniaturize + thumbnail = tmpIcon; + } + } + + @Override + public void propertyChange(PropertyChangeEvent e) { + boolean update = false; + String prop = e.getPropertyName(); + + if (JFileChooser.DIRECTORY_CHANGED_PROPERTY.equals(prop)) { + file = null; + update = true; + } + else if (JFileChooser.SELECTED_FILE_CHANGED_PROPERTY.equals(prop)) { + file = (File) e.getNewValue(); + update = true; + } + if (update) { + thumbnail = null; + if (isShowing()) { + loadImage(); + repaint(); + } + } + } + + @Override + protected void paintComponent(Graphics g) { + if (thumbnail == null) { + loadImage(); + } + if (thumbnail != null) { + int x = getWidth()/2 - thumbnail.getIconWidth()/2; + int y = getHeight()/2 - thumbnail.getIconHeight()/2; + + if (y < 0) { + y = 0; + } + + if (x < 5) { + x = 5; + } + thumbnail.paintIcon(this, g, x, y); + } + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/PanelAction.java b/src/main/java/osm/jp/gpx/matchtime/gui/PanelAction.java new file mode 100644 index 0000000..88085dc --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/PanelAction.java @@ -0,0 +1,11 @@ +package osm.jp.gpx.matchtime.gui; + +public interface PanelAction { + void openAction(); + + /** + * 入力条件が満たされているかどうか + * @return + */ + boolean isEnable(); +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/ParamAction.java b/src/main/java/osm/jp/gpx/matchtime/gui/ParamAction.java new file mode 100644 index 0000000..d09db2e --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/ParamAction.java @@ -0,0 +1,7 @@ +package osm.jp.gpx.matchtime.gui; + +public interface ParamAction { + boolean isEnable(); + void setText(String text); + String getText(); +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/ParameterData.java b/src/main/java/osm/jp/gpx/matchtime/gui/ParameterData.java new file mode 100644 index 0000000..76eb082 --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/ParameterData.java @@ -0,0 +1,23 @@ +package osm.jp.gpx.matchtime.gui; + +import java.util.Observable; + +public class ParameterData extends Observable { + String content = ""; + + String getContent() { + return content; + } + + void setContent(String content) { + this.content = content; + setChanged(); + super.notifyObservers(content); + clearChanged(); + } + + @Override + public void notifyObservers(Object arg) { + setContent(arg.toString()); + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanel.java b/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanel.java new file mode 100644 index 0000000..00f1418 --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanel.java @@ -0,0 +1,54 @@ +package osm.jp.gpx.matchtime.gui; + +import java.awt.Dimension; +import java.util.ResourceBundle; + +import javax.swing.BoxLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +/** + * パラメータを設定する為のパネル。 + * この1インスタンスで、1パラメータをあらわす。 + */ +public abstract class ParameterPanel extends JPanel implements ParamAction { + private static final long serialVersionUID = 4629824800747170556L; + public JTextField argField; + public JLabel argLabel; + public ResourceBundle i18n = ResourceBundle.getBundle("i18n"); + + @SuppressWarnings("OverridableMethodCallInConstructor") + public ParameterPanel(String label, String text) { + this(); + this.argLabel.setText(label); + this.argField.setText(text); + } + + public ParameterPanel() { + super(); + + argLabel = new JLabel(); + argField = new JTextField(); + + this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); + this.setMaximumSize(new Dimension(1920, 40)); + this.add(argLabel); + this.add(argField); + } + + public ParameterPanel setLabel(String label) { + this.argLabel.setText(label); + return this; + } + + @Override + public void setText(String text) { + this.argField.setText(text); + } + + @Override + public String getText() { + return this.argField.getText(); + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelFolder.java b/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelFolder.java new file mode 100644 index 0000000..0f46afc --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelFolder.java @@ -0,0 +1,108 @@ +package osm.jp.gpx.matchtime.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.FileNotFoundException; +import javax.swing.JButton; +import javax.swing.JFileChooser; + +@SuppressWarnings("serial") +public class ParameterPanelFolder extends ParameterPanel implements ActionListener +{ + JFileChooser fc; + JButton selectButton; + int chooser; + + /** + * コンストラクタ + * ディレクトリのみ選択可能なダイアログ + * @param label + * @param text + */ + public ParameterPanelFolder(String label, String text) { + this(label, text, JFileChooser.DIRECTORIES_ONLY); + } + + @SuppressWarnings({"OverridableMethodCallInConstructor", "LeakingThisInConstructor"}) + public ParameterPanelFolder(String label, String text, int chooser) { + super(label, text); + + // Create a file chooser + this.chooser = chooser; + + // "選択..." + selectButton = new JButton( + i18n.getString("button.select"), + AdjustTerra.createImageIcon("/images/Open16.gif") + ); + selectButton.addActionListener(this); + this.add(selectButton); + } + + public void setEnable(boolean f) { + super.setEnabled(f); + selectButton.setEnabled(f); + } + + public File getDirectory() throws FileNotFoundException { + String path = this.argField.getText(); + if (path == null) { + throw new FileNotFoundException("Folder is Not specifiyed yet."); + } + File sdir = new File(path); + if (!sdir.exists()) { + throw new FileNotFoundException(String.format("Folder '%s' is Not exists.", path)); + } + if (!sdir.isDirectory()) { + throw new FileNotFoundException(String.format("Folder '%s' is Not directory.", path)); + } + return sdir; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == selectButton){ + File sdir; + try { + sdir = getDirectory(); + } catch (FileNotFoundException ex) { + sdir = new File("."); + this.argField.setText(sdir.getAbsolutePath()); + } + if (sdir.exists()) { + this.fc = new JFileChooser(sdir); + } + else { + this.fc = new JFileChooser(); + } + this.fc.setFileSelectionMode(this.chooser); + + int returnVal = this.fc.showOpenDialog(ParameterPanelFolder.this); + + if (returnVal == JFileChooser.APPROVE_OPTION) { + File file = this.fc.getSelectedFile(); + this.argField.setText(file.getAbsolutePath()); + } + } + } + + /** + * 有効な値が設定されているかどうか + * @return + */ + @Override + public boolean isEnable() { + String text = this.argField.getText(); + if (text == null) { + return false; + } + try { + File dir = new File(text); + return (dir.exists() && dir.isDirectory()); + } + catch (Exception e) { + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelGpx.java b/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelGpx.java new file mode 100644 index 0000000..6a463f9 --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelGpx.java @@ -0,0 +1,126 @@ +package osm.jp.gpx.matchtime.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import osm.jp.gpx.AppParameters; + +@SuppressWarnings("serial") +public class ParameterPanelGpx extends ParameterPanel implements ActionListener +{ + JFileChooser fc; + JButton selectButton; + public JCheckBox noFirstNode; // CheckBox: "セグメント'trkseg'の最初の1ノードは無視する。" + public JCheckBox gpxReuse; // CheckBox: "生成されたGPXファイル(ファイル名が'_.gpx'で終わるもの)も変換の対象にする" + + /** + * コンストラクタ + * @param label + * @param text + */ + @SuppressWarnings({"OverridableMethodCallInConstructor", "LeakingThisInConstructor"}) + public ParameterPanelGpx(String label, String text) { + super(label, text); + + // "選択..." + selectButton = new JButton( + i18n.getString("button.select"), + AdjustTerra.createImageIcon("/images/Open16.gif") + ); + selectButton.addActionListener(this); + this.add(selectButton); + } + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == selectButton){ + System.out.println("ParameterPanelGpx.actionPerformed(openButton)"); + File sdir = new File(this.argField.getText()); + if (sdir.exists()) { + this.fc = new JFileChooser(sdir); + } + else { + this.fc = new JFileChooser(); + } + this.fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + this.fc.addChoosableFileFilter(new GpxAndFolderFilter()); + this.fc.setAcceptAllFileFilterUsed(false); + + int returnVal = this.fc.showOpenDialog(ParameterPanelGpx.this); + + if (returnVal == JFileChooser.APPROVE_OPTION) { + File file = this.fc.getSelectedFile(); + this.argField.setText(file.getAbsolutePath()); + } + } + } + + public File getGpxFile() { + if (isEnable()) { + return new File(getText()); + } + return null; + } + + /** + * "セグメント'trkseg'の最初の1ノードは無視する。" + * @param label テキスト + * @param params プロパティ + */ + public void addNoFirstNode(String label, AppParameters params) { + boolean selected = false; + if (params.getProperty(AppParameters.GPX_NO_FIRST_NODE).equals("true")) { + selected = true; + } + noFirstNode = new JCheckBox(label, selected); + } + + public boolean isNoFirstNodeSelected() { + return (noFirstNode != null) && noFirstNode.isSelected(); + } + + /** + * "生成されたGPXファイル(ファイル名が'_.gpx'で終わるもの)も変換の対象にする" + * @param label テキスト + * @param params プロパティ + */ + public void addGpxReuse(String label, AppParameters params) { + boolean selected = false; + if (params.getProperty(AppParameters.GPX_REUSE).equals("true")) { + selected = true; + } + gpxReuse = new JCheckBox(label, selected); + } + + public boolean isGpxReuseSelected() { + return (gpxReuse != null) && gpxReuse.isSelected(); + } + + /** + * このフィールドに有効な値が設定されているかどうか + * @return + */ + @Override + public boolean isEnable() { + String text = this.argField.getText(); + if (text != null) { + File file = new File(text); + if (file.exists()) { + if (file.isFile()) { + String name = file.getName().toUpperCase(); + if (name.endsWith(".GPX")) { + return true; + } + } + else if (file.isDirectory()) { + return true; + } + } + } + return false; + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelImageFile.java b/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelImageFile.java new file mode 100644 index 0000000..2c99c17 --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelImageFile.java @@ -0,0 +1,109 @@ +package osm.jp.gpx.matchtime.gui; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.FileNotFoundException; +import javax.swing.JButton; +import javax.swing.JFileChooser; + +@SuppressWarnings("serial") +public class ParameterPanelImageFile extends ParameterPanel { + JFileChooser fc; + public JButton openButton; + public ParameterPanelFolder paramDir; + + @SuppressWarnings("OverridableMethodCallInConstructor") + public ParameterPanelImageFile( + String label, String text, + ParameterPanelFolder paramDir + ) { + super(label, text); + + // "選択..." + SelectButtonAction buttonAction = new SelectButtonAction(); + openButton = new JButton(i18n.getString("button.select")); + openButton.addActionListener(buttonAction); + this.add(openButton); + + //Create a file chooser + this.paramDir = paramDir; + } + + class SelectButtonAction implements java.awt.event.ActionListener + { + @SuppressWarnings("override") + public void actionPerformed(ActionEvent e) { + selectImage_Action(e); + } + } + + public void selectImage_Action(ActionEvent ev) { + File sdir = new File(paramDir.getText()); + System.out.println(sdir.toPath()); + if (sdir.isDirectory()) { + fc = new JFileChooser(sdir); + } + else { + fc = new JFileChooser(); + } + + fc.addChoosableFileFilter(new ImageFilter()); + fc.setAcceptAllFileFilterUsed(false); + fc.setFileView(new ImageFileView()); + fc.setAccessory(new ImagePreview(fc)); + + //Show it. "選択" + int returnVal = fc.showDialog(ParameterPanelImageFile.this, i18n.getString("dialog.select")); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File file = fc.getSelectedFile(); + this.argField.setText(file.getName()); + } + fc.setSelectedFile(null); + } + + public File getImageFile() { + if (this.paramDir.isEnable()) { + String text = this.argField.getText(); + if (text != null) { + try { + File dir = this.paramDir.getDirectory(); + File file = new File(dir, text); + if (file.exists() && file.isFile()) { + return file; + } + } + catch (FileNotFoundException e) { + return null; + } + } + } + return null; + } + + /** + * + * @return + */ + @Override + public boolean isEnable() { + if (this.paramDir.isEnable()) { + String text = this.argField.getText(); + if (text != null) { + try { + File dir = this.paramDir.getDirectory(); + File file = new File(dir, text); + if (file.exists() && file.isFile()) { + String name = file.getName().toUpperCase(); + if (name.endsWith(".JPG") || name.endsWith(".JPEG")) { + return true; + } + } + } + catch (FileNotFoundException e) { + return false; + } + } + } + return false; + } +} \ No newline at end of file diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelOutput.java b/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelOutput.java new file mode 100644 index 0000000..ce4b1ea --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelOutput.java @@ -0,0 +1,123 @@ +package osm.jp.gpx.matchtime.gui; + +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import osm.jp.gpx.AppParameters; + +@SuppressWarnings("serial") +public class ParameterPanelOutput extends ParameterPanelFolder +{ + JCheckBox outputIMG; // IMGの変換 する/しない + JCheckBox outputIMG_all; // 'out of GPX time'でもIMGの変換をする {ON | OFF} + JCheckBox exifON; // EXIF 書き出しモード / !(EXIFの書き換えはしない) + JCheckBox gpxOutputWpt; // GPXにを書き出す + JCheckBox gpxOverwriteMagvar; // ソースGPXのを無視する + JCheckBox gpxOutputSpeed; // GPXにを書き出す + + /** + * コンストラクタ + * ディレクトリのみ選択可能なダイアログ + * @param label + * @param text + */ + public ParameterPanelOutput(String label, String text) { + super(label, text, JFileChooser.DIRECTORIES_ONLY); + } + + /** + * チェックボックス "IMGの変換をする" + * @param label テキスト + * @param params プロパティ + */ + public void addCheckChangeImage(String label, AppParameters params) { + boolean selected = false; + if (params.getProperty(AppParameters.IMG_OUTPUT).equals("true")) { + selected = true; + } + outputIMG = new JCheckBox(label, selected); + } + + /** + * チェックボックス "GPXファイル時間外のファイルもコピーする" + * @param label + * @param params + */ + public void addCheckOutofGpxTime(String label, AppParameters params) { + boolean selected = false; + if (params.getProperty(AppParameters.IMG_OUTPUT_ALL).equals("true")) { + selected = true; + } + outputIMG_all = new JCheckBox(label, selected); + } + + /** + * チェックボックス "EXIFの変換をする" + * @param label + * @param params + */ + public void addCheckOutputExif(String label, AppParameters params) { + boolean selected = false; + if (params.getProperty(AppParameters.IMG_OUTPUT_EXIF).equals("true")) { + selected = true; + } + exifON = new JCheckBox(label, selected); + } + + /** + * チェックボックス "ポイントマーカー[WPT]をGPXファイルに出力する" + * @param label + * @param params + */ + public void addCheckOutputWpt(String label, AppParameters params) { + boolean selected = false; + if (params.getProperty(AppParameters.GPX_OUTPUT_WPT).equals("true")) { + selected = true; + } + gpxOutputWpt = new JCheckBox(label, selected); + gpxOutputWpt.setEnabled(true); + } + + /** + * チェックボックス "ソースGPXの<MAGVAR>を無視する" + * @param label + * @param params + */ + public void addCheckIgnoreMagvar(String label, AppParameters params) { + boolean selected = false; + if (params.getProperty(AppParameters.GPX_OVERWRITE_MAGVAR).equals("true")) { + selected = true; + } + gpxOverwriteMagvar = new JCheckBox(label, selected); + gpxOverwriteMagvar.setEnabled(true); + } + + /** + * チェックボックス "出力GPXに[SPEED]を上書きする" + * @param label + * @param params + */ + public void addCheckOutputSpeed(String label, AppParameters params) { + boolean selected = false; + if (params.getProperty(AppParameters.GPX_OUTPUT_SPEED).equals("true")) { + selected = true; + } + gpxOutputSpeed = new JCheckBox(label, selected); + gpxOutputSpeed.setEnabled(true); + } + + /** + * checkbox[IMG変換]を変更した場合のアクション + * ON ー> IMG出力フォルダのフィールドを有効にする + * OFF -> IMG出力フォルダのフィールドを無効にする + * @param event + */ + class ChangeImageAction implements java.awt.event.ActionListener { + @Override + public void actionPerformed(java.awt.event.ActionEvent event) { + Object object = event.getSource(); + if (object == outputIMG) { + setEnabled(outputIMG.isEnabled()); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelSelecter.java b/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelSelecter.java new file mode 100644 index 0000000..15b8d96 --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelSelecter.java @@ -0,0 +1,45 @@ +package osm.jp.gpx.matchtime.gui; + +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; + +@SuppressWarnings("serial") +public class ParameterPanelSelecter extends JPanel implements ActionListener { + public static final int ITEM_WIDTH_1 = 160; + public static final int ITEM_WIDTH_2 = 240; + public static final int LINE_WIDTH = ITEM_WIDTH_1 + ITEM_WIDTH_2; + public static final int LINE_HEIGHT = 18; + public JLabel label; + public JComboBox field; + public String value; + + @SuppressWarnings({"OverridableMethodCallInConstructor", "LeakingThisInConstructor"}) + public ParameterPanelSelecter(String title, String[] items) { + super(null); + this.value = items[0]; + + this.label = new JLabel(title, JLabel.RIGHT); + this.label.setBounds(0, 0, ITEM_WIDTH_1 - 6, LINE_HEIGHT); + add(this.label); + + this.field = new JComboBox<>(); + this.field.addActionListener(this); + for (String item : items) { + this.field.addItem(item); + } + this.field.setBounds(ITEM_WIDTH_1, 0, ITEM_WIDTH_2, LINE_HEIGHT); + add(this.field); + + setPreferredSize(new Dimension(ITEM_WIDTH_1, LINE_HEIGHT)); + } + + @Override + public void actionPerformed(ActionEvent e) { + this.value = (String)this.field.getSelectedItem(); + } +} \ No newline at end of file diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelTime.java b/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelTime.java new file mode 100644 index 0000000..2a9b0c3 --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/ParameterPanelTime.java @@ -0,0 +1,195 @@ +package osm.jp.gpx.matchtime.gui; + +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.IOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JRadioButton; +import org.apache.commons.imaging.ImageReadException; +import org.apache.commons.imaging.Imaging; +import org.apache.commons.imaging.common.ImageMetadata; +import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata; +import org.apache.commons.imaging.formats.tiff.TiffImageMetadata; +import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants; +import osm.jp.gpx.AppParameters; +import osm.jp.gpx.Restamp; +import static osm.jp.gpx.matchtime.gui.AdjustTerra.dfjp; +import osm.jp.gpx.matchtime.gui.restamp.DialogCorectTime; + +/** + * パラメータを設定する為のパネル。 + * この1インスタンスで、1パラメータをあらわす。 + */ +public class ParameterPanelTime extends ParameterPanel { + SimpleDateFormat sdf = (SimpleDateFormat)DateFormat.getDateTimeInstance(); + ParameterPanelImageFile imageFile; // 基準時刻画像 + + + // 基準時刻の指定グループ (排他選択) + public ButtonGroup baseTimeGroup = new ButtonGroup(); + public JRadioButton exifBase = null; // EXIF日時を基準にする/ !(ファイル更新日時を基準にする) + public JRadioButton fupdateBase = null; // File更新日時を基準にする/ !(EXIF日時を基準にする) + + public JButton updateButton; + public JButton resetButton; + Window owner; + + @SuppressWarnings("OverridableMethodCallInConstructor") + public ParameterPanelTime( + String label, + String text, + ParameterPanelImageFile imageFile + ) { + super(label, text); + this.imageFile = imageFile; + + // "ボタン[変更...]" + UpdateButtonAction buttonAction = new UpdateButtonAction(this); + updateButton = new JButton(i18n.getString("button.update")); + updateButton.addActionListener(buttonAction); + this.add(updateButton); + + // "ボタン[再設定...]" + ResetButtonAction resetAction = new ResetButtonAction(this); + resetButton = new JButton(i18n.getString("button.reset")); + resetButton.addActionListener(resetAction); + resetButton.setVisible(false); + this.add(resetButton); + } + + public ParameterPanelTime setOwner(Window owner) { + this.owner = owner; + return this; + } + + /** + * 「EXIFの日時を基準にする」 + * @param label テキスト + * @param params プロパティ + */ + public void addExifBase(String label, AppParameters params) { + boolean selected = false; + if (params.getProperty(AppParameters.GPX_BASETIME).equals("EXIF_TIME")) { + selected = true; + } + exifBase = new JRadioButton(label, selected); + baseTimeGroup.add(exifBase); + } + + /** + * 「File更新日時を基準にする」 + * @param label テキスト + * @param params プロパティ + */ + public void addFileUpdate(String label, AppParameters params) { + boolean selected = false; + if (params.getProperty(AppParameters.GPX_BASETIME).equals("FILE_UPDATE")) { + selected = true; + } + fupdateBase = new JRadioButton(label, selected); + baseTimeGroup.add(fupdateBase); + } + + public ParameterPanelImageFile getImageFile() { + return this.imageFile; + } + + + /** + * [変更...]ボタンのアクション + */ + class UpdateButtonAction implements java.awt.event.ActionListener + { + ParameterPanelTime param; + + public UpdateButtonAction(ParameterPanelTime param) { + this.param = param; + } + + @SuppressWarnings("override") + public void actionPerformed(ActionEvent e) { + fileSelect_Action(param); + (new DialogCorectTime(param, owner)).setVisible(true); + } + } + + /** + * [再設定...]ボタンのアクション + */ + class ResetButtonAction implements java.awt.event.ActionListener + { + ParameterPanelTime paramPanelTime; + + public ResetButtonAction(ParameterPanelTime param) { + this.paramPanelTime = param; + } + + @SuppressWarnings("override") + public void actionPerformed(ActionEvent e) { + fileSelect_Action(paramPanelTime); + } + } + + /** + * 画像ファイルが選択されたときのアクション + * 1.ラジオボタンの選択を参照してTEXTフィールドにファイルの「日時」を設定する + * @param param + */ + void fileSelect_Action(ParameterPanelTime param) { + if (imageFile.isEnable()) { + File timeFile = imageFile.getImageFile(); + + // Radio Selecter + sdf.applyPattern(Restamp.TIME_PATTERN); + if ((exifBase != null) && exifBase.isSelected()) { + try { + ImageMetadata meta = Imaging.getMetadata(timeFile); + JpegImageMetadata jpegMetadata = (JpegImageMetadata)meta; + if (jpegMetadata != null) { + TiffImageMetadata exif = jpegMetadata.getExif(); + if (exif != null) { + String dateTimeOriginal = exif.getFieldValue(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL)[0]; + long lastModifyTime = sdf.parse(dateTimeOriginal).getTime(); + param.argField.setText(dfjp.format(new Date(lastModifyTime))); + } + else { + param.argField.setText("exif == null"); + } + } + } + catch (IOException | ParseException | ImageReadException ex) {} + } + else { + long lastModified = timeFile.lastModified(); + param.argField.setText(sdf.format(new Date(lastModified))); + } + } + else { + param.argField.setText(""); + } + } + + @Override + public boolean isEnable() { + if (this.imageFile.isEnable()) { + String text = this.argField.getText(); + if (text != null) { + try { + sdf.applyPattern(Restamp.TIME_PATTERN); + sdf.parse(text); + return true; + } + catch (ParseException e) { + return false; + } + } + } + return false; + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/QuitDialog.java b/src/main/java/osm/jp/gpx/matchtime/gui/QuitDialog.java new file mode 100644 index 0000000..bf5b9e3 --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/QuitDialog.java @@ -0,0 +1,107 @@ +package osm.jp.gpx.matchtime.gui; + +import java.awt.Font; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.util.ResourceBundle; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; + +@SuppressWarnings("serial") +public class QuitDialog extends JDialog implements WindowListener +{ + JButton yesButton; + JButton noButton; + JLabel label1; + + @SuppressWarnings("OverridableMethodCallInConstructor") + public QuitDialog(JFrame parent, boolean modal) { + super(parent, modal); + + ResourceBundle i18n = ResourceBundle.getBundle("i18n"); + + addWindowListener((WindowListener) this); + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + + setLayout(null); + setSize(getInsets().left + getInsets().right + 337, getInsets().top + getInsets().bottom + 135); + + yesButton = new JButton(String.format(" %s ", i18n.getString("dialog.quit"))); + yesButton.addActionListener(new java.awt.event.ActionListener() { + @Override + public void actionPerformed(java.awt.event.ActionEvent evt) { + Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(new WindowEvent((Window)getParent(), 201)); + System.exit(0); + } + }); + yesButton.setBounds(getInsets().left + 72, getInsets().top + 80, 79, 22); + yesButton.setFont(new Font("Dialog", 1, 12)); + add(yesButton); + + noButton = new JButton(i18n.getString("dialog.cancel")); + noButton.addActionListener(new java.awt.event.ActionListener() { + @Override + public void actionPerformed(java.awt.event.ActionEvent evt) { + Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(new WindowEvent(QuitDialog.this, WindowEvent.WINDOW_CLOSING)); + setVisible(false); + } + }); + noButton.setBounds(getInsets().left + 185, getInsets().top + 80, 99, 22); + noButton.setFont(new Font("Dialog", 1, 12)); + add(noButton); + + label1 = new JLabel(i18n.getString("dialog.msg1"), JLabel.CENTER); + label1.setBounds(78, 33, 180, 23); + add(label1); + setTitle(i18n.getString("dialog.msg1")); + setResizable(false); + setVisible(true); + } + + @Override + public void setVisible(boolean b) { + if(b) { + Rectangle bounds = getParent().getBounds(); + Rectangle abounds = getBounds(); + setLocation(bounds.x + (bounds.width - abounds.width) / 2, bounds.y + (bounds.height - abounds.height) / 2); + } + super.setVisible(b); + } + + + @Override + public void windowActivated(WindowEvent e) { + } + + @Override + public void windowClosed(WindowEvent e) { + setVisible(false); + } + + @Override + public void windowClosing(WindowEvent e) { + setVisible(false); + } + + @Override + public void windowDeactivated(WindowEvent e) { + } + + @Override + public void windowDeiconified(WindowEvent e) { + } + + @Override + public void windowIconified(WindowEvent e) { + } + + @Override + public void windowOpened(WindowEvent e) { + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/SimpleDocumentListener.java b/src/main/java/osm/jp/gpx/matchtime/gui/SimpleDocumentListener.java new file mode 100644 index 0000000..735c6ee --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/SimpleDocumentListener.java @@ -0,0 +1,25 @@ +package osm.jp.gpx.matchtime.gui; + +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +@FunctionalInterface +public interface SimpleDocumentListener extends DocumentListener { + void update(DocumentEvent e); + + @Override + default void insertUpdate(DocumentEvent e) { + update(e); + } + + @Override + default void removeUpdate(DocumentEvent e) { + update(e); + } + + @Override + default void changedUpdate(DocumentEvent e) { + update(e); + } +} + diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/Utils.java b/src/main/java/osm/jp/gpx/matchtime/gui/Utils.java new file mode 100644 index 0000000..2f3880f --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/Utils.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Oracle or the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package osm.jp.gpx.matchtime.gui; + +import java.io.File; +import javax.swing.ImageIcon; + +/* Utils.java is used by FileChooserDemo2.java. */ +public class Utils { + + /** + * + */ + public final static String JPEG = "jpeg"; + public final static String JPG = "jpg"; + public final static String GIF = "gif"; + public final static String TIFF = "tiff"; + public final static String TIF = "tif"; + public final static String PNG = "png"; + + /* + * Get the extension of a file. + */ + public static String getExtension(File f) { + String ext = null; + String s = f.getName(); + int i = s.lastIndexOf('.'); + + if (i > 0 && i < s.length() - 1) { + ext = s.substring(i+1).toLowerCase(); + } + return ext; + } + + /** Returns an ImageIcon, or null if the path was invalid. + * @param path + * @return + */ + protected static ImageIcon createImageIcon(String path) { + java.net.URL imgURL = Utils.class.getResource(path); + if (imgURL != null) { + return new ImageIcon(imgURL); + } else { + System.err.println("Couldn't find file: " + path); + return null; + } + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/restamp/CardImageFile.java b/src/main/java/osm/jp/gpx/matchtime/gui/restamp/CardImageFile.java new file mode 100644 index 0000000..1c9de29 --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/restamp/CardImageFile.java @@ -0,0 +1,89 @@ +package osm.jp.gpx.matchtime.gui.restamp; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Window; +import javax.swing.BoxLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import static osm.jp.gpx.matchtime.gui.AdjustTerra.i18n; +import osm.jp.gpx.matchtime.gui.Card; +import osm.jp.gpx.matchtime.gui.PanelAction; +import osm.jp.gpx.matchtime.gui.ParameterPanelImageFile; +import osm.jp.gpx.matchtime.gui.ParameterPanelTime; + +/** + * [基準画像(開始/終了)]選択パネル + * @author yuu + */ +public class CardImageFile extends Card implements PanelAction { + ParameterPanelImageFile arg_baseTimeImg; + ParameterPanelTime arg_basetime; + + /** + * コンストラクタ + * @param tabbe parent panel + * @param arg_basetime // 開始画像の基準時刻: + * @param owner + * @param text + * @param pre + * @param next + */ + public CardImageFile( + JTabbedPane tabbe, + ParameterPanelTime arg_basetime, + Window owner, + String text, + int pre, int next + ) { + super(tabbe, text, pre, next); + arg_basetime.setOwner(owner); + this.arg_baseTimeImg = arg_basetime.getImageFile(); + this.arg_basetime = arg_basetime; + + JPanel argsPanel = new JPanel(); + argsPanel.setLayout(new BoxLayout(argsPanel, BoxLayout.PAGE_AXIS)); + argsPanel.add(packLine(new JLabel(i18n.getString("label.200")), new JPanel())); + argsPanel.add(arg_baseTimeImg); + + JPanel separater = new JPanel(); + separater.setMinimumSize(new Dimension(40, 20)); + argsPanel.add(separater); + + argsPanel.add(packLine(new JLabel(i18n.getString("label.300")), new JPanel())); + argsPanel.add(arg_basetime); + + // ラジオボタン: 「EXIF日時を基準にする」 + if (arg_basetime.exifBase != null) { + argsPanel.add(arg_basetime.exifBase); + } + + // ラジオボタン: 「File更新日時を基準にする」 + if (arg_basetime.fupdateBase != null) { + argsPanel.add(arg_basetime.fupdateBase); + } + + JPanel space = new JPanel(); + space.setMinimumSize(new Dimension(40, 20)); + space.setMaximumSize(new Dimension(40, Short.MAX_VALUE)); + argsPanel.add(space); + + this.mainPanel.add(argsPanel, BorderLayout.CENTER); + } + + /** + * 入力条件が満たされているかどうか + * @return + */ + @Override + public boolean isEnable() { + return (arg_baseTimeImg.isEnable() && arg_basetime.isEnable()); + } + + @Override + @SuppressWarnings("empty-statement") + public void openAction() { + ; // 何もしない + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/restamp/CardPerformFile.java b/src/main/java/osm/jp/gpx/matchtime/gui/restamp/CardPerformFile.java new file mode 100644 index 0000000..eb4aa82 --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/restamp/CardPerformFile.java @@ -0,0 +1,104 @@ +package osm.jp.gpx.matchtime.gui.restamp; + +import java.awt.BorderLayout; +import java.io.File; +import java.util.ArrayList; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import osm.jp.gpx.matchtime.gui.AdjustTerra; +import static osm.jp.gpx.matchtime.gui.AdjustTerra.i18n; +import osm.jp.gpx.matchtime.gui.Card; +import osm.jp.gpx.matchtime.gui.PanelAction; +import osm.jp.gpx.matchtime.gui.ParameterPanelTime; + +/** + * [基準画像(開始/終了)]選択パネル + * @author yuu + */ +public class CardPerformFile extends Card implements PanelAction { + //JPanel argsPanel; // パラメータ設定パネル (上部) + ParameterPanelTime arg1_basetime; + ParameterPanelTime arg2_basetime; + JButton doButton; // [処理実行]ボタン + + /** + * コンストラクタ + * @param tabbe parent panel + * @param arg1_basetime // 開始画像の基準時刻: + * @param arg2_basetime // 開始画像の基準時刻: + */ + public CardPerformFile( + JTabbedPane tabbe, + ParameterPanelTime arg1_basetime, + ParameterPanelTime arg2_basetime + ) { + super(tabbe, AdjustTerra.i18n.getString("tab.restamp.400"), 2, 4); + this.arg1_basetime = arg1_basetime; + this.arg2_basetime = arg2_basetime; + + JPanel argsPanel = new JPanel(); + argsPanel.setLayout(new BoxLayout(argsPanel, BoxLayout.PAGE_AXIS)); + argsPanel.add(packLine(new JLabel(i18n.getString("label.200")), new JPanel())); + + // [処理実行]ボタン + doButton = new JButton( + i18n.getString("button.execute"), + AdjustTerra.createImageIcon("/images/media_playback_start.png") + ); + argsPanel.add(doButton); + + this.mainPanel.add(argsPanel, BorderLayout.CENTER); + + //{{REGISTER_LISTENERS + SymAction lSymAction = new SymAction(); + doButton.addActionListener(lSymAction); + //}} + } + + class SymAction implements java.awt.event.ActionListener { + @Override + public void actionPerformed(java.awt.event.ActionEvent event) { + Object object = event.getSource(); + if (object == doButton) { + doButton_Action(event); + } + } + } + + /** + * [実行]ボタンをクリックしたときの動作 + * @param event + */ + @SuppressWarnings("UseSpecificCatch") + void doButton_Action(java.awt.event.ActionEvent event) { + ArrayList arry = new ArrayList<>(); + File file = arg1_basetime.getImageFile().getImageFile(); + File dir = file.getParentFile(); + arry.add(dir.getAbsolutePath()); + arry.add(file.getName()); + arry.add(arg1_basetime.argField.getText()); + file = arg2_basetime.getImageFile().getImageFile(); + arry.add(file.getName()); + arry.add(arg2_basetime.argField.getText()); + String[] argv = arry.toArray(new String[arry.size()]); + (new DoRestamp(argv)).setVisible(true); + } + + /** + * 入力条件が満たされているかどうか + * @return + */ + @Override + public boolean isEnable() { + return (arg1_basetime.isEnable() && arg2_basetime.isEnable()); + } + + @Override + @SuppressWarnings("empty-statement") + public void openAction() { + ; // 何もしない + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/restamp/CardSourceFolder.java b/src/main/java/osm/jp/gpx/matchtime/gui/restamp/CardSourceFolder.java new file mode 100644 index 0000000..1ddba59 --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/restamp/CardSourceFolder.java @@ -0,0 +1,51 @@ +package osm.jp.gpx.matchtime.gui.restamp; + +import java.awt.BorderLayout; +import javax.swing.BoxLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import osm.jp.gpx.matchtime.gui.AdjustTerra; +import static osm.jp.gpx.matchtime.gui.AdjustTerra.i18n; +import osm.jp.gpx.matchtime.gui.Card; +import osm.jp.gpx.matchtime.gui.PanelAction; +import osm.jp.gpx.matchtime.gui.ParameterPanelFolder; + +/** + * [対象フォルダ]設定パネル + * @author yuu + */ +public class CardSourceFolder extends Card implements PanelAction { + ParameterPanelFolder arg_srcFolder; // 対象フォルダ + + /** + * コンストラクタ + * @param tabbe parent panel + * @param arg_srcFolder 対象フォルダ + */ + public CardSourceFolder(JTabbedPane tabbe, ParameterPanelFolder arg_srcFolder) { + super(tabbe, AdjustTerra.i18n.getString("tab.100"), -1, 1); + this.arg_srcFolder = arg_srcFolder; + this.mainPanel.add(new JLabel(i18n.getString("label.100")), BorderLayout.NORTH); + + JPanel argsPanel = new JPanel(); // パラメータ設定パネル (上部) + argsPanel.setLayout(new BoxLayout(argsPanel, BoxLayout.Y_AXIS)); + argsPanel.add(arg_srcFolder); + this.mainPanel.add(argsPanel, BorderLayout.CENTER); + } + + /** + * 入力条件が満たされているかどうか + * @return + */ + @Override + public boolean isEnable() { + return this.arg_srcFolder.isEnable(); + } + + @Override + @SuppressWarnings("empty-statement") + public void openAction() { + ; // 何もしない + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/restamp/DialogCorectTime.java b/src/main/java/osm/jp/gpx/matchtime/gui/restamp/DialogCorectTime.java new file mode 100644 index 0000000..743c33a --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/restamp/DialogCorectTime.java @@ -0,0 +1,230 @@ +package osm.jp.gpx.matchtime.gui.restamp; + +import java.awt.BorderLayout; +import java.awt.Dialog; +import java.awt.GridLayout; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.Window; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import osm.jp.gpx.matchtime.gui.AdjustTerra; +import static osm.jp.gpx.matchtime.gui.AdjustTerra.createImageIcon; +import static osm.jp.gpx.matchtime.gui.AdjustTerra.i18n; +import osm.jp.gpx.matchtime.gui.PanelAction; +import osm.jp.gpx.matchtime.gui.ParameterPanelTime; + +/** + * [基準画像(開始)]選択パネル + * @author yuu + */ +public class DialogCorectTime extends JDialog implements PanelAction { + public JPanel mainPanel; + ParameterPanelTime arg_basetime; // 開始画像の基準時刻(parent) + ParameterPanelTime basetime; // 開始画像の基準時刻(tempolarry) + java.awt.Button closeButton; + JButton expandButton; + JButton zoomInButton; + JButton zoomOutButton; + JLabel imageLabel; // 開始画像の基準時刻画像表示 + JScrollPane imageSPane; // スクロールパネル + + /** + * コンストラクタ + * @param arg3_basetime 開始画像の基準時刻: + * @param owner + */ + @SuppressWarnings("OverridableMethodCallInConstructor") + public DialogCorectTime(ParameterPanelTime arg3_basetime, Window owner) { + super(owner, AdjustTerra.i18n.getString("tab.restamp.300"), Dialog.ModalityType.DOCUMENT_MODAL); + this.arg_basetime = arg3_basetime; + + // INIT_CONTROLS + setLayout(new BorderLayout()); + setSize( + getInsets().left + getInsets().right + 720, + getInsets().top + getInsets().bottom + 480 + ); + + //---- CENTER ----- + JPanel centerPanel = new JPanel(); + centerPanel.setLayout(new BorderLayout()); + add(centerPanel, BorderLayout.CENTER); + + //---- CENTER.NORTH ----- + JPanel argsPanel; // パラメータ設定パネル (上部) + argsPanel = new JPanel(); + argsPanel.setLayout(new GridLayout(2, 1)); + + // 3. 正確な撮影時刻を入力してください。 + // カメラの時計が正確ならば、設定を変更する必要はありません。 + JLabel label3 = new JLabel(); + label3.setText(i18n.getString("label.300")); + argsPanel.add(label3); + + basetime = new ParameterPanelTime( + arg_basetime.argLabel.getText(), + "", + arg_basetime.getImageFile() + ); + basetime.updateButton.setVisible(false); + basetime.resetButton.setVisible(true); + argsPanel.add(basetime); + centerPanel.add(argsPanel, BorderLayout.NORTH); + + //---- CENTER.CENTER ----- + // 参考画像 + imageLabel = new JLabel(); + imageSPane = new JScrollPane(imageLabel); + centerPanel.add(imageSPane, BorderLayout.CENTER); + + //---- CENTER.SOUTH ----- + // 画像ファイル選択ダイアログを起動するボタン + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); + expandButton = new JButton(createImageIcon("/images/Fit16.gif")); + buttonPanel.add(expandButton); + zoomInButton = new JButton(createImageIcon("/images/ZoomIn16.gif")); + buttonPanel.add(zoomInButton); + zoomOutButton = new JButton(createImageIcon("/images/ZoomOut16.gif")); + buttonPanel.add(zoomOutButton); + centerPanel.add(buttonPanel, BorderLayout.SOUTH); + + //---- SOUTH ----- + closeButton = new java.awt.Button(); + closeButton.setLabel(i18n.getString("button.close") ); + closeButton.setBounds(145,65,66,27); + add(closeButton, BorderLayout.SOUTH); + + // 選択された画像ファイルを表示する + imageView_Action(); + + //{{REGISTER_LISTENERS + SymWindow aSymWindow = new SymWindow(); + this.addWindowListener(aSymWindow); + SymAction lSymAction = new SymAction(); + closeButton.addActionListener(lSymAction); + expandButton.addActionListener(lSymAction); + zoomInButton.addActionListener(lSymAction); + zoomOutButton.addActionListener(lSymAction); + //}} + } + + class SymWindow extends java.awt.event.WindowAdapter + { + @Override + public void windowClosing(java.awt.event.WindowEvent event) { + Object object = event.getSource(); + if (object == DialogCorectTime.this) { + dialog_WindowClosing(); + } + } + } + + class SymAction implements java.awt.event.ActionListener + { + @Override + public void actionPerformed(java.awt.event.ActionEvent event) { + Object object = event.getSource(); + if (object == closeButton) { + dialog_WindowClosing(); + } + else if (object == expandButton) { + imageView_Action(); + } + else if (object == zoomInButton) { + zoomin_Action(); + } + else if (object == zoomOutButton) { + zoomout_Action(); + } + } + } + + ImageIcon refImage; + + /** + * 選択された画像ファイルを表示する + * 基準画像ボタンがクリックされた時に、基準時刻フィールドに基準画像の作成日時を設定する。 + */ + @SuppressWarnings("UseSpecificCatch") + public void imageView_Action() { + try { + String path = basetime.getImageFile().getImageFile().getAbsolutePath(); + + // View Image File + int size_x = imageSPane.getWidth() - 8; + ImageIcon tmpIcon = new ImageIcon(path); + refImage = tmpIcon; + if (tmpIcon.getIconWidth() > size_x) { + refImage = new ImageIcon(tmpIcon.getImage().getScaledInstance(size_x, -1, Image.SCALE_DEFAULT)); + } + imageLabel.setIcon(refImage); + } + catch(NullPointerException e) { + // 何もしない + } + repaint(); + } + + public void zoomin_Action() { + if (refImage != null) { + int size_x = imageLabel.getWidth(); + String path = basetime.getImageFile().getImageFile().getAbsolutePath(); + ImageIcon tmpIcon = new ImageIcon(path); + refImage = new ImageIcon(tmpIcon.getImage().getScaledInstance(size_x * 2, -1, Image.SCALE_DEFAULT)); + imageLabel.setIcon(refImage); + repaint(); + } + } + + public void zoomout_Action() { + if (refImage != null) { + int size_x = imageLabel.getWidth(); + ImageIcon tmpIcon = refImage; + refImage = new ImageIcon(tmpIcon.getImage().getScaledInstance(size_x / 2, -1, Image.SCALE_DEFAULT)); + imageLabel.setIcon(refImage); + repaint(); + } + } + + /** + * ダイアログが閉じられるときのアクション + */ + void dialog_WindowClosing() { + String workStr = basetime.getText(); + arg_basetime.setText(workStr); + dispose(); + } + + @Override + public void setVisible(boolean b) { + if(b) { + Rectangle bounds = getParent().getBounds(); + Rectangle abounds = getBounds(); + setLocation(bounds.x + (bounds.width - abounds.width)/ 2, + bounds.y + (bounds.height - abounds.height)/2); + } + super.setVisible(b); + } + + @Override + @SuppressWarnings("empty-statement") + public void openAction() { + ; // 何もしない + } + + /** + * 入力条件が満たされているかどうか + * @return + */ + @Override + public boolean isEnable() { + return this.basetime.isEnable(); + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/restamp/DoRestamp.java b/src/main/java/osm/jp/gpx/matchtime/gui/restamp/DoRestamp.java new file mode 100644 index 0000000..27d355a --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/restamp/DoRestamp.java @@ -0,0 +1,231 @@ +package osm.jp.gpx.matchtime.gui.restamp; +import osm.jp.gpx.matchtime.gui.Command; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.io.*; +import javax.swing.*; + +/** + * 処理 + */ +@SuppressWarnings("serial") +public class DoRestamp extends JDialog { + public static final String TITLE = "Do Command"; + + // Used for addNotify check. + boolean fComponentsAdjusted = false; + String[] args; + + //{{DECLARE_CONTROLS + JPanel buttonPanel; // ボタン配置パネル (下部) + JButton closeButton; // [クローズ]ボタン + JButton doButton; // [実行]ボタン + JTextArea textArea; // 実行結果を表示するJTextArea (中央) + //}} + + @SuppressWarnings("OverridableMethodCallInConstructor") + public DoRestamp(String[] args) { + super(); // 親フォームなしのモーダルダイアログを基盤にする + this.args = args; + + // INIT_CONTROLS + @SuppressWarnings("OverridableMethodCallInConstructor") + Container container = getContentPane(); + container.setLayout(new BorderLayout()); + //parentFrame.setVisible(false); + setSize(getInsets().left + getInsets().right + 980, getInsets().top + getInsets().bottom + 480); + setTitle(DoRestamp.TITLE); + + // コントロールパネル + buttonPanel = new JPanel(); + + doButton = new JButton("実行"); + doButton.setToolTipText("処理を実行します."); + doButton.setEnabled(true); + doButton.addActionListener((ActionEvent event) -> { + // 処理中であることを示すため + // ボタンの文字列を変更し,使用不可にする + doButton.setText("処理中..."); + doButton.setEnabled(false); + + // SwingWorker を生成し,実行する + LongTaskWorker worker = new LongTaskWorker(doButton); + worker.execute(); + }); + buttonPanel.add(doButton); + + closeButton = new JButton("閉じる"); + closeButton.setToolTipText("処理を終了します."); + closeButton.addActionListener((ActionEvent event) -> { + dispose(); + }); + buttonPanel.add(closeButton); + + this.getContentPane().add("South", buttonPanel); + + // 説明文 + textArea = new JTextArea(); + JScrollPane sc=new JScrollPane(textArea); + textArea.setFont(new Font(Font.MONOSPACED,Font.PLAIN,12)); + textArea.setTabSize(4); + this.getContentPane().add("Center", sc); + + try { + textArea.append("> java -jar AdjustTime.jar osm.jp.gpx.Restamp"); + for (String arg : args) { + textArea.append(" '" + arg + "'"); + } + textArea.append("\n\n"); + } + catch (Exception e) { + System.out.println(e.toString()); + } + + // JFrameの表示 + //parentFrame.setVisible(true); + } + + /** + * Shows or hides the component depending on the boolean flag b. + * @param b trueのときコンポーネントを表示; その他のとき, componentを隠す. + * @see java.awt.Component#isVisible + */ + @Override + public void setVisible(boolean b) { + if(b) { + setLocation(80, 80); + } + super.setVisible(b); + } + + @Override + public void addNotify() { + // Record the size of the window prior to calling parents addNotify. + Dimension d = getSize(); + + super.addNotify(); + + if (fComponentsAdjusted) { + return; + } + + // Adjust components according to the insets + setSize(getInsets().left + getInsets().right + d.width, getInsets().top + getInsets().bottom + d.height); + Component components[] = getComponents(); + for (Component component : components) { + Point p = component.getLocation(); + p.translate(getInsets().left, getInsets().top); + component.setLocation(p); + } + fComponentsAdjusted = true; + } + + + /** + * JTextAreaに書き出すOutputStream + */ + public static class JTextAreaOutputStream extends OutputStream { + private final ByteArrayOutputStream os; + + /** 書き出し対象 */ + private final JTextArea textArea; + private final String encode; + + public JTextAreaOutputStream(JTextArea textArea, String encode) { + this.textArea = textArea; + this.encode = encode; + this.os = new ByteArrayOutputStream(); + } + + /** + * OutputStream#write(byte[])のオーバーライド + * @param arg + * @throws java.io.IOException + */ + @Override + public void write(int arg) throws IOException { + this.os.write(arg); + } + + /** + * flush()でJTextAreaに書き出す + * @throws java.io.IOException + */ + @Override + public void flush() throws IOException { + // 文字列のエンコード + final String str = new String(this.os.toByteArray(), this.encode); + // 実際の書き出し処理 + SwingUtilities.invokeLater( + new Runnable(){ + @Override + public void run() { + JTextAreaOutputStream.this.textArea.append(str); + } + } + ); + // 書き出した内容はクリアする + this.os.reset(); + } + } + + // 非同期に行う処理を記述するためのクラス + class LongTaskWorker extends SwingWorker { + private final JButton button; + + public LongTaskWorker(JButton button) { + this.button = button; + } + + // 非同期に行われる処理 + @Override + @SuppressWarnings("SleepWhileInLoop") + public Object doInBackground() { + // ながーい処理 + PrintStream defOut = System.out; + PrintStream defErr = System.err; + + OutputStream os = new JTextAreaOutputStream(textArea, "UTF-8"); + PrintStream stdout = new PrintStream(os, true); // 自動flushをtrueにしておく + + // System.out にJTextAreaOutputStreamに書き出すPrintStreamを設定 + System.setOut(stdout); + System.setErr(stdout); + + try { + Command command = new Command(osm.jp.gpx.Restamp.class); + command.setArgs(args); + command.start(); // コマンドを実行 + while (command.isAlive()) { + try { + Thread.sleep(10); + } catch (InterruptedException e) {} + } + } + catch(Exception e) { + e.printStackTrace(stdout); + } + finally { + System.setOut(defOut); + System.setErr(defErr); + done(); + } + + return null; + } + + // 非同期処理後に実行 + @Override + protected void done() { + // 処理が終了したので,文字列を元に戻し + // ボタンを使用可能にする + button.setText("実行"); + button.setEnabled(true); + } + } +} diff --git a/src/main/java/osm/jp/gpx/matchtime/gui/restamp/RestampDialog.java b/src/main/java/osm/jp/gpx/matchtime/gui/restamp/RestampDialog.java new file mode 100644 index 0000000..d03eaa9 --- /dev/null +++ b/src/main/java/osm/jp/gpx/matchtime/gui/restamp/RestampDialog.java @@ -0,0 +1,271 @@ +package osm.jp.gpx.matchtime.gui.restamp; +import java.awt.*; +import java.io.IOException; +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import osm.jp.gpx.AppParameters; +import osm.jp.gpx.matchtime.gui.AdjustTerra; +import osm.jp.gpx.matchtime.gui.Card; +import osm.jp.gpx.matchtime.gui.ParameterPanelFolder; +import osm.jp.gpx.matchtime.gui.ParameterPanelImageFile; +import static osm.jp.gpx.matchtime.gui.AdjustTerra.i18n; +import osm.jp.gpx.matchtime.gui.ParameterPanelTime; +import osm.jp.gpx.matchtime.gui.SimpleDocumentListener; + +@SuppressWarnings("serial") +public class RestampDialog extends JDialog +{ + //{{DECLARE_CONTROLS + java.awt.Button closeButton; + JTabbedPane cardPanel; // ウィザード形式パネル(タブ型) + Card[] cards; + ParameterPanelFolder arg1_srcFolder; // 対象フォルダ + ParameterPanelImageFile arg2_baseTimeImg; // 開始画像ファイルパス + ParameterPanelTime arg2_basetime; // 開始画像の基準時刻: + ParameterPanelImageFile arg3_baseTimeImg; // 終了画像ファイルパス + ParameterPanelTime arg3_basetime; // 終了画像の基準時刻: + AppParameters params; + //}} + + class SymWindow extends java.awt.event.WindowAdapter + { + @Override + public void windowClosing(java.awt.event.WindowEvent event) { + Object object = event.getSource(); + if (object == RestampDialog.this) { + dispose(); + } + } + } + + class SymAction implements java.awt.event.ActionListener + { + @Override + public void actionPerformed(java.awt.event.ActionEvent event) { + Object object = event.getSource(); + if (object == closeButton) { + dispose(); + } + } + } + + @SuppressWarnings("OverridableMethodCallInConstructor") + public RestampDialog(Frame parent, boolean modal) throws IOException { + super(parent, modal); + + // INIT_CONTROLS + setLayout(new BorderLayout()); + setSize( + getInsets().left + getInsets().right + 720, + getInsets().top + getInsets().bottom + 480 + ); + setTitle(i18n.getString("menu.restamp") + "... "); + + //---- CENTER ----- + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + add(mainPanel, BorderLayout.CENTER); + + //---- SOUTH ----- + JPanel southPanel = new JPanel(new BorderLayout()); + southPanel.add(Box.createVerticalStrut(10), BorderLayout.SOUTH); + southPanel.add(Box.createVerticalStrut(10), BorderLayout.NORTH); + add(southPanel, BorderLayout.SOUTH); + + //---- SPACE ----- + add(Box.createVerticalStrut(30), BorderLayout.NORTH); + add(Box.createHorizontalStrut(10), BorderLayout.WEST); + add(Box.createHorizontalStrut(10), BorderLayout.EAST); + + closeButton = new java.awt.Button(); + closeButton.setLabel(i18n.getString("button.close") ); + closeButton.setBounds(145,65,66,27); + southPanel.add(closeButton); + //}} + + //--------------------------------------------------------------------- + params = new AppParameters(); + cards = new Card[4]; + cardPanel = new JTabbedPane(JTabbedPane.LEFT); + mainPanel.add(cardPanel, BorderLayout.CENTER); + int cardNo = 0; + + //--------------------------------------------------------------------- + // 1.[対象フォルダ]設定パネル + { + arg1_srcFolder = new ParameterPanelFolder( + i18n.getString("label.110") +": ", + params.getProperty(AppParameters.IMG_SOURCE_FOLDER) + ); + arg1_srcFolder.argField + .getDocument() + .addDocumentListener( + new SimpleDocumentListener() { + @Override + public void update(DocumentEvent e) { + toEnable(0, arg1_srcFolder.isEnable()); + } + } + ); + + Card card = new CardSourceFolder(cardPanel, arg1_srcFolder); + cardPanel.addTab(card.getTitle(), card); + cardPanel.setEnabledAt(cardNo, true); + cards[cardNo] = card; + cardNo++; + } + + //--------------------------------------------------------------------- + // 2. [基準画像(開始)]選択パネル + { + // 基準時刻画像 + arg2_baseTimeImg = new ParameterPanelImageFile( + i18n.getString("label.210") +": ", + null, + arg1_srcFolder + ); + + // 2a. 基準時刻: + arg2_basetime = new ParameterPanelTime( + i18n.getString("label.310"), + null, + arg2_baseTimeImg + ); + arg2_basetime.argField.getDocument().addDocumentListener( + new SimpleDocumentListener() { + @Override + public void update(DocumentEvent e) { + toEnable(1, arg2_basetime.isEnable()); + } + } + ); + + CardImageFile card = new CardImageFile( + cardPanel, arg2_basetime, (Window)this, + AdjustTerra.i18n.getString("tab.restamp.200"), 0, 2); + cardPanel.addTab(card.getTitle(), card); + cardPanel.setEnabledAt(cardNo, false); + cards[cardNo] = card; + cardNo++; + } + + //--------------------------------------------------------------------- + // 3. 最終画像の本当の時刻を設定の入力画面 + { + // 基準時刻画像 + arg3_baseTimeImg = new ParameterPanelImageFile( + i18n.getString("label.210") +": ", + null, + arg1_srcFolder + ); + + // 3a. 基準時刻: + arg3_basetime = new ParameterPanelTime( + i18n.getString("label.310"), + null, + arg3_baseTimeImg + ); + arg3_basetime.argField.getDocument().addDocumentListener( + new SimpleDocumentListener() { + @Override + public void update(DocumentEvent e) { + toEnable(2, arg3_basetime.isEnable()); + } + } + ); + + CardImageFile card = new CardImageFile( + cardPanel, arg3_basetime, (Window)this, + AdjustTerra.i18n.getString("tab.restamp.250"), 1, 3 + ); + cardPanel.addTab(card.getTitle(), card); + cardPanel.setEnabledAt(cardNo, false); + cards[cardNo] = card; + cardNo++; + } + + //--------------------------------------------------------------------- + // 4. 実行画面 + { + CardPerformFile card = new CardPerformFile( + cardPanel, + arg2_basetime, + arg3_basetime + ); + cardPanel.addTab(card.getTitle(), card); + cardPanel.setEnabledAt(cardNo, false); + cards[cardNo] = card; + cardNo++; + } + + //{{REGISTER_LISTENERS + SymWindow aSymWindow = new SymWindow(); + this.addWindowListener(aSymWindow); + SymAction lSymAction = new SymAction(); + closeButton.addActionListener(lSymAction); + //}} + } + + + void toEnable(final int cardNo, final boolean enable) { + if ((cardNo >= 0) && (cardNo < cards.length)) { + cardPanel.setEnabledAt(cardNo, enable); + if ((cardNo -1) >= 0) { + cards[cardNo -1].nextButton.setEnabled(enable); + } + if ((cardNo +1) < cards.length) { + cardPanel.setEnabledAt(cardNo+1, enable); + cards[cardNo +1].backButton.setEnabled(enable); + cards[cardNo].nextButton.setEnabled(enable); + } + } + } + + @SuppressWarnings("OverridableMethodCallInConstructor") + public RestampDialog(Frame parent, String title, boolean modal) throws IOException { + this(parent, modal); + setTitle(title); + } + + // Used for addNotify redundency check. + boolean fComponentsAdjusted = false; + + @Override + public void addNotify() { + // Record the size of the window prior to calling parents addNotify. + super.addNotify(); + + // Only do this once. + if (fComponentsAdjusted) { + return; + } + + // Adjust components according to the insets + setSize(getInsets().left + getInsets().right + getSize().width, getInsets().top + getInsets().bottom + getSize().height); + Component components[] = getComponents(); + for (Component component : components) { + Point p = component.getLocation(); + p.translate(getInsets().left, getInsets().top); + component.setLocation(p); + } + + // Used for addNotify check. + fComponentsAdjusted = true; + } + + /** + * Shows or hides the component depending on the boolean flag b. + * @param b if true, show the component; otherwise, hide the component. + * @see java.awt.Component#isVisible + */ + @Override + public void setVisible(boolean b) { + if(b) { + Rectangle bounds = getParent().getBounds(); + Rectangle abounds = getBounds(); + setLocation(bounds.x + (bounds.width - abounds.width)/ 2, + bounds.y + (bounds.height - abounds.height)/2); + } + super.setVisible(b); + } +} diff --git a/src/main/resources/AdjustTerra.bat b/src/main/resources/AdjustTerra.bat new file mode 100644 index 0000000..c1d573e --- /dev/null +++ b/src/main/resources/AdjustTerra.bat @@ -0,0 +1 @@ +javaw -cp AdjustTerra.jar osm.jp.gpx.matchtime.gui.AdjustTerra diff --git a/src/main/resources/AdjustTerra.sh b/src/main/resources/AdjustTerra.sh new file mode 100644 index 0000000..2993ed7 --- /dev/null +++ b/src/main/resources/AdjustTerra.sh @@ -0,0 +1,2 @@ +javaw -cp AdjustTerra.jar osm.jp.gpx.matchtime.gui.AdjustTerra + diff --git a/src/main/resources/AdjustTime.ini b/src/main/resources/AdjustTime.ini new file mode 100644 index 0000000..3d439b1 --- /dev/null +++ b/src/main/resources/AdjustTime.ini @@ -0,0 +1,13 @@ +#by AdjustTime +GPX.BASETIME=EXIF_TIME +IMG.OUTPUT_EXIF=true +GPX.OUTPUT_SPEED=false +GPX.noFirstNode=true +IMG.OUTPUT=false +GPX.gpxSplit=true +IMG.TIME=2016-08-14T11\:45\:47 +GPX.REUSE=true +IMG.BASE_FILE=IMG_0182.jpg +IMG.SOURCE_FOLDER=. +GPX.SOURCE_FOLDER=. +IMG.OUTPUT_FOLDER=. \ No newline at end of file diff --git a/src/main/resources/LICENSE.txt b/src/main/resources/LICENSE.txt new file mode 100644 index 0000000..a37e276 --- /dev/null +++ b/src/main/resources/LICENSE.txt @@ -0,0 +1,43 @@ +The MIT License (MIT) + +Copyright (c) 2014 Yuu Hayashi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +以下に定める条件に従い、本ソフトウェアおよび関連文書のファイル(以下「ソフトウェア」)の複製を取得するすべて +の人に対し、ソフトウェアを無制限に扱うことを無償で許可します。これには、ソフトウェアの複製を使用、複写、変 +更、結合、掲載、頒布、サブライセンス、および/または販売する権利、およびソフトウェアを提供する相手に同じこ +とを許可する権利も無制限に含まれます。 + +上記の著作権表示および本許諾表示を、ソフトウェアのすべての複製または重要な部分に記載するものとします。 + +ソフトウェアは「現状のまま」で、明示であるか暗黙であるかを問わず、何らの保証もなく提供されます。ここでいう保証 +とは、商品性、特定の目的への適合性、および権利非侵害についての保証も含みますが、それに限定されるもので +はありません。 作者または著作権者は、契約行為、不法行為、またはそれ以外であろうと、ソフトウェアに起因または +関連し、あるいはソフトウェアの使用またはその他の扱いによって生じる一切の請求、損害、その他の義務について何 +らの責任も負わないものとします。 + +---------------- + +osm.jp.gpx.GeoDistance.java は'やまだらけ'様の著作物です。 + Copyright (C) 2007-2012 やまだらけ + The MIT License (MIT) + 参照元: http://yamadarake.jp/trdi/report000001.html + 「Cords.java」を改変 diff --git a/src/main/resources/README.jp.txt b/src/main/resources/README.jp.txt new file mode 100644 index 0000000..cc3890f --- /dev/null +++ b/src/main/resources/README.jp.txt @@ -0,0 +1,120 @@ +[ AdjustTerra ] + +GPSログファイル(GPX)を元にして写真へ「位置情報(緯度経度)」と「方向」を追記します。(EXIF更新) + +[概要] +GPSログの記録時刻とデジカメの撮影時刻とを見比べて、GPSログ内に写真へのリンク情報を付加した新しいGPSログファイルを作成します。 + + ※ 対象とする画像ファイルは'*.jpg'のみです。 + ※ GPSログの形式は「GPX」形式に対応しています。 + * 画像ファイルの撮影日時をファイルの更新日時/EXIF撮影日時から選択することができます。 + - ファイル更新日時: 高速処理が可能です。 + 一部のトイカメラ系のデジカメにはEXIF情報が正しく付加されないものがあります。そのような機種におすすめです。 + - EXIF撮影日時: ファイル更新日時が利用できない場合はこちらを使ってください。 + iPadなど直接ファイルを扱えないデバイスの場合はファイル更新日時が使えません。 + うっかりファイルをコピーしてしまった場合は、ファイル更新日時が撮影日時を意味しなくなります。その時もEXIFにしてください。 + * 画像の精確な撮影時刻を入力することでGPSログとの時差を自動補正します。 + * 結果は、取り込み元のGPXファイルとは別に、元ファイル名にアンダーバー「_」を付加した.ファイルに出力します。 + - SPEED(速度): 出力GPXにタグを付加することができます。 + - MAGVAR(方向): 'MAGVAR'とは磁気方位のことです。直前のポイントとの2点間の位置関係を'MAGVAR'として出力できます。 + - 出力先のGPXに写真へのリンク情報を付加する/付加しないを選択可能にしました。 + [☑ 出力GPXにポイントマーカーを書き出す] + * 画像にEXIF情報を付加することができます。 + - 緯度経度: GPSログから算出した緯度・経度情報をEXIFに書き出すことができます。 + - 撮影方向: GPSログから移動方向を擬似撮影方向としてEXIFに書き出すことができます。(カメラの向きではありません) + +http://sourceforge.jp/projects/importpicture/wiki/FrontPage + +[起動] +下記のように'AdjustTerra'を起動するとGUIでパラメータを逐次設定可能です。(推奨起動方法) + +> java -cp AdjustTerra.jar osm.jp.gpx.matchtime.gui.AdjustTerra + + +下記のコマンドラインによる起動方式は度重なる機能追加によりパラメーターが増大したため複雑になりすぎ作者でさえわけがわからなくなりました。 +一応、過去の起動方法を記載しておきます。しかし、コマンドラインからの引数は2016-10-03版以降は正しく引き継がれません。 +GUI版の'AdjustTerra.jar'を使ってください。 + +> java -jar importPicture.jar