Changeset 14205 in josm for trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
- Timestamp:
- 2018-08-30T23:15:06+02:00 (6 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
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 milliseconds811 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 }
Note:
See TracChangeset
for help on using the changeset viewer.