- Timestamp:
- 2018-08-30T23:15:06+02:00 (6 years ago)
- Location:
- trunk/src/org/openstreetmap/josm
- Files:
-
- 1 added
- 3 edited
- 1 copied
- 2 moved
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java
r14204 r14205 1 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui.layer.geoimage; 3 4 import java.awt.Image; 2 package org.openstreetmap.josm.data.gpx; 3 5 4 import java.io.File; 6 5 import java.io.IOException; 7 import java.util.Collections;8 6 import java.util.Date; 9 7 … … 25 23 /** 26 24 * Stores info about each image 25 * @since 14205 (extracted from gui.layer.geoimage.ImageEntry) 27 26 */ 28 public final classImageEntry implements Comparable<ImageEntry>, Cloneable {27 public class GpxImageEntry implements Comparable<GpxImageEntry>, Cloneable { 29 28 private File file; 30 29 private Integer exifOrientation; … … 40 39 /** Temporary source of GPS time if not correlated with GPX track. */ 41 40 private Date exifGpsTime; 42 private Image thumbnail;43 41 44 42 /** … … 65 63 * solution for this, but it works.) 66 64 */ 67 ImageEntry tmp; 68 69 /** 70 * Constructs a new {@code ImageEntry}. 71 */ 72 public ImageEntry() {} 73 74 /** 75 * Constructs a new {@code ImageEntry}. 65 private GpxImageEntry tmp; 66 67 /** 68 * Constructs a new {@code GpxImageEntry}. 69 */ 70 public GpxImageEntry() {} 71 72 /** 73 * Constructs a new {@code GpxImageEntry}. 76 74 * @param file Path to image file on disk 77 75 */ 78 public ImageEntry(File file) { 76 public GpxImageEntry(File file) { 79 77 setFile(file); 80 78 } 81 79 82 80 /** 83 * Returns width of the image this ImageEntry represents. 84 * @return width of the image this ImageEntry represents 81 * Returns width of the image this GpxImageEntry represents. 82 * @return width of the image this GpxImageEntry represents 85 83 * @since 13220 86 84 */ … … 90 88 91 89 /** 92 * Returns height of the image this ImageEntry represents. 93 * @return height of the image this ImageEntry represents 90 * Returns height of the image this GpxImageEntry represents. 91 * @return height of the image this GpxImageEntry represents 94 92 * @since 13220 95 93 */ … … 219 217 220 218 /** 221 * Determines whether a thumbnail is set 222 * @return {@code true} if a thumbnail is set 223 */ 224 public boolean hasThumbnail() { 225 return thumbnail != null; 226 } 227 228 /** 229 * Returns the thumbnail. 230 * @return the thumbnail 231 */ 232 public Image getThumbnail() { 233 return thumbnail; 234 } 235 236 /** 237 * Sets the thumbnail. 238 * @param thumbnail thumbnail 239 */ 240 public void setThumbnail(Image thumbnail) { 241 this.thumbnail = thumbnail; 242 } 243 244 /** 245 * Loads the thumbnail if it was not loaded yet. 246 * @see ThumbsLoader 247 */ 248 public void loadThumbnail() { 249 if (thumbnail == null) { 250 new ThumbsLoader(Collections.singleton(this)).run(); 251 } 252 } 253 254 /** 255 * Sets the width of this ImageEntry. 256 * @param width set the width of this ImageEntry 219 * Sets the width of this GpxImageEntry. 220 * @param width set the width of this GpxImageEntry 257 221 * @since 13220 258 222 */ … … 262 226 263 227 /** 264 * Sets the height of this ImageEntry. 265 * @param height set the height of this ImageEntry 228 * Sets the height of this GpxImageEntry. 229 * @param height set the height of this GpxImageEntry 266 230 * @since 13220 267 231 */ … … 348 312 349 313 @Override 350 public ImageEntry clone() { 351 try { 352 return (ImageEntry) super.clone(); 314 public GpxImageEntry clone() { 315 try { 316 return (GpxImageEntry) super.clone(); 353 317 } catch (CloneNotSupportedException e) { 354 318 throw new IllegalStateException(e); … … 357 321 358 322 @Override 359 public int compareTo(ImageEntry image) { 323 public int compareTo(GpxImageEntry image) { 360 324 if (exifTime != null && image.exifTime != null) 361 325 return exifTime.compareTo(image.exifTime); … … 385 349 * @return temporary variable 386 350 */ 387 public ImageEntry getTmp() { 351 public GpxImageEntry getTmp() { 388 352 if (tmp == null) { 389 353 createTmp(); -
trunk/src/org/openstreetmap/josm/data/gpx/GpxTimeOffset.java
r14204 r14205 1 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm. gui.layer.geoimage;2 package org.openstreetmap.josm.data.gpx; 3 3 4 4 import static org.openstreetmap.josm.tools.I18n.tr; … … 14 14 /** 15 15 * Time offset of GPX correlation. 16 * @since 1 1914(extracted from {@linkCorrelateGpxWithImages})16 * @since 14205 (extracted from {@code CorrelateGpxWithImages}) 17 17 */ 18 public final class Offset { 18 public final class GpxTimeOffset { 19 19 20 static final Offset ZERO = new Offset(0); 20 /** 21 * The time offset 0. 22 */ 23 public static final GpxTimeOffset ZERO = new GpxTimeOffset(0); 21 24 private final long milliseconds; 22 25 23 private Offset(long milliseconds) { 26 private GpxTimeOffset(long milliseconds) { 24 27 this.milliseconds = milliseconds; 25 28 } 26 29 27 static Offset milliseconds(long milliseconds) { 28 return new Offset(milliseconds); 30 /** 31 * Constructs a new {@code GpxTimeOffset} from milliseconds. 32 * @param milliseconds time offset in milliseconds. 33 * @return new {@code GpxTimeOffset} 34 */ 35 public static GpxTimeOffset milliseconds(long milliseconds) { 36 return new GpxTimeOffset(milliseconds); 29 37 } 30 38 31 static Offset seconds(long seconds) { 32 return new Offset(1000 * seconds); 39 /** 40 * Constructs a new {@code GpxTimeOffset} from seconds. 41 * @param seconds time offset in seconds. 42 * @return new {@code GpxTimeOffset} 43 */ 44 public static GpxTimeOffset seconds(long seconds) { 45 return new GpxTimeOffset(1000 * seconds); 33 46 } 34 47 35 long getMilliseconds() { 48 /** 49 * Get time offset in milliseconds. 50 * @return time offset in milliseconds 51 */ 52 public long getMilliseconds() { 36 53 return milliseconds; 37 54 } 38 55 39 long getSeconds() { 56 /** 57 * Get time offset in seconds. 58 * @return time offset in seconds 59 */ 60 public long getSeconds() { 40 61 return milliseconds / 1000; 41 62 } 42 63 43 String formatOffset() { 64 /** 65 * Formats time offset. 66 * @return formatted time offset. Format: decimal number 67 */ 68 public String formatOffset() { 44 69 if (milliseconds % 1000 == 0) { 45 70 return Long.toString(milliseconds / 1000); … … 51 76 } 52 77 53 static Offset parseOffset(String offset) throws ParseException { 78 /** 79 * Parses time offset. 80 * @param offset time offset. Format: decimal number 81 * @return time offset 82 * @throws ParseException if time offset can't be parsed 83 */ 84 public static GpxTimeOffset parseOffset(String offset) throws ParseException { 54 85 String error = tr("Error while parsing offset.\nExpected format: {0}", "number"); 55 86 … … 59 90 offset = offset.substring(1); 60 91 } 61 return Offset.milliseconds(Math.round(JosmDecimalFormatSymbolsProvider.parseDouble(offset) * 1000)); 92 return GpxTimeOffset.milliseconds(Math.round(JosmDecimalFormatSymbolsProvider.parseDouble(offset) * 1000)); 62 93 } catch (NumberFormatException nfe) { 63 94 throw (ParseException) new ParseException(error, 0).initCause(nfe); 64 95 } 65 96 } else { 66 return Offset.ZERO; 97 return GpxTimeOffset.ZERO; 67 98 } 68 99 } 69 100 70 int getDayOffset() { 101 /** 102 * Returns the day difference. 103 * @return the day difference 104 */ 105 public int getDayOffset() { 71 106 // Find day difference 72 107 return (int) Math.round(((double) getMilliseconds()) / TimeUnit.DAYS.toMillis(1)); 73 108 } 74 109 75 Offset withoutDayOffset() { 110 /** 111 * Returns offset without day difference. 112 * @return offset without day difference 113 */ 114 public GpxTimeOffset withoutDayOffset() { 76 115 return milliseconds(getMilliseconds() - TimeUnit.DAYS.toMillis(getDayOffset())); 77 116 } 78 117 79 Pair<Timezone, Offset> splitOutTimezone() { 118 /** 119 * Split out timezone and offset. 120 * @return pair of timezone and offset 121 */ 122 public Pair<GpxTimezone, GpxTimeOffset> splitOutTimezone() { 80 123 // In hours 81 124 final double tz = ((double) withoutDayOffset().getSeconds()) / TimeUnit.HOURS.toSeconds(1); … … 85 128 final double timezone = (double) Math.round(tz * 2) / 2; // hours, rounded to one decimal place 86 129 final long delta = Math.round(getMilliseconds() - timezone * TimeUnit.HOURS.toMillis(1)); 87 return Pair.create(new Timezone(timezone), Offset.milliseconds(delta)); 130 return Pair.create(new GpxTimezone(timezone), GpxTimeOffset.milliseconds(delta)); 88 131 } 89 132 … … 91 134 public boolean equals(Object o) { 92 135 if (this == o) return true; 93 if (!(o instanceof Offset)) return false; 94 Offset offset = (Offset) o; 136 if (!(o instanceof GpxTimeOffset)) return false; 137 GpxTimeOffset offset = (GpxTimeOffset) o; 95 138 return milliseconds == offset.milliseconds; 96 139 } -
trunk/src/org/openstreetmap/josm/data/gpx/GpxTimezone.java
r14204 r14205 1 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm. gui.layer.geoimage;2 package org.openstreetmap.josm.data.gpx; 3 3 4 4 import static org.openstreetmap.josm.tools.I18n.tr; … … 6 6 import java.text.ParseException; 7 7 import java.util.Objects; 8 import java.util.regex.Matcher; 9 import java.util.regex.Pattern; 8 10 9 11 /** 10 12 * Timezone in hours.<p> 11 13 * TODO: should probably be replaced by {@link java.util.TimeZone}. 12 * @since 1 1914(extracted from {@linkCorrelateGpxWithImages})14 * @since 14205 (extracted from {@code CorrelateGpxWithImages}) 13 15 */ 14 public final class Timezone { 16 public final class GpxTimezone { 15 17 16 static final Timezone ZERO = new Timezone(0.0); 18 /** 19 * The timezone 0. 20 */ 21 public static final GpxTimezone ZERO = new GpxTimezone(0.0); 17 22 private final double timezone; 18 23 19 Timezone(double hours) { 24 /** 25 * Construcs a new {@code GpxTimezone}. 26 * @param hours timezone in hours 27 */ 28 public GpxTimezone(double hours) { 20 29 this.timezone = hours; 21 30 } … … 29 38 } 30 39 31 String formatTimezone() { 40 /** 41 * Formats time zone. 42 * @return formatted time zone. Format: ±HH:MM 43 */ 44 public String formatTimezone() { 32 45 StringBuilder ret = new StringBuilder(); 33 46 … … 49 62 } 50 63 51 static Timezone parseTimezone(String timezone) throws ParseException { 52 64 /** 65 * Parses timezone. 66 * @param timezone timezone. Expected format: ±HH:MM 67 * @return timezone 68 * @throws ParseException if timezone can't be parsed 69 */ 70 public static GpxTimezone parseTimezone(String timezone) throws ParseException { 53 71 if (timezone.isEmpty()) 54 72 return ZERO; 55 73 56 String error = tr("Error while parsingtimezone.\nExpected format: {0}", "+H:MM");74 Matcher m = Pattern.compile("^([\\+\\-]?)(\\d{1,2})(?:\\:([0-5]\\d))?$").matcher(timezone); 57 75 58 char sgnTimezone = '+'; 59 StringBuilder hTimezone = new StringBuilder(); 60 StringBuilder mTimezone = new StringBuilder(); 61 int state = 1; // 1=start/sign, 2=hours, 3=minutes. 62 for (int i = 0; i < timezone.length(); i++) { 63 char c = timezone.charAt(i); 64 switch (c) { 65 case ' ': 66 if (state != 2 || hTimezone.length() != 0) 67 throw new ParseException(error, i); 68 break; 69 case '+': 70 case '-': 71 if (state == 1) { 72 sgnTimezone = c; 73 state = 2; 74 } else 75 throw new ParseException(error, i); 76 break; 77 case ':': 78 case '.': 79 if (state == 2) { 80 state = 3; 81 } else 82 throw new ParseException(error, i); 83 break; 84 case '0': 85 case '1': 86 case '2': 87 case '3': 88 case '4': 89 case '5': 90 case '6': 91 case '7': 92 case '8': 93 case '9': 94 switch (state) { 95 case 1: 96 case 2: 97 state = 2; 98 hTimezone.append(c); 99 break; 100 case 3: 101 mTimezone.append(c); 102 break; 103 default: 104 throw new ParseException(error, i); 105 } 106 break; 107 default: 108 throw new ParseException(error, i); 76 ParseException pe = new ParseException(tr("Error while parsing timezone.\nExpected format: {0}", "±HH:MM"), 0); 77 try { 78 if (m.find()) { 79 int sign = "-".equals(m.group(1)) ? -1 : 1; 80 int hour = Integer.parseInt(m.group(2)); 81 int min = m.group(3) == null ? 0 : Integer.parseInt(m.group(3)); 82 return new GpxTimezone(sign * (hour + min / 60.0)); 109 83 } 84 } catch (IndexOutOfBoundsException | NumberFormatException ex) { 85 pe.initCause(ex); 110 86 } 111 112 int h = 0; 113 int m = 0; 114 try { 115 h = Integer.parseInt(hTimezone.toString()); 116 if (mTimezone.length() > 0) { 117 m = Integer.parseInt(mTimezone.toString()); 118 } 119 } catch (NumberFormatException nfe) { 120 // Invalid timezone 121 throw (ParseException) new ParseException(error, 0).initCause(nfe); 122 } 123 124 if (h > 12 || m > 59) 125 throw new ParseException(error, 0); 126 else 127 return new Timezone((h + m / 60.0) * (sgnTimezone == '-' ? -1 : 1)); 87 throw pe; 128 88 } 129 89 … … 131 91 public boolean equals(Object o) { 132 92 if (this == o) return true; 133 if (!(o instanceof Timezone)) return false; 134 Timezone timezone1 = (Timezone) o; 93 if (!(o instanceof GpxTimezone)) return false; 94 GpxTimezone timezone1 = (GpxTimezone) o; 135 95 return Double.compare(timezone1.timezone, timezone) == 0; 136 96 } -
trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
r14153 r14205 6 6 7 7 import java.awt.BorderLayout; 8 import java.awt.Component; 8 9 import java.awt.Cursor; 9 10 import java.awt.Dimension; … … 35 36 import java.util.Hashtable; 36 37 import java.util.List; 38 import java.util.Objects; 37 39 import java.util.Optional; 38 40 import java.util.TimeZone; … … 44 46 import javax.swing.JButton; 45 47 import javax.swing.JCheckBox; 48 import javax.swing.JComponent; 46 49 import javax.swing.JFileChooser; 47 50 import javax.swing.JLabel; … … 52 55 import javax.swing.JSeparator; 53 56 import javax.swing.JSlider; 57 import javax.swing.JSpinner; 54 58 import javax.swing.ListSelectionModel; 55 59 import javax.swing.MutableComboBoxModel; 60 import javax.swing.SpinnerNumberModel; 56 61 import javax.swing.SwingConstants; 62 import javax.swing.border.Border; 57 63 import javax.swing.event.ChangeEvent; 58 64 import javax.swing.event.ChangeListener; … … 62 68 import org.openstreetmap.josm.actions.DiskAccessAction; 63 69 import org.openstreetmap.josm.actions.ExtensionFileFilter; 64 import org.openstreetmap.josm.data.gpx.GpxConstants;65 70 import org.openstreetmap.josm.data.gpx.GpxData; 71 import org.openstreetmap.josm.data.gpx.GpxImageCorrelation; 72 import org.openstreetmap.josm.data.gpx.GpxTimeOffset; 73 import org.openstreetmap.josm.data.gpx.GpxTimezone; 66 74 import org.openstreetmap.josm.data.gpx.GpxTrack; 67 75 import org.openstreetmap.josm.data.gpx.GpxTrackSegment; … … 75 83 import org.openstreetmap.josm.gui.layer.GpxLayer; 76 84 import org.openstreetmap.josm.gui.layer.Layer; 85 import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent; 86 import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener; 87 import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent; 88 import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent; 77 89 import org.openstreetmap.josm.gui.widgets.AbstractFileChooser; 78 90 import org.openstreetmap.josm.gui.widgets.FileChooserManager; … … 84 96 import org.openstreetmap.josm.io.nmea.NmeaReader; 85 97 import org.openstreetmap.josm.spi.preferences.Config; 98 import org.openstreetmap.josm.spi.preferences.IPreferences; 86 99 import org.openstreetmap.josm.tools.GBC; 87 100 import org.openstreetmap.josm.tools.ImageProvider; … … 101 114 102 115 private final transient GeoImageLayer yLayer; 103 private transient Timezone timezone; 104 private transient Offset delta; 116 private transient GpxTimezone timezone; 117 private transient GpxTimeOffset delta; 118 private static boolean forceTags = false; 105 119 106 120 /** … … 112 126 new ImageProvider("dialogs/geoimage/gpx2img").getResource().attachImageIcon(this, true); 113 127 this.yLayer = layer; 128 MainApplication.getLayerManager().addLayerChangeListener(new GpxLayerAddedListener()); 114 129 } 115 130 … … 130 145 // Parse values again, to display an error if the format is not recognized 131 146 try { 132 timezone = Timezone.parseTimezone(tfTimezone.getText().trim()); 147 timezone = GpxTimezone.parseTimezone(tfTimezone.getText().trim()); 133 148 } catch (ParseException e) { 134 149 JOptionPane.showMessageDialog(MainApplication.getMainFrame(), e.getMessage(), … … 138 153 139 154 try { 140 delta = Offset.parseOffset(tfOffset.getText().trim()); 155 delta = GpxTimeOffset.parseOffset(tfOffset.getText().trim()); 141 156 } catch (ParseException e) { 142 157 JOptionPane.showMessageDialog(MainApplication.getMainFrame(), e.getMessage(), … … 323 338 } 324 339 340 private class AdvancedSettingsActionListener implements ActionListener { 341 342 private class CheckBoxActionListener implements ActionListener { 343 private final JComponent[] comps; 344 345 CheckBoxActionListener(JComponent... c) { 346 comps = Objects.requireNonNull(c); 347 } 348 349 @Override 350 public void actionPerformed(ActionEvent e) { 351 setEnabled((JCheckBox) e.getSource()); 352 } 353 354 public void setEnabled(JCheckBox cb) { 355 for (JComponent comp : comps) { 356 if (comp instanceof JSpinner) { 357 comp.setEnabled(cb.isSelected()); 358 } else if (comp instanceof JPanel) { 359 boolean en = cb.isSelected(); 360 for (Component c : comp.getComponents()) { 361 if (c instanceof JSpinner) { 362 c.setEnabled(en); 363 } else { 364 c.setEnabled(cb.isSelected()); 365 if (en && c instanceof JCheckBox) { 366 en = ((JCheckBox) c).isSelected(); 367 } 368 } 369 } 370 } 371 } 372 } 373 } 374 375 private void addCheckBoxActionListener(JCheckBox cb, JComponent... c) { 376 CheckBoxActionListener listener = new CheckBoxActionListener(c); 377 cb.addActionListener(listener); 378 listener.setEnabled(cb); 379 } 380 381 @Override 382 public void actionPerformed(ActionEvent e) { 383 384 IPreferences s = Config.getPref(); 385 JPanel p = new JPanel(new GridBagLayout()); 386 387 Border border1 = BorderFactory.createEmptyBorder(0, 20, 0, 0); 388 Border border2 = BorderFactory.createEmptyBorder(10, 0, 5, 0); 389 Border border = BorderFactory.createEmptyBorder(0, 40, 0, 0); 390 FlowLayout layout = new FlowLayout(); 391 392 JLabel l = new JLabel(tr("Segment settings")); 393 l.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); 394 p.add(l, GBC.eol()); 395 JCheckBox cInterpolSeg = new JCheckBox(tr("Interpolate between segments"), s.getBoolean("geoimage.seg.int", true)); 396 cInterpolSeg.setBorder(border1); 397 p.add(cInterpolSeg, GBC.eol()); 398 399 JCheckBox cInterpolSegTime = new JCheckBox(tr("only when the segments are less than # minutes apart:"), 400 s.getBoolean("geoimage.seg.int.time", true)); 401 JSpinner sInterpolSegTime = new JSpinner( 402 new SpinnerNumberModel(s.getInt("geoimage.seg.int.time.val", 60), 0, Integer.MAX_VALUE, 1)); 403 ((JSpinner.DefaultEditor) sInterpolSegTime.getEditor()).getTextField().setColumns(3); 404 JPanel pInterpolSegTime = new JPanel(layout); 405 pInterpolSegTime.add(cInterpolSegTime); 406 pInterpolSegTime.add(sInterpolSegTime); 407 pInterpolSegTime.setBorder(border); 408 p.add(pInterpolSegTime, GBC.eol()); 409 410 JCheckBox cInterpolSegDist = new JCheckBox(tr("only when the segments are less than # meters apart:"), 411 s.getBoolean("geoimage.seg.int.dist", true)); 412 JSpinner sInterpolSegDist = new JSpinner( 413 new SpinnerNumberModel(s.getInt("geoimage.seg.int.dist.val", 50), 0, Integer.MAX_VALUE, 1)); 414 ((JSpinner.DefaultEditor) sInterpolSegDist.getEditor()).getTextField().setColumns(3); 415 JPanel pInterpolSegDist = new JPanel(layout); 416 pInterpolSegDist.add(cInterpolSegDist); 417 pInterpolSegDist.add(sInterpolSegDist); 418 pInterpolSegDist.setBorder(border); 419 p.add(pInterpolSegDist, GBC.eol()); 420 421 JCheckBox cTagSeg = new JCheckBox(tr("Tag images at the closest end of a segment, when not interpolated"), 422 s.getBoolean("geoimage.seg.tag", true)); 423 cTagSeg.setBorder(border1); 424 p.add(cTagSeg, GBC.eol()); 425 426 JCheckBox cTagSegTime = new JCheckBox(tr("only within # minutes of the closest trackpoint:"), 427 s.getBoolean("geoimage.seg.tag.time", true)); 428 JSpinner sTagSegTime = new JSpinner( 429 new SpinnerNumberModel(s.getInt("geoimage.seg.tag.time.val", 2), 0, Integer.MAX_VALUE, 1)); 430 ((JSpinner.DefaultEditor) sTagSegTime.getEditor()).getTextField().setColumns(3); 431 JPanel pTagSegTime = new JPanel(layout); 432 pTagSegTime.add(cTagSegTime); 433 pTagSegTime.add(sTagSegTime); 434 pTagSegTime.setBorder(border); 435 p.add(pTagSegTime, GBC.eol()); 436 437 l = new JLabel(tr("Track settings (note that multiple tracks can be in one GPX file)")); 438 l.setBorder(border2); 439 p.add(l, GBC.eol()); 440 JCheckBox cInterpolTrack = new JCheckBox(tr("Interpolate between tracks"), s.getBoolean("geoimage.trk.int", false)); 441 cInterpolTrack.setBorder(border1); 442 p.add(cInterpolTrack, GBC.eol()); 443 444 JCheckBox cInterpolTrackTime = new JCheckBox(tr("only when the tracks are less than # minutes apart:"), 445 s.getBoolean("geoimage.trk.int.time", false)); 446 JSpinner sInterpolTrackTime = new JSpinner( 447 new SpinnerNumberModel(s.getInt("geoimage.trk.int.time.val", 60), 0, Integer.MAX_VALUE, 1)); 448 ((JSpinner.DefaultEditor) sInterpolTrackTime.getEditor()).getTextField().setColumns(3); 449 JPanel pInterpolTrackTime = new JPanel(layout); 450 pInterpolTrackTime.add(cInterpolTrackTime); 451 pInterpolTrackTime.add(sInterpolTrackTime); 452 pInterpolTrackTime.setBorder(border); 453 p.add(pInterpolTrackTime, GBC.eol()); 454 455 JCheckBox cInterpolTrackDist = new JCheckBox(tr("only when the tracks are less than # meters apart:"), 456 s.getBoolean("geoimage.trk.int.dist", false)); 457 JSpinner sInterpolTrackDist = new JSpinner( 458 new SpinnerNumberModel(s.getInt("geoimage.trk.int.dist.val", 50), 0, Integer.MAX_VALUE, 1)); 459 ((JSpinner.DefaultEditor) sInterpolTrackDist.getEditor()).getTextField().setColumns(3); 460 JPanel pInterpolTrackDist = new JPanel(layout); 461 pInterpolTrackDist.add(cInterpolTrackDist); 462 pInterpolTrackDist.add(sInterpolTrackDist); 463 pInterpolTrackDist.setBorder(border); 464 p.add(pInterpolTrackDist, GBC.eol()); 465 466 JCheckBox cTagTrack = new JCheckBox("<html>" + 467 tr("Tag images at the closest end of a track, when not interpolated<br>" + 468 "(also applies before the first and after the last track)") + "</html>", 469 s.getBoolean("geoimage.trk.tag", true)); 470 cTagTrack.setBorder(border1); 471 p.add(cTagTrack, GBC.eol()); 472 473 JCheckBox cTagTrackTime = new JCheckBox(tr("only within # minutes of the closest trackpoint:"), 474 s.getBoolean("geoimage.trk.tag.time", true)); 475 JSpinner sTagTrackTime = new JSpinner( 476 new SpinnerNumberModel(s.getInt("geoimage.trk.tag.time.val", 2), 0, Integer.MAX_VALUE, 1)); 477 ((JSpinner.DefaultEditor) sTagTrackTime.getEditor()).getTextField().setColumns(3); 478 JPanel pTagTrackTime = new JPanel(layout); 479 pTagTrackTime.add(cTagTrackTime); 480 pTagTrackTime.add(sTagTrackTime); 481 pTagTrackTime.setBorder(border); 482 p.add(pTagTrackTime, GBC.eol()); 483 484 l = new JLabel(tr("Advanced")); 485 l.setBorder(border2); 486 p.add(l, GBC.eol()); 487 JCheckBox cForce = new JCheckBox("<html>" + 488 tr("Force tagging of all pictures (temporarily overrides the settings above).") + "<br>" + 489 tr("This option will not be saved permanently.") + "</html>", forceTags); 490 cForce.setBorder(BorderFactory.createEmptyBorder(0, 20, 10, 0)); 491 p.add(cForce, GBC.eol()); 492 493 addCheckBoxActionListener(cInterpolSegTime, sInterpolSegTime); 494 addCheckBoxActionListener(cInterpolSegDist, sInterpolSegDist); 495 addCheckBoxActionListener(cInterpolSeg, pInterpolSegTime, pInterpolSegDist); 496 497 addCheckBoxActionListener(cTagSegTime, sTagSegTime); 498 addCheckBoxActionListener(cTagSeg, pTagSegTime); 499 500 addCheckBoxActionListener(cInterpolTrackTime, sInterpolTrackTime); 501 addCheckBoxActionListener(cInterpolTrackDist, sInterpolTrackDist); 502 addCheckBoxActionListener(cInterpolTrack, pInterpolTrackTime, pInterpolTrackDist); 503 504 addCheckBoxActionListener(cTagTrackTime, sTagTrackTime); 505 addCheckBoxActionListener(cTagTrack, pTagTrackTime); 506 507 508 ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(), tr("Advanced settings"), tr("OK"), tr("Cancel")) 509 .setButtonIcons("ok", "cancel").setContent(p); 510 if (ed.showDialog().getValue() == 1) { 511 512 s.putBoolean("geoimage.seg.int", cInterpolSeg.isSelected()); 513 s.putBoolean("geoimage.seg.int.dist", cInterpolSegDist.isSelected()); 514 s.putInt("geoimage.seg.int.dist.val", (int) sInterpolSegDist.getValue()); 515 s.putBoolean("geoimage.seg.int.time", cInterpolSegTime.isSelected()); 516 s.putInt("geoimage.seg.int.time.val", (int) sInterpolSegTime.getValue()); 517 s.putBoolean("geoimage.seg.tag", cTagSeg.isSelected()); 518 s.putBoolean("geoimage.seg.tag.time", cTagSegTime.isSelected()); 519 s.putInt("geoimage.seg.tag.time.val", (int) sTagSegTime.getValue()); 520 521 s.putBoolean("geoimage.trk.int", cInterpolTrack.isSelected()); 522 s.putBoolean("geoimage.trk.int.dist", cInterpolTrackDist.isSelected()); 523 s.putInt("geoimage.trk.int.dist.val", (int) sInterpolTrackDist.getValue()); 524 s.putBoolean("geoimage.trk.int.time", cInterpolTrackTime.isSelected()); 525 s.putInt("geoimage.trk.int.time.val", (int) sInterpolTrackTime.getValue()); 526 s.putBoolean("geoimage.trk.tag", cTagTrack.isSelected()); 527 s.putBoolean("geoimage.trk.tag.time", cTagTrackTime.isSelected()); 528 s.putInt("geoimage.trk.tag.time.val", (int) sTagTrackTime.getValue()); 529 530 forceTags = cForce.isSelected(); // This setting is not supposed to be saved permanently 531 532 statusBarUpdater.updateStatusBar(); 533 yLayer.updateBufferAndRepaint(); 534 } 535 } 536 } 537 325 538 /** 326 539 * This action listener is called when the user has a photo of the time of his GPS receiver. It … … 399 612 400 613 String tzDesc = tzStr + " (" + 401 new Timezone(((double) tz.getRawOffset()) / TimeUnit.HOURS.toMillis(1)).formatTimezone() + 614 new GpxTimezone(((double) tz.getRawOffset()) / TimeUnit.HOURS.toMillis(1)).formatTimezone() + 402 615 ')'; 403 616 vtTimezones.add(tzDesc); … … 417 630 418 631 cbTimezones.setSelectedItem(defaultTz.getID() + " (" + 419 new Timezone(((double) defaultTz.getRawOffset()) / TimeUnit.HOURS.toMillis(1)).formatTimezone() + 632 new GpxTimezone(((double) defaultTz.getRawOffset()) / TimeUnit.HOURS.toMillis(1)).formatTimezone() + 420 633 ')'); 421 634 … … 515 728 516 729 Config.getPref().put("geoimage.timezoneid", tzId); 517 tfOffset.setText(Offset.milliseconds(delta).formatOffset()); 730 tfOffset.setText(GpxTimeOffset.milliseconds(delta).formatOffset()); 518 731 tfTimezone.setText(tzValue); 519 732 … … 524 737 yLayer.updateBufferAndRepaint(); 525 738 } 739 } 740 741 private class GpxLayerAddedListener implements LayerChangeListener { 742 @Override 743 public void layerAdded(LayerAddEvent e) { 744 if (syncDialog != null && syncDialog.isVisible()) { 745 Layer layer = e.getAddedLayer(); 746 if (layer instanceof GpxLayer) { 747 GpxLayer gpx = (GpxLayer) layer; 748 GpxDataWrapper gdw = new GpxDataWrapper(gpx.getName(), gpx.data, gpx.data.storageFile); 749 gpxLst.add(gdw); 750 MutableComboBoxModel<GpxDataWrapper> model = (MutableComboBoxModel<GpxDataWrapper>) cbGpx.getModel(); 751 if (gpxLst.get(0).file == null) { 752 gpxLst.remove(0); 753 model.removeElementAt(0); 754 } 755 model.addElement(gdw); 756 } 757 } 758 } 759 760 @Override 761 public void layerRemoving(LayerRemoveEvent e) {} 762 763 @Override 764 public void layerOrderChanged(LayerOrderChangeEvent e) {} 526 765 } 527 766 … … 578 817 579 818 try { 580 timezone = Timezone.parseTimezone(Optional.ofNullable(Config.getPref().get("geoimage.timezone", "0:00")).orElse("0:00")); 819 timezone = GpxTimezone.parseTimezone(Optional.ofNullable(Config.getPref().get("geoimage.timezone", "0:00")).orElse("0:00")); 581 820 } catch (ParseException e) { 582 timezone = Timezone.ZERO; 821 timezone = GpxTimezone.ZERO; 822 Logging.trace(e); 583 823 } 584 824 … … 587 827 588 828 try { 589 delta = Offset.parseOffset(Config.getPref().get("geoimage.delta", "0")); 829 delta = GpxTimeOffset.parseOffset(Config.getPref().get("geoimage.delta", "0")); 590 830 } catch (ParseException e) { 591 delta = Offset.ZERO; 831 delta = GpxTimeOffset.ZERO; 832 Logging.trace(e); 592 833 } 593 834 … … 606 847 JButton buttonAdjust = new JButton(tr("Manual adjust")); 607 848 buttonAdjust.addActionListener(new AdjustActionListener()); 849 850 JButton buttonAdvanced = new JButton(tr("Advanced settings...")); 851 buttonAdvanced.addActionListener(new AdvancedSettingsActionListener()); 608 852 609 853 JLabel labelPosition = new JLabel(tr("Override position for: ")); … … 668 912 669 913 gbc = GBC.std().fill(GBC.BOTH).insets(5, 5, 5, 5); 670 gbc.gridx = 2;914 gbc.gridx = 1; 671 915 gbc.gridy = y++; 672 916 gbc.weightx = 0.5; 917 panelTf.add(buttonAdvanced, gbc); 918 919 gbc.gridx = 2; 673 920 panelTf.add(buttonAutoGuess, gbc); 674 921 … … 716 963 717 964 statusBarUpdater.updateStatusBar(); 965 yLayer.updateBufferAndRepaint(); 718 966 719 967 outerPanel = new JPanel(new BorderLayout()); … … 782 1030 private String statusText() { 783 1031 try { 784 timezone = Timezone.parseTimezone(tfTimezone.getText().trim()); 785 delta = Offset.parseOffset(tfOffset.getText().trim()); 1032 timezone = GpxTimezone.parseTimezone(tfTimezone.getText().trim()); 1033 delta = GpxTimeOffset.parseOffset(tfOffset.getText().trim()); 786 1034 } catch (ParseException e) { 787 1035 return e.getMessage(); … … 801 1049 for (ImageEntry ie : dateImgLst) { 802 1050 ie.createTmp(); 803 ie. tmp.setPos(null);1051 ie.getTmp().setPos(null); 804 1052 } 805 1053 … … 808 1056 return tr("No gpx selected"); 809 1057 810 final long offsetMs = ((long) (timezone.getHours() * TimeUnit.HOURS.toMillis(1))) + delta.getMilliseconds(); // in milliseconds 811 lastNumMatched = matchGpxTrack(dateImgLst, selGpx.data, offsetMs); 1058 final long offsetMs = ((long) (timezone.getHours() * TimeUnit.HOURS.toMillis(-1))) + delta.getMilliseconds(); // in milliseconds 1059 lastNumMatched = GpxImageCorrelation.matchGpxTrack(dateImgLst, selGpx.data, offsetMs, forceTags); 812 1060 813 1061 return trn("<html>Matched <b>{0}</b> of <b>{1}</b> photo to GPX track.</html>", … … 838 1086 public void actionPerformed(ActionEvent arg0) { 839 1087 840 final Offset offset = Offset.milliseconds( 1088 final GpxTimeOffset offset = GpxTimeOffset.milliseconds( 841 1089 delta.getMilliseconds() + Math.round(timezone.getHours() * TimeUnit.HOURS.toMillis(1))); 842 1090 final int dayOffset = offset.getDayOffset(); 843 final Pair<Timezone, Offset> timezoneOffsetPair = offset.withoutDayOffset().splitOutTimezone(); 1091 final Pair<GpxTimezone, GpxTimeOffset> timezoneOffsetPair = offset.withoutDayOffset().splitOutTimezone(); 844 1092 845 1093 // Info Labels … … 854 1102 // CHECKSTYLE.OFF: ParenPad 855 1103 for (int i = -12; i <= 12; i += 6) { 856 labelTable.put(i * 2, new JLabel(new Timezone(i).formatTimezone())); 1104 labelTable.put(i * 2, new JLabel(new GpxTimezone(i).formatTimezone())); 857 1105 } 858 1106 // CHECKSTYLE.ON: ParenPad … … 872 1120 // CHECKSTYLE.OFF: ParenPad 873 1121 for (int i = -60; i <= 60; i += 30) { 874 labelTable.put(i * 10, new JLabel(Offset.seconds(i).formatOffset())); 1122 labelTable.put(i * 10, new JLabel(GpxTimeOffset.seconds(i).formatOffset())); 875 1123 } 876 1124 // CHECKSTYLE.ON: ParenPad … … 883 1131 @Override 884 1132 public void stateChanged(ChangeEvent e) { 885 timezone = new Timezone(sldTimezone.getValue() / 2.); 1133 timezone = new GpxTimezone(sldTimezone.getValue() / 2.); 886 1134 887 1135 lblTimezone.setText(tr("Timezone: {0}", timezone.formatTimezone())); 888 1136 lblMinutes.setText(tr("Minutes: {0}", sldMinutes.getValue())); 889 lblSeconds.setText(tr("Seconds: {0}", Offset.milliseconds(100L * sldSeconds.getValue()).formatOffset())); 890 891 delta = Offset.milliseconds(100L * sldSeconds.getValue() 1137 lblSeconds.setText(tr("Seconds: {0}", GpxTimeOffset.milliseconds(100L * sldSeconds.getValue()).formatOffset())); 1138 1139 delta = GpxTimeOffset.milliseconds(100L * sldSeconds.getValue() 892 1140 + TimeUnit.MINUTES.toMillis(sldMinutes.getValue()) 893 1141 + TimeUnit.DAYS.toMillis(dayOffset)); … … 968 1216 * @throws NoGpxTimestamps when the gpx track does not contain a timestamp 969 1217 */ 970 static Pair<Timezone, Offset> autoGuess(List<ImageEntry> imgs, GpxData gpx) throws NoGpxTimestamps { 1218 static Pair<GpxTimezone, GpxTimeOffset> autoGuess(List<ImageEntry> imgs, GpxData gpx) throws NoGpxTimestamps { 971 1219 972 1220 // Init variables … … 991 1239 } 992 1240 993 return Offset.milliseconds(firstExifDate - firstGPXDate).splitOutTimezone(); 1241 return GpxTimeOffset.milliseconds(firstExifDate - firstGPXDate).splitOutTimezone(); 994 1242 } 995 1243 … … 1006 1254 1007 1255 try { 1008 final Pair<Timezone, Offset> r = autoGuess(imgs, gpx); 1256 final Pair<GpxTimezone, GpxTimeOffset> r = autoGuess(imgs, gpx); 1009 1257 timezone = r.a; 1010 1258 delta = r.b; … … 1088 1336 } 1089 1337 1090 /**1091 * Match a list of photos to a gpx track with a given offset.1092 * All images need a exifTime attribute and the List must be sorted according to these times.1093 * @param images images to match1094 * @param selectedGpx selected GPX data1095 * @param offset offset1096 * @return number of matched points1097 */1098 static int matchGpxTrack(List<ImageEntry> images, GpxData selectedGpx, long offset) {1099 int ret = 0;1100 1101 for (GpxTrack trk : selectedGpx.tracks) {1102 for (GpxTrackSegment segment : trk.getSegments()) {1103 1104 long prevWpTime = 0;1105 WayPoint prevWp = null;1106 1107 for (WayPoint curWp : segment.getWayPoints()) {1108 final Date parsedTime = curWp.setTimeFromAttribute();1109 if (parsedTime != null) {1110 final long curWpTime = parsedTime.getTime() + offset;1111 ret += matchPoints(images, prevWp, prevWpTime, curWp, curWpTime, offset);1112 1113 prevWp = curWp;1114 prevWpTime = curWpTime;1115 continue;1116 }1117 prevWp = null;1118 prevWpTime = 0;1119 }1120 }1121 }1122 return ret;1123 }1124 1125 private static Double getElevation(WayPoint wp) {1126 String value = wp.getString(GpxConstants.PT_ELE);1127 if (value != null && !value.isEmpty()) {1128 try {1129 return Double.valueOf(value);1130 } catch (NumberFormatException e) {1131 Logging.warn(e);1132 }1133 }1134 return null;1135 }1136 1137 static int matchPoints(List<ImageEntry> images, WayPoint prevWp, long prevWpTime,1138 WayPoint curWp, long curWpTime, long offset) {1139 // Time between the track point and the previous one, 5 sec if first point, i.e. photos take1140 // 5 sec before the first track point can be assumed to be take at the starting position1141 long interval = prevWpTime > 0 ? Math.abs(curWpTime - prevWpTime) : TimeUnit.SECONDS.toMillis(5);1142 int ret = 0;1143 1144 // i is the index of the timewise last photo that has the same or earlier EXIF time1145 int i = getLastIndexOfListBefore(images, curWpTime);1146 1147 // no photos match1148 if (i < 0)1149 return 0;1150 1151 Double speed = null;1152 Double prevElevation = null;1153 1154 if (prevWp != null) {1155 double distance = prevWp.getCoor().greatCircleDistance(curWp.getCoor());1156 // This is in km/h, 3.6 * m/s1157 if (curWpTime > prevWpTime) {1158 speed = 3600 * distance / (curWpTime - prevWpTime);1159 }1160 prevElevation = getElevation(prevWp);1161 }1162 1163 Double curElevation = getElevation(curWp);1164 1165 // First trackpoint, then interval is set to five seconds, i.e. photos up to five seconds1166 // before the first point will be geotagged with the starting point1167 if (prevWpTime == 0 || curWpTime <= prevWpTime) {1168 while (i >= 0) {1169 final ImageEntry curImg = images.get(i);1170 long time = curImg.getExifTime().getTime();1171 if (time > curWpTime || time < curWpTime - interval) {1172 break;1173 }1174 if (curImg.tmp.getPos() == null) {1175 curImg.tmp.setPos(curWp.getCoor());1176 curImg.tmp.setSpeed(speed);1177 curImg.tmp.setElevation(curElevation);1178 curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));1179 curImg.tmp.flagNewGpsData();1180 ret++;1181 }1182 i--;1183 }1184 return ret;1185 }1186 1187 // This code gives a simple linear interpolation of the coordinates between current and1188 // previous track point assuming a constant speed in between1189 while (i >= 0) {1190 ImageEntry curImg = images.get(i);1191 long imgTime = curImg.getExifTime().getTime();1192 if (imgTime < prevWpTime) {1193 break;1194 }1195 1196 if (prevWp != null && curImg.tmp.getPos() == null) {1197 // The values of timeDiff are between 0 and 1, it is not seconds but a dimensionless variable1198 double timeDiff = (double) (imgTime - prevWpTime) / interval;1199 curImg.tmp.setPos(prevWp.getCoor().interpolate(curWp.getCoor(), timeDiff));1200 curImg.tmp.setSpeed(speed);1201 if (curElevation != null && prevElevation != null) {1202 curImg.tmp.setElevation(prevElevation + (curElevation - prevElevation) * timeDiff);1203 }1204 curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));1205 curImg.tmp.flagNewGpsData();1206 1207 ret++;1208 }1209 i--;1210 }1211 return ret;1212 }1213 1214 private static int getLastIndexOfListBefore(List<ImageEntry> images, long searchedTime) {1215 int lstSize = images.size();1216 1217 // No photos or the first photo taken is later than the search period1218 if (lstSize == 0 || searchedTime < images.get(0).getExifTime().getTime())1219 return -1;1220 1221 // The search period is later than the last photo1222 if (searchedTime > images.get(lstSize - 1).getExifTime().getTime())1223 return lstSize-1;1224 1225 // The searched index is somewhere in the middle, do a binary search from the beginning1226 int curIndex;1227 int startIndex = 0;1228 int endIndex = lstSize-1;1229 while (endIndex - startIndex > 1) {1230 curIndex = (endIndex + startIndex) / 2;1231 if (searchedTime > images.get(curIndex).getExifTime().getTime()) {1232 startIndex = curIndex;1233 } else {1234 endIndex = curIndex;1235 }1236 }1237 if (searchedTime < images.get(endIndex).getExifTime().getTime())1238 return startIndex;1239 1240 // This final loop is to check if photos with the exact same EXIF time follows1241 while ((endIndex < (lstSize-1)) && (images.get(endIndex).getExifTime().getTime()1242 == images.get(endIndex + 1).getExifTime().getTime())) {1243 endIndex++;1244 }1245 return endIndex;1246 }1247 1338 } -
trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java
r13256 r14205 4 4 import java.awt.Image; 5 5 import java.io.File; 6 import java.io.IOException;7 6 import java.util.Collections; 8 import java.util.Date;9 7 10 import org.openstreetmap.josm.data.coor.CachedLatLon; 11 import org.openstreetmap.josm.data.coor.LatLon; 12 import org.openstreetmap.josm.tools.ExifReader; 13 import org.openstreetmap.josm.tools.JosmRuntimeException; 14 import org.openstreetmap.josm.tools.Logging; 15 16 import com.drew.imaging.jpeg.JpegMetadataReader; 17 import com.drew.lang.CompoundException; 18 import com.drew.metadata.Directory; 19 import com.drew.metadata.Metadata; 20 import com.drew.metadata.MetadataException; 21 import com.drew.metadata.exif.ExifIFD0Directory; 22 import com.drew.metadata.exif.GpsDirectory; 23 import com.drew.metadata.jpeg.JpegDirectory; 8 import org.openstreetmap.josm.data.gpx.GpxImageEntry; 24 9 25 10 /** 26 * Stores info about each image 11 * Stores info about each image, with an optional thumbnail 12 * @since 2662 27 13 */ 28 public final class ImageEntry implements Comparable<ImageEntry>, Cloneable { 29 private File file; 30 private Integer exifOrientation; 31 private LatLon exifCoor; 32 private Double exifImgDir; 33 private Date exifTime; 34 /** 35 * Flag isNewGpsData indicates that the GPS data of the image is new or has changed. 36 * GPS data includes the position, speed, elevation, time (e.g. as extracted from the GPS track). 37 * The flag can used to decide for which image file the EXIF GPS data is (re-)written. 38 */ 39 private boolean isNewGpsData; 40 /** Temporary source of GPS time if not correlated with GPX track. */ 41 private Date exifGpsTime; 14 public final class ImageEntry extends GpxImageEntry { 15 42 16 private Image thumbnail; 43 44 /**45 * The following values are computed from the correlation with the gpx track46 * or extracted from the image EXIF data.47 */48 private CachedLatLon pos;49 /** Speed in kilometer per hour */50 private Double speed;51 /** Elevation (altitude) in meters */52 private Double elevation;53 /** The time after correlation with a gpx track */54 private Date gpsTime;55 56 private int width;57 private int height;58 59 /**60 * When the correlation dialog is open, we like to show the image position61 * for the current time offset on the map in real time.62 * On the other hand, when the user aborts this operation, the old values63 * should be restored. We have a temporary copy, that overrides64 * the normal values if it is not null. (This may be not the most elegant65 * solution for this, but it works.)66 */67 ImageEntry tmp;68 17 69 18 /** 70 19 * Constructs a new {@code ImageEntry}. 71 20 */ 72 public ImageEntry() {} 21 public ImageEntry() { 22 } 73 23 74 24 /** … … 77 27 */ 78 28 public ImageEntry(File file) { 79 setFile(file); 80 } 81 82 /** 83 * Returns width of the image this ImageEntry represents. 84 * @return width of the image this ImageEntry represents 85 * @since 13220 86 */ 87 public int getWidth() { 88 return width; 89 } 90 91 /** 92 * Returns height of the image this ImageEntry represents. 93 * @return height of the image this ImageEntry represents 94 * @since 13220 95 */ 96 public int getHeight() { 97 return height; 98 } 99 100 /** 101 * Returns the position value. The position value from the temporary copy 102 * is returned if that copy exists. 103 * @return the position value 104 */ 105 public CachedLatLon getPos() { 106 if (tmp != null) 107 return tmp.pos; 108 return pos; 109 } 110 111 /** 112 * Returns the speed value. The speed value from the temporary copy is 113 * returned if that copy exists. 114 * @return the speed value 115 */ 116 public Double getSpeed() { 117 if (tmp != null) 118 return tmp.speed; 119 return speed; 120 } 121 122 /** 123 * Returns the elevation value. The elevation value from the temporary 124 * copy is returned if that copy exists. 125 * @return the elevation value 126 */ 127 public Double getElevation() { 128 if (tmp != null) 129 return tmp.elevation; 130 return elevation; 131 } 132 133 /** 134 * Returns the GPS time value. The GPS time value from the temporary copy 135 * is returned if that copy exists. 136 * @return the GPS time value 137 */ 138 public Date getGpsTime() { 139 if (tmp != null) 140 return getDefensiveDate(tmp.gpsTime); 141 return getDefensiveDate(gpsTime); 142 } 143 144 /** 145 * Convenient way to determine if this entry has a GPS time, without the cost of building a defensive copy. 146 * @return {@code true} if this entry has a GPS time 147 * @since 6450 148 */ 149 public boolean hasGpsTime() { 150 return (tmp != null && tmp.gpsTime != null) || gpsTime != null; 151 } 152 153 /** 154 * Returns associated file. 155 * @return associated file 156 */ 157 public File getFile() { 158 return file; 159 } 160 161 /** 162 * Returns EXIF orientation 163 * @return EXIF orientation 164 */ 165 public Integer getExifOrientation() { 166 return exifOrientation != null ? exifOrientation : 1; 167 } 168 169 /** 170 * Returns EXIF time 171 * @return EXIF time 172 */ 173 public Date getExifTime() { 174 return getDefensiveDate(exifTime); 175 } 176 177 /** 178 * Convenient way to determine if this entry has a EXIF time, without the cost of building a defensive copy. 179 * @return {@code true} if this entry has a EXIF time 180 * @since 6450 181 */ 182 public boolean hasExifTime() { 183 return exifTime != null; 184 } 185 186 /** 187 * Returns the EXIF GPS time. 188 * @return the EXIF GPS time 189 * @since 6392 190 */ 191 public Date getExifGpsTime() { 192 return getDefensiveDate(exifGpsTime); 193 } 194 195 /** 196 * Convenient way to determine if this entry has a EXIF GPS time, without the cost of building a defensive copy. 197 * @return {@code true} if this entry has a EXIF GPS time 198 * @since 6450 199 */ 200 public boolean hasExifGpsTime() { 201 return exifGpsTime != null; 202 } 203 204 private static Date getDefensiveDate(Date date) { 205 if (date == null) 206 return null; 207 return new Date(date.getTime()); 208 } 209 210 public LatLon getExifCoor() { 211 return exifCoor; 212 } 213 214 public Double getExifImgDir() { 215 if (tmp != null) 216 return tmp.exifImgDir; 217 return exifImgDir; 29 super(file); 218 30 } 219 31 … … 251 63 } 252 64 } 253 254 /**255 * Sets the width of this ImageEntry.256 * @param width set the width of this ImageEntry257 * @since 13220258 */259 public void setWidth(int width) {260 this.width = width;261 }262 263 /**264 * Sets the height of this ImageEntry.265 * @param height set the height of this ImageEntry266 * @since 13220267 */268 public void setHeight(int height) {269 this.height = height;270 }271 272 /**273 * Sets the position.274 * @param pos cached position275 */276 public void setPos(CachedLatLon pos) {277 this.pos = pos;278 }279 280 /**281 * Sets the position.282 * @param pos position (will be cached)283 */284 public void setPos(LatLon pos) {285 setPos(pos != null ? new CachedLatLon(pos) : null);286 }287 288 /**289 * Sets the speed.290 * @param speed speed291 */292 public void setSpeed(Double speed) {293 this.speed = speed;294 }295 296 /**297 * Sets the elevation.298 * @param elevation elevation299 */300 public void setElevation(Double elevation) {301 this.elevation = elevation;302 }303 304 /**305 * Sets associated file.306 * @param file associated file307 */308 public void setFile(File file) {309 this.file = file;310 }311 312 /**313 * Sets EXIF orientation.314 * @param exifOrientation EXIF orientation315 */316 public void setExifOrientation(Integer exifOrientation) {317 this.exifOrientation = exifOrientation;318 }319 320 /**321 * Sets EXIF time.322 * @param exifTime EXIF time323 */324 public void setExifTime(Date exifTime) {325 this.exifTime = getDefensiveDate(exifTime);326 }327 328 /**329 * Sets the EXIF GPS time.330 * @param exifGpsTime the EXIF GPS time331 * @since 6392332 */333 public void setExifGpsTime(Date exifGpsTime) {334 this.exifGpsTime = getDefensiveDate(exifGpsTime);335 }336 337 public void setGpsTime(Date gpsTime) {338 this.gpsTime = getDefensiveDate(gpsTime);339 }340 341 public void setExifCoor(LatLon exifCoor) {342 this.exifCoor = exifCoor;343 }344 345 public void setExifImgDir(Double exifDir) {346 this.exifImgDir = exifDir;347 }348 349 @Override350 public ImageEntry clone() {351 try {352 return (ImageEntry) super.clone();353 } catch (CloneNotSupportedException e) {354 throw new IllegalStateException(e);355 }356 }357 358 @Override359 public int compareTo(ImageEntry image) {360 if (exifTime != null && image.exifTime != null)361 return exifTime.compareTo(image.exifTime);362 else if (exifTime == null && image.exifTime == null)363 return 0;364 else if (exifTime == null)365 return -1;366 else367 return 1;368 }369 370 /**371 * Make a fresh copy and save it in the temporary variable. Use372 * {@link #applyTmp()} or {@link #discardTmp()} if the temporary variable373 * is not needed anymore.374 */375 public void createTmp() {376 tmp = clone();377 tmp.tmp = null;378 }379 380 /**381 * Get temporary variable that is used for real time parameter382 * adjustments. The temporary variable is created if it does not exist383 * yet. Use {@link #applyTmp()} or {@link #discardTmp()} if the temporary384 * variable is not needed anymore.385 * @return temporary variable386 */387 public ImageEntry getTmp() {388 if (tmp == null) {389 createTmp();390 }391 return tmp;392 }393 394 /**395 * Copy the values from the temporary variable to the main instance. The396 * temporary variable is deleted.397 * @see #discardTmp()398 */399 public void applyTmp() {400 if (tmp != null) {401 pos = tmp.pos;402 speed = tmp.speed;403 elevation = tmp.elevation;404 gpsTime = tmp.gpsTime;405 exifImgDir = tmp.exifImgDir;406 isNewGpsData = tmp.isNewGpsData;407 tmp = null;408 }409 }410 411 /**412 * Delete the temporary variable. Temporary modifications are lost.413 * @see #applyTmp()414 */415 public void discardTmp() {416 tmp = null;417 }418 419 /**420 * If it has been tagged i.e. matched to a gpx track or retrieved lat/lon from exif421 * @return {@code true} if it has been tagged422 */423 public boolean isTagged() {424 return pos != null;425 }426 427 /**428 * String representation. (only partial info)429 */430 @Override431 public String toString() {432 return file.getName()+": "+433 "pos = "+pos+" | "+434 "exifCoor = "+exifCoor+" | "+435 (tmp == null ? " tmp==null" :436 " [tmp] pos = "+tmp.pos);437 }438 439 /**440 * Indicates that the image has new GPS data.441 * That flag is set by new GPS data providers. It is used e.g. by the photo_geotagging plugin442 * to decide for which image file the EXIF GPS data needs to be (re-)written.443 * @since 6392444 */445 public void flagNewGpsData() {446 isNewGpsData = true;447 }448 449 /**450 * Remove the flag that indicates new GPS data.451 * The flag is cleared by a new GPS data consumer.452 */453 public void unflagNewGpsData() {454 isNewGpsData = false;455 }456 457 /**458 * Queries whether the GPS data changed. The flag value from the temporary459 * copy is returned if that copy exists.460 * @return {@code true} if GPS data changed, {@code false} otherwise461 * @since 6392462 */463 public boolean hasNewGpsData() {464 if (tmp != null)465 return tmp.isNewGpsData;466 return isNewGpsData;467 }468 469 /**470 * Extract GPS metadata from image EXIF. Has no effect if the image file is not set471 *472 * If successful, fills in the LatLon, speed, elevation, image direction, and other attributes473 * @since 9270474 */475 public void extractExif() {476 477 Metadata metadata;478 479 if (file == null) {480 return;481 }482 483 try {484 metadata = JpegMetadataReader.readMetadata(file);485 } catch (CompoundException | IOException ex) {486 Logging.error(ex);487 setExifTime(null);488 setExifCoor(null);489 setPos(null);490 return;491 }492 493 // Changed to silently cope with no time info in exif. One case494 // of person having time that couldn't be parsed, but valid GPS info495 try {496 setExifTime(ExifReader.readTime(metadata));497 } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException ex) {498 Logging.warn(ex);499 setExifTime(null);500 }501 502 final Directory dir = metadata.getFirstDirectoryOfType(JpegDirectory.class);503 final Directory dirExif = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);504 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);505 506 try {507 if (dirExif != null) {508 int orientation = dirExif.getInt(ExifIFD0Directory.TAG_ORIENTATION);509 setExifOrientation(orientation);510 }511 } catch (MetadataException ex) {512 Logging.debug(ex);513 }514 515 try {516 if (dir != null) {517 // there are cases where these do not match width and height stored in dirExif518 int width = dir.getInt(JpegDirectory.TAG_IMAGE_WIDTH);519 int height = dir.getInt(JpegDirectory.TAG_IMAGE_HEIGHT);520 setWidth(width);521 setHeight(height);522 }523 } catch (MetadataException ex) {524 Logging.debug(ex);525 }526 527 if (dirGps == null) {528 setExifCoor(null);529 setPos(null);530 return;531 }532 533 final Double speed = ExifReader.readSpeed(dirGps);534 if (speed != null) {535 setSpeed(speed);536 }537 538 final Double ele = ExifReader.readElevation(dirGps);539 if (ele != null) {540 setElevation(ele);541 }542 543 try {544 final LatLon latlon = ExifReader.readLatLon(dirGps);545 setExifCoor(latlon);546 setPos(getExifCoor());547 } catch (MetadataException | IndexOutOfBoundsException ex) { // (other exceptions, e.g. #5271)548 Logging.error("Error reading EXIF from file: " + ex);549 setExifCoor(null);550 setPos(null);551 }552 553 try {554 final Double direction = ExifReader.readDirection(dirGps);555 if (direction != null) {556 setExifImgDir(direction);557 }558 } catch (IndexOutOfBoundsException ex) { // (other exceptions, e.g. #5271)559 Logging.debug(ex);560 }561 562 final Date gpsDate = dirGps.getGpsDate();563 if (gpsDate != null) {564 setExifGpsTime(gpsDate);565 }566 }567 65 } -
trunk/src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java
r12620 r14205 11 11 12 12 import org.openstreetmap.josm.data.coor.LatLon; 13 import org.openstreetmap.josm.data.gpx.GpxImageEntry; 13 14 import org.openstreetmap.josm.gui.layer.GpxLayer; 14 15 import org.openstreetmap.josm.gui.layer.Layer; … … 71 72 } 72 73 73 private static void handleElement(ImageEntry entry, Element attrElem) { 74 private static void handleElement(GpxImageEntry entry, Element attrElem) { 74 75 try { 75 76 switch(attrElem.getTagName()) {
Note:
See TracChangeset
for help on using the changeset viewer.