Changeset 2662 in josm for trunk/src/org
- Timestamp:
- 2009-12-19T18:56:45+01:00 (15 years ago)
- Location:
- trunk/src/org/openstreetmap/josm/gui
- Files:
-
- 1 added
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/gui/ExtendedDialog.java
r2627 r2662 192 192 } 193 193 194 protected void setupDialog() { 194 private boolean setupDone = false; 195 196 /** 197 * This is called by showDialog(). 198 * Only invoke from outside if you need to modify the contentPane 199 */ 200 public void setupDialog() { 201 if (setupDone) 202 return; 203 setupDone = true; 204 195 205 setupEscListener(); 196 206 -
trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
r2649 r2662 16 16 import java.awt.event.ActionEvent; 17 17 import java.awt.event.ActionListener; 18 import java.awt.event.ItemEvent; 19 import java.awt.event.ItemListener; 20 import java.awt.event.WindowAdapter; 21 import java.awt.event.WindowEvent; 18 22 import java.io.File; 19 23 import java.io.FileInputStream; … … 35 39 36 40 import javax.swing.AbstractListModel; 41 import javax.swing.BorderFactory; 37 42 import javax.swing.ButtonGroup; 38 43 import javax.swing.JButton; … … 46 51 import javax.swing.JRadioButton; 47 52 import javax.swing.JScrollPane; 53 import javax.swing.JSeparator; 48 54 import javax.swing.JSlider; 49 55 import javax.swing.JTextField; 50 56 import javax.swing.ListSelectionModel; 57 import javax.swing.SwingConstants; 51 58 import javax.swing.event.ChangeEvent; 52 59 import javax.swing.event.ChangeListener; 60 import javax.swing.event.DocumentEvent; 61 import javax.swing.event.DocumentListener; 53 62 import javax.swing.event.ListSelectionEvent; 54 63 import javax.swing.event.ListSelectionListener; … … 63 72 import org.openstreetmap.josm.gui.layer.GpxLayer; 64 73 import org.openstreetmap.josm.gui.layer.Layer; 65 import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer.ImageEntry;66 74 import org.openstreetmap.josm.io.GpxReader; 67 75 import org.openstreetmap.josm.tools.ExifReader; … … 79 87 private static List<GpxData> loadedGpxData = new ArrayList<GpxData>(); 80 88 81 public static class CorrelateParameters {82 GpxData gpxData;83 float timezone;84 long offset;85 }86 87 89 GeoImageLayer yLayer = null; 90 double timezone; 91 long delta; 92 93 public CorrelateGpxWithImages(GeoImageLayer layer) { 94 this.yLayer = layer; 95 } 88 96 89 97 private static class GpxDataWrapper { … … 104 112 } 105 113 114 ExtendedDialog syncDialog; 106 115 Vector<GpxDataWrapper> gpxLst = new Vector<GpxDataWrapper>(); 107 JPanel panel = null; 108 JComboBox cbGpx = null; 109 JTextField tfTimezone = null; 110 JTextField tfOffset = null; 111 JRadioButton rbAllImg = null; 112 JRadioButton rbUntaggedImg = null; 113 JRadioButton rbNoExifImg = null; 116 JPanel outerPanel; 117 JComboBox cbGpx; 118 JTextField tfTimezone; 119 JTextField tfOffset; 120 JCheckBox cbExifImg; 121 JCheckBox cbTaggedImg; 122 JCheckBox cbShowThumbs; 123 JLabel statusBarText; 124 StatusBarListener statusBarListener; 125 126 // remember the last number of matched photos 127 int lastNumMatched = 0; 114 128 115 129 /** This class is called when the user doesn't find the GPX file he needs in the files that have … … 140 154 141 155 try { 142 panel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));156 outerPanel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); 143 157 144 158 Main.pref.put("lastDirectory", sel.getPath()); … … 197 211 cbGpx.setSelectedIndex(cbGpx.getItemCount() - 1); 198 212 } finally { 199 panel.setCursor(Cursor.getDefaultCursor());213 outerPanel.setCursor(Cursor.getDefaultCursor()); 200 214 } 201 215 } … … 257 271 panelTf.add(new JLabel(tr("Gps time (read from the above photo): ")), gc); 258 272 259 tfGpsTime = new JTextField( );273 tfGpsTime = new JTextField(12); 260 274 tfGpsTime.setEnabled(false); 261 tfGpsTime.setMinimumSize(new Dimension(15 0, tfGpsTime.getMinimumSize().height));275 tfGpsTime.setMinimumSize(new Dimension(155, tfGpsTime.getMinimumSize().height)); 262 276 gc.gridx = 1; 263 277 gc.weightx = 1.0; … … 334 348 if (date != null) { 335 349 lbExifTime.setText(new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(date)); 336 tfGpsTime.setText(new SimpleDateFormat("dd/MM/yyyy ").format(date));350 tfGpsTime.setText(new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(date)); 337 351 tfGpsTime.setCaretPosition(tfGpsTime.getText().length()); 338 352 tfGpsTime.setEnabled(true); 353 tfGpsTime.requestFocus(); 339 354 } else { 340 355 lbExifTime.setText(tr("No date")); … … 418 433 419 434 } 420 421 } 422 } 423 424 public CorrelateGpxWithImages(GeoImageLayer layer) { 425 this.yLayer = layer; 435 statusBarListener.updateStatusBar(); 436 yLayer.updateBufferAndRepaint(); 437 } 426 438 } 427 439 … … 454 466 455 467 JPanel panelCb = new JPanel(); 456 panelCb.setLayout(new FlowLayout());457 468 458 469 panelCb.add(new JLabel(tr("GPX track: "))); … … 465 476 466 477 JButton buttonOpen = new JButton(tr("Open another GPX trace")); 467 buttonOpen.setIcon(ImageProvider.get("dialogs/geoimage/geoimage-open"));468 478 buttonOpen.addActionListener(new LoadGpxDataActionListener()); 469 470 479 panelCb.add(buttonOpen); 471 480 … … 473 482 panelTf.setLayout(new GridBagLayout()); 474 483 475 GridBagConstraints gc = new GridBagConstraints(); 476 gc.anchor = GridBagConstraints.WEST; 477 478 gc.gridx = gc.gridy = 0; 479 gc.gridwidth = gc.gridheight = 1; 480 gc.fill = GridBagConstraints.NONE; 481 gc.weightx = gc.weighty = 0.0; 482 panelTf.add(new JLabel(tr("Timezone: ")), gc); 483 484 float gpstimezone = Float.parseFloat(Main.pref.get("geoimage.doublegpstimezone", "0.0")); 485 if (gpstimezone == 0.0) { 486 gpstimezone = - Long.parseLong(Main.pref.get("geoimage.gpstimezone", "0")); 487 } 488 tfTimezone = new JTextField(); 489 tfTimezone.setText(formatTimezone(gpstimezone)); 490 491 gc.gridx = 1; 492 gc.gridy = 0; 493 gc.gridwidth = gc.gridheight = 1; 494 gc.fill = GridBagConstraints.HORIZONTAL; 495 gc.weightx = 1.0; 496 gc.weighty = 0.0; 497 panelTf.add(tfTimezone, gc); 498 499 gc.gridx = 0; 500 gc.gridy = 1; 501 gc.gridwidth = gc.gridheight = 1; 502 gc.fill = GridBagConstraints.NONE; 503 gc.weightx = gc.weighty = 0.0; 504 panelTf.add(new JLabel(tr("Offset:")), gc); 505 506 long delta = Long.parseLong(Main.pref.get("geoimage.delta", "0")) / 1000; 507 tfOffset = new JTextField(); 484 String prefTimezone = Main.pref.get("geoimage.timezone", "0:00"); 485 if (prefTimezone == null) { 486 prefTimezone = "0:00"; 487 } 488 try { 489 timezone = parseTimezone(prefTimezone); 490 } catch (ParseException e) { 491 timezone = 0; 492 } 493 494 tfTimezone = new JTextField(10); 495 tfTimezone.setText(formatTimezone(timezone)); 496 497 try { 498 delta = parseOffset(Main.pref.get("geoimage.delta", "0")); 499 } catch (ParseException e) { 500 delta = 0; 501 } 502 delta = delta / 1000; 503 504 tfOffset = new JTextField(10); 508 505 tfOffset.setText(Long.toString(delta)); 509 gc.gridx = gc.gridy = 1; 510 gc.gridwidth = gc.gridheight = 1; 511 gc.fill = GridBagConstraints.HORIZONTAL; 512 gc.weightx = 1.0; 513 gc.weighty = 0.0; 514 panelTf.add(tfOffset, gc); 515 516 JButton buttonViewGpsPhoto = new JButton(tr("<html>I can take a picture of my GPS receiver.<br>" 517 + "Can this help?</html>")); 506 507 JPanel panelBtn = new JPanel(); 508 509 JButton buttonViewGpsPhoto = new JButton(tr("<html>Use photo of an accurate clock,<br>" 510 + "e.g. GPS receiver display</html>")); 511 buttonViewGpsPhoto.setIcon(ImageProvider.get("clock")); 518 512 buttonViewGpsPhoto.addActionListener(new SetOffsetActionListener()); 519 gc.gridx = 2; 520 gc.gridy = 0; 521 gc.gridwidth = 1; 522 gc.gridheight = 2; 523 gc.fill = GridBagConstraints.BOTH; 524 gc.weightx = 0.5; 525 gc.weighty = 1.0; 526 panelTf.add(buttonViewGpsPhoto, gc); 527 528 gc.gridx = 0; 529 gc.gridy = 2; 530 gc.gridwidth = gc.gridheight = 1; 531 gc.fill = GridBagConstraints.NONE; 532 gc.weightx = gc.weighty = 0.0; 533 panelTf.add(new JLabel(tr("Update position for: ")), gc); 534 535 gc.gridx = 1; 536 gc.gridy = 2; 537 gc.gridwidth = 2; 538 gc.gridheight = 1; 539 gc.fill = GridBagConstraints.HORIZONTAL; 540 gc.weightx = 1.0; 541 gc.weighty = 0.0; 542 rbAllImg = new JRadioButton(tr("All images")); 543 panelTf.add(rbAllImg, gc); 544 545 gc.gridx = 1; 546 gc.gridy = 3; 547 gc.gridwidth = 2; 548 gc.gridheight = 1; 549 gc.fill = GridBagConstraints.HORIZONTAL; 550 gc.weightx = 1.0; 551 gc.weighty = 0.0; 552 rbNoExifImg = new JRadioButton(tr("Images with no exif position")); 553 panelTf.add(rbNoExifImg, gc); 554 555 gc.gridx = 1; 556 gc.gridy = 4; 557 gc.gridwidth = 2; 558 gc.gridheight = 1; 559 gc.fill = GridBagConstraints.HORIZONTAL; 560 gc.weightx = 1.0; 561 gc.weighty = 0.0; 562 rbUntaggedImg = new JRadioButton(tr("Not yet tagged images")); 563 panelTf.add(rbUntaggedImg, gc); 564 565 gc.gridx = 0; 566 gc.gridy = 5; 567 gc.gridwidth = 2; 568 gc.gridheight = 1; 569 gc.fill = GridBagConstraints.NONE; 570 gc.weightx = gc.weighty = 0.0; 571 yLayer.useThumbs = Main.pref.getBoolean("geoimage.showThumbs", false); 572 JCheckBox cbShowThumbs = new JCheckBox(tr("Show Thumbnail images on the map"), yLayer.useThumbs); 573 panelTf.add(cbShowThumbs, gc); 574 575 ButtonGroup group = new ButtonGroup(); 576 group.add(rbAllImg); 577 group.add(rbNoExifImg); 578 group.add(rbUntaggedImg); 579 580 rbUntaggedImg.setSelected(true); 581 582 panel = new JPanel(); 583 panel.setLayout(new BorderLayout()); 584 585 panel.add(panelCb, BorderLayout.PAGE_START); 586 panel.add(panelTf, BorderLayout.CENTER); 587 588 boolean isOk = false; 589 GpxDataWrapper selectedGpx = null; 590 while (! isOk) { 591 ExtendedDialog dialog = new ExtendedDialog( 592 Main.parent, 593 tr("Correlate images with GPX track"), 594 new String[] { tr("Correlate"), tr("Auto-Guess"), tr("Cancel") } 595 ); 596 597 dialog.setContent(panel); 598 dialog.setButtonIcons(new String[] { "ok.png", "dialogs/geoimage/gpx2imgManual.png", "cancel.png" }); 599 dialog.showDialog(); 600 int answer = dialog.getValue(); 601 if(answer != 1 && answer != 2) 513 514 JButton buttonAutoGuess = new JButton(tr("Auto-Guess")); 515 buttonAutoGuess.addActionListener(new AutoGuessActionListener()); 516 517 JButton buttonAdjust = new JButton(tr("Manual adjust")); 518 buttonAdjust.addActionListener(new AdjustActionListener()); 519 520 JLabel labelPosition = new JLabel(tr("Override position for: ")); 521 522 int numAll = getSortedImgList(true, true).size(); 523 int numExif = numAll - getSortedImgList(false, true).size(); 524 int numTagged = numAll - getSortedImgList(true, false).size(); 525 526 cbExifImg = new JCheckBox(tr("Images with geo location in exif data ({0}/{1})", numExif, numAll)); 527 cbExifImg.setEnabled(numExif != 0); 528 529 cbTaggedImg = new JCheckBox(tr("Images that are already tagged ({0}/{1})", numTagged, numAll), true); 530 cbTaggedImg.setEnabled(numTagged != 0); 531 532 labelPosition.setEnabled(cbExifImg.isEnabled() || cbTaggedImg.isEnabled()); 533 534 boolean ticked = yLayer.thumbsLoaded || Main.pref.getBoolean("geoimage.showThumbs", false); 535 cbShowThumbs = new JCheckBox(tr("Show Thumbnail images on the map"), ticked); 536 cbShowThumbs.setEnabled(!yLayer.thumbsLoaded); 537 /*cbShowThumbs.addItemListener(new ItemListener() { 538 public void itemStateChanged(ItemEvent e) { 539 if (e.getStateChange() == ItemEvent.SELECTED) { 540 yLayer.loadThumbs(); 541 } else { 542 } 543 } 544 });*/ 545 546 int y=0; 547 GBC gbc = GBC.eol(); 548 gbc.gridx = 0; 549 gbc.gridy = y++; 550 panelTf.add(panelCb, gbc); 551 552 553 gbc = GBC.eol().fill(GBC.HORIZONTAL).insets(0,0,0,12); 554 gbc.gridx = 0; 555 gbc.gridy = y++; 556 panelTf.add(new JSeparator(SwingConstants.HORIZONTAL), gbc); 557 558 559 gbc = GBC.std(); 560 gbc.gridx = 0; 561 gbc.gridy = y; 562 panelTf.add(new JLabel(tr("Timezone: ")), gbc); 563 564 gbc = GBC.std().fill(GBC.HORIZONTAL); 565 gbc.gridx = 1; 566 gbc.gridy = y++; 567 gbc.weightx = 1.; 568 panelTf.add(tfTimezone, gbc); 569 570 gbc = GBC.std(); 571 gbc.gridx = 0; 572 gbc.gridy = y; 573 panelTf.add(new JLabel(tr("Offset:")), gbc); 574 575 gbc = GBC.std().fill(GBC.HORIZONTAL); 576 gbc.gridx = 1; 577 gbc.gridy = y++; 578 gbc.weightx = 1.; 579 panelTf.add(tfOffset, gbc); 580 581 gbc = GBC.std().insets(5,5,5,5); 582 gbc.gridx = 2; 583 gbc.gridy = y-2; 584 gbc.gridheight = 2; 585 gbc.gridwidth = 2; 586 gbc.fill = GridBagConstraints.BOTH; 587 gbc.weightx = 0.5; 588 panelTf.add(buttonViewGpsPhoto, gbc); 589 590 gbc = GBC.std().fill(GBC.BOTH).insets(5,5,5,5); 591 gbc.gridx = 2; 592 gbc.gridy = y++; 593 gbc.weightx = 0.5; 594 panelTf.add(buttonAutoGuess, gbc); 595 596 gbc.gridx = 3; 597 panelTf.add(buttonAdjust, gbc); 598 599 gbc = GBC.eol().fill(GBC.HORIZONTAL).insets(0,12,0,0); 600 gbc.gridx = 0; 601 gbc.gridy = y++; 602 panelTf.add(new JSeparator(SwingConstants.HORIZONTAL), gbc); 603 604 gbc = GBC.eol(); 605 gbc.gridx = 0; 606 gbc.gridy = y++; 607 panelTf.add(labelPosition, gbc); 608 609 gbc = GBC.eol(); 610 gbc.gridx = 1; 611 gbc.gridy = y++; 612 panelTf.add(cbExifImg, gbc); 613 614 gbc = GBC.eol(); 615 gbc.gridx = 1; 616 gbc.gridy = y++; 617 panelTf.add(cbTaggedImg, gbc); 618 619 gbc = GBC.eol(); 620 gbc.gridx = 0; 621 gbc.gridy = y++; 622 panelTf.add(cbShowThumbs, gbc); 623 624 final JPanel statusBar = new JPanel(); 625 statusBar.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); 626 statusBar.setBorder(BorderFactory.createLoweredBevelBorder()); 627 statusBarText = new JLabel(" "); 628 statusBarText.setFont(statusBarText.getFont().deriveFont(8)); 629 statusBar.add(statusBarText); 630 631 statusBarListener = new StatusBarListener() { 632 @Override 633 public void updateStatusBar() { 634 statusBarText.setText(statusText()); 635 } 636 private String statusText() { 637 try { 638 timezone = parseTimezone(tfTimezone.getText().trim()); 639 delta = parseOffset(tfOffset.getText().trim()); 640 } catch (ParseException e) { 641 return e.getMessage(); 642 } 643 644 // Construct a list of images that have a date, and sort them on the date. 645 ArrayList<ImageEntry> dateImgLst = getSortedImgList(); 646 for (ImageEntry ie : dateImgLst) { 647 ie.cleanTmp(); 648 } 649 650 GpxDataWrapper selGpx = selectedGPX(false); 651 if (selGpx == null) 652 return tr("No gpx selected"); 653 654 lastNumMatched = matchGpxTrack(dateImgLst, selGpx.data, (long) (timezone * 3600) + delta); 655 656 return tr("<html>Matched <b>{0}</b> of <b>{1}</b> photos to GPX track.", lastNumMatched, dateImgLst.size()); 657 } 658 }; 659 660 tfTimezone.getDocument().addDocumentListener(statusBarListener); 661 tfOffset.getDocument().addDocumentListener(statusBarListener); 662 cbExifImg.addItemListener(statusBarListener); 663 cbTaggedImg.addItemListener(statusBarListener); 664 665 statusBarListener.updateStatusBar(); 666 667 outerPanel = new JPanel(); 668 outerPanel.setLayout(new BorderLayout()); 669 outerPanel.add(statusBar, BorderLayout.PAGE_END); 670 671 672 syncDialog = new ExtendedDialog( 673 Main.parent, 674 tr("Correlate images with GPX track"), 675 new String[] { tr("Correlate"), tr("Cancel") }, 676 false 677 ); 678 syncDialog.setContent(panelTf, false); 679 syncDialog.setButtonIcons(new String[] { "ok.png", "cancel.png" }); 680 syncDialog.setupDialog(); 681 outerPanel.add(syncDialog.getContentPane(), BorderLayout.PAGE_START); 682 syncDialog.setContentPane(outerPanel); 683 syncDialog.pack(); 684 syncDialog.addWindowListener(new WindowAdapter() { 685 final int CANCEL = -1; 686 final int DONE = 0; 687 final int AGAIN = 1; 688 final int NOTHING = 2; 689 private int checkAndSave() { 690 if (syncDialog.isVisible()) { 691 // nothing happened: JOSM was minimized or similar 692 return NOTHING; 693 } 694 int answer = syncDialog.getValue(); 695 if(answer != 1) 696 return CANCEL; 697 698 // Parse values again, to display an error if the format is not recognized 699 try { 700 timezone = parseTimezone(tfTimezone.getText().trim()); 701 } catch (ParseException e) { 702 JOptionPane.showMessageDialog(Main.parent, e.getMessage(), 703 tr("Invalid timezone"), JOptionPane.ERROR_MESSAGE); 704 return AGAIN; 705 } 706 707 try { 708 delta = parseOffset(tfOffset.getText().trim()); 709 } catch (ParseException e) { 710 JOptionPane.showMessageDialog(Main.parent, e.getMessage(), 711 tr("Invalid offset"), JOptionPane.ERROR_MESSAGE); 712 return AGAIN; 713 } 714 715 if (lastNumMatched == 0) { 716 if (new ExtendedDialog( 717 Main.parent, 718 tr("Correlate images with GPX track"), 719 new String[] { tr("OK"), tr("Try Again") }). 720 setContent(tr("No images could be matched!")). 721 setButtonIcons(new String[] { "ok.png", "dialogs/refresh.png"}). 722 showDialog().getValue() == 2) 723 return AGAIN; 724 } 725 return DONE; 726 } 727 728 public void windowDeactivated(WindowEvent e) { 729 int result = checkAndSave(); 730 switch (result) { 731 case NOTHING: 732 break; 733 case CANCEL: 734 { 735 for (ImageEntry ie : yLayer.data) { 736 ie.tmp = null; 737 } 738 yLayer.updateBufferAndRepaint(); 739 break; 740 } 741 case AGAIN: 742 actionPerformed(null); 743 break; 744 case DONE: 745 { 746 Main.pref.put("geoimage.timezone", formatTimezone(timezone)); 747 Main.pref.put("geoimage.delta", Long.toString(delta * 1000)); 748 Main.pref.put("geoimage.showThumbs", yLayer.useThumbs); 749 750 yLayer.useThumbs = cbShowThumbs.isSelected();//FIXME 751 yLayer.loadThumbs(); 752 753 // Search whether an other layer has yet defined some bounding box. 754 // If none, we'll zoom to the bounding box of the layer with the photos. 755 boolean boundingBoxedLayerFound = false; 756 for (Layer l: Main.map.mapView.getAllLayers()) { 757 if (l != yLayer) { 758 BoundingXYVisitor bbox = new BoundingXYVisitor(); 759 l.visitBoundingBox(bbox); 760 if (bbox.getBounds() != null) { 761 boundingBoxedLayerFound = true; 762 break; 763 } 764 } 765 } 766 if (! boundingBoxedLayerFound) { 767 BoundingXYVisitor bbox = new BoundingXYVisitor(); 768 yLayer.visitBoundingBox(bbox); 769 Main.map.mapView.recalculateCenterScale(bbox); 770 } 771 772 773 for (ImageEntry ie : yLayer.data) { 774 ie.applyTmp(); 775 } 776 777 yLayer.updateBufferAndRepaint(); 778 779 780 break; 781 } 782 default: 783 throw new IllegalStateException(); 784 } 785 } 786 }); 787 syncDialog.showDialog(); 788 } 789 790 private static abstract class StatusBarListener implements DocumentListener, ItemListener { 791 public void insertUpdate(DocumentEvent ev) { 792 updateStatusBar(); 793 } 794 public void removeUpdate(DocumentEvent ev) { 795 updateStatusBar(); 796 } 797 public void changedUpdate(DocumentEvent ev) { 798 } 799 public void itemStateChanged(ItemEvent e) { 800 updateStatusBar(); 801 } 802 abstract public void updateStatusBar(); 803 } 804 805 /** 806 * Presents dialog with sliders for manual adjust. 807 */ 808 private class AdjustActionListener implements ActionListener { 809 810 public void actionPerformed(ActionEvent arg0) { 811 812 long diff = delta + Math.round(timezone*60*60); 813 814 double diffInH = (double)diff/(60*60); // hours 815 816 // Find day difference 817 final int dayOffset = (int)Math.round(diffInH / 24); // days 818 double tmz = diff - dayOffset*24*60*60; // seconds 819 820 // In hours, rounded to two decimal places 821 tmz = (double)Math.round(tmz*100/(60*60)) / 100; 822 823 // Due to imprecise clocks we might get a "+3:28" timezone, which should obviously be 3:30 with 824 // -2 minutes offset. This determines the real timezone and finds offset. 825 double fixTimezone = (double)Math.round(tmz * 2)/2; // hours, rounded to one decimal place 826 int offset = (int)Math.round(diff - fixTimezone*60*60) - dayOffset*24*60*60; // seconds 827 828 // Info Labels 829 final JLabel lblMatches = new JLabel(); 830 831 // Timezone Slider 832 // The slider allows to switch timezon from -12:00 to 12:00 in 30 minutes 833 // steps. Therefore the range is -24 to 24. 834 final JLabel lblTimezone = new JLabel(); 835 final JSlider sldTimezone = new JSlider(-24, 24, 0); 836 sldTimezone.setPaintLabels(true); 837 Hashtable<Integer,JLabel> labelTable = new Hashtable<Integer, JLabel>(); 838 labelTable.put(-24, new JLabel("-12:00")); 839 labelTable.put(-12, new JLabel( "-6:00")); 840 labelTable.put( 0, new JLabel( "0:00")); 841 labelTable.put( 12, new JLabel( "6:00")); 842 labelTable.put( 24, new JLabel( "12:00")); 843 sldTimezone.setLabelTable(labelTable); 844 845 // Minutes Slider 846 final JLabel lblMinutes = new JLabel(); 847 final JSlider sldMinutes = new JSlider(-15, 15, 0); 848 sldMinutes.setPaintLabels(true); 849 sldMinutes.setMajorTickSpacing(5); 850 851 // Seconds slider 852 final JLabel lblSeconds = new JLabel(); 853 final JSlider sldSeconds = new JSlider(-60, 60, 0); 854 sldSeconds.setPaintLabels(true); 855 sldSeconds.setMajorTickSpacing(30); 856 857 // This is called whenever one of the sliders is moved. 858 // It updates the labels and also calls the "match photos" code 859 class sliderListener implements ChangeListener { 860 public void stateChanged(ChangeEvent e) { 861 // parse slider position into real timezone 862 double tz = Math.abs(sldTimezone.getValue()); 863 String zone = tz % 2 == 0 864 ? (int)Math.floor(tz/2) + ":00" 865 : (int)Math.floor(tz/2) + ":30"; 866 if(sldTimezone.getValue() < 0) { 867 zone = "-" + zone; 868 } 869 870 lblTimezone.setText(tr("Timezone: {0}", zone)); 871 lblMinutes.setText(tr("Minutes: {0}", sldMinutes.getValue())); 872 lblSeconds.setText(tr("Seconds: {0}", sldSeconds.getValue())); 873 874 try { 875 timezone = parseTimezone(zone); 876 } catch (ParseException pe) { 877 throw new RuntimeException(); 878 } 879 delta = sldMinutes.getValue()*60 + sldSeconds.getValue(); 880 881 tfTimezone.getDocument().removeDocumentListener(statusBarListener); 882 tfOffset.getDocument().removeDocumentListener(statusBarListener); 883 884 tfTimezone.setText(formatTimezone(timezone)); 885 tfOffset.setText(Long.toString(delta + dayOffset*24*60*60)); // add the day offset to the offset field 886 887 tfTimezone.getDocument().addDocumentListener(statusBarListener); 888 tfOffset.getDocument().addDocumentListener(statusBarListener); 889 890 891 892 lblMatches.setText(statusBarText.getText() + tr("<br>(Time difference of {0} days)", Math.abs(dayOffset))); 893 894 statusBarListener.updateStatusBar(); 895 yLayer.updateBufferAndRepaint(); 896 } 897 } 898 899 // Put everything together 900 JPanel p = new JPanel(new GridBagLayout()); 901 p.setPreferredSize(new Dimension(400, 230)); 902 p.add(lblMatches, GBC.eol().fill()); 903 p.add(lblTimezone, GBC.eol().fill()); 904 p.add(sldTimezone, GBC.eol().fill().insets(0, 0, 0, 10)); 905 p.add(lblMinutes, GBC.eol().fill()); 906 p.add(sldMinutes, GBC.eol().fill().insets(0, 0, 0, 10)); 907 p.add(lblSeconds, GBC.eol().fill()); 908 p.add(sldSeconds, GBC.eol().fill()); 909 910 // If there's an error in the calculation the found values 911 // will be off range for the sliders. Catch this error 912 // and inform the user about it. 913 try { 914 sldTimezone.setValue((int)(fixTimezone*2)); 915 sldMinutes.setValue(offset/60); 916 sldSeconds.setValue(offset%60); 917 } catch(Exception e) { 918 JOptionPane.showMessageDialog(Main.parent, 919 tr("An error occurred while trying to match the photos to the GPX track." 920 +" You can adjust the sliders to manually match the photos."), 921 tr("Matching photos to track failed"), 922 JOptionPane.WARNING_MESSAGE); 923 } 924 925 // Call the sliderListener once manually so labels get adjusted 926 new sliderListener().stateChanged(null); 927 // Listeners added here, otherwise it tries to match three times 928 // (when setting the default values) 929 sldTimezone.addChangeListener(new sliderListener()); 930 sldMinutes.addChangeListener(new sliderListener()); 931 sldSeconds.addChangeListener(new sliderListener()); 932 933 // There is no way to cancel this dialog, all changes get applied 934 // immediately. Therefore "Close" is marked with an "OK" icon. 935 // Settings are only saved temporarily to the layer. 936 new ExtendedDialog(Main.parent, 937 tr("Adjust timezone and offset"), 938 new String[] { tr("Close")}). 939 setContent(p).setButtonIcons(new String[] {"ok.png"}).showDialog(); 940 } 941 } 942 943 private class AutoGuessActionListener implements ActionListener { 944 945 public void actionPerformed(ActionEvent arg0) { 946 GpxDataWrapper gpxW = selectedGPX(true); 947 if (gpxW == null) 602 948 return; 603 604 // Check the selected values 605 Object item = cbGpx.getSelectedItem(); 606 607 if (item == null || ! (item instanceof GpxDataWrapper)) { 608 JOptionPane.showMessageDialog(Main.parent, tr("You should select a GPX track"), 609 tr("No selected GPX track"), JOptionPane.ERROR_MESSAGE ); 610 continue; 611 } 612 selectedGpx = ((GpxDataWrapper) item); 613 614 if (answer == 2) { 615 autoGuess(selectedGpx.data); 949 GpxData gpx = gpxW.data; 950 951 ArrayList<ImageEntry> imgs = getSortedImgList(); 952 PrimaryDateParser dateParser = new PrimaryDateParser(); 953 954 // no images found, exit 955 if(imgs.size() <= 0) { 956 JOptionPane.showMessageDialog(Main.parent, 957 tr("The selected photos don't contain time information."), 958 tr("Photos don't contain time information"), JOptionPane.WARNING_MESSAGE); 616 959 return; 617 960 } 618 961 619 Float timezoneValue = parseTimezone(tfTimezone.getText().trim()); 620 if (timezoneValue == null) { 621 JOptionPane.showMessageDialog(Main.parent, tr("Error while parsing timezone.\nExpected format: {0}", "+H:MM"), 622 tr("Invalid timezone"), JOptionPane.ERROR_MESSAGE); 623 continue; 624 } 625 gpstimezone = timezoneValue.floatValue(); 626 627 String deltaText = tfOffset.getText().trim(); 628 if (deltaText.length() > 0) { 629 try { 630 if(deltaText.startsWith("+")) { 631 deltaText = deltaText.substring(1); 962 // Init variables 963 long firstExifDate = imgs.get(0).time.getTime()/1000; 964 965 long firstGPXDate = -1; 966 // Finds first GPX point 967 outer: for (GpxTrack trk : gpx.tracks) { 968 for (Collection<WayPoint> segment : trk.trackSegs) { 969 for (WayPoint curWp : segment) { 970 String curDateWpStr = (String) curWp.attr.get("time"); 971 if (curDateWpStr == null) { 972 continue; 973 } 974 975 try { 976 firstGPXDate = dateParser.parse(curDateWpStr).getTime()/1000; 977 break outer; 978 } catch(Exception e) {} 632 979 } 633 delta = Long.parseLong(deltaText); 634 } catch(NumberFormatException nfe) { 635 JOptionPane.showMessageDialog(Main.parent, tr("Error while parsing offset.\nExpected format: {0}", "number"), 636 tr("Invalid offset"), JOptionPane.ERROR_MESSAGE); 637 continue; 638 } 639 } else { 640 delta = 0; 641 } 642 643 yLayer.useThumbs = cbShowThumbs.isSelected(); 644 645 Main.pref.put("geoimage.doublegpstimezone", Double.toString(gpstimezone)); 646 Main.pref.put("geoimage.gpstimezone", Long.toString(- ((long) gpstimezone))); 647 Main.pref.put("geoimage.delta", Long.toString(delta * 1000)); 648 Main.pref.put("geoimage.showThumbs", yLayer.useThumbs); 649 isOk = true; 650 651 if (yLayer.useThumbs) { 652 yLayer.thumbsloader = new ThumbsLoader(yLayer); 653 Thread t = new Thread(yLayer.thumbsloader); 654 t.setPriority(Thread.MIN_PRIORITY); 655 t.start(); 656 } 657 658 } 659 660 // Construct a list of images that have a date, and sort them on the date. 661 ArrayList<ImageEntry> dateImgLst = getSortedImgList(rbAllImg.isSelected(), rbNoExifImg.isSelected()); 662 663 int matched = matchGpxTrack(dateImgLst, selectedGpx.data, (long) (gpstimezone * 3600) + delta); 664 665 // Search whether an other layer has yet defined some bounding box. 666 // If none, we'll zoom to the bounding box of the layer with the photos. 667 boolean boundingBoxedLayerFound = false; 668 for (Layer l: Main.map.mapView.getAllLayers()) { 669 if (l != yLayer) { 670 BoundingXYVisitor bbox = new BoundingXYVisitor(); 671 l.visitBoundingBox(bbox); 672 if (bbox.getBounds() != null) { 673 boundingBoxedLayerFound = true; 674 break; 675 } 676 } 677 } 678 if (! boundingBoxedLayerFound) { 679 BoundingXYVisitor bbox = new BoundingXYVisitor(); 680 yLayer.visitBoundingBox(bbox); 681 Main.map.mapView.recalculateCenterScale(bbox); 682 } 683 684 Main.map.repaint(); 685 686 JOptionPane.showMessageDialog(Main.parent, tr("Found {0} matches of {1} in GPX track {2}", matched, dateImgLst.size(), selectedGpx.name), 687 tr("GPX Track loaded"), 688 ((dateImgLst.size() > 0 && matched == 0) ? JOptionPane.WARNING_MESSAGE 689 : JOptionPane.INFORMATION_MESSAGE)); 690 691 } 692 693 // These variables all belong to "auto guess" but need to be accessible 694 // from the slider change listener 695 private int dayOffset; 696 private JLabel lblMatches; 697 private JLabel lblOffset; 698 private JLabel lblTimezone; 699 private JLabel lblMinutes; 700 private JLabel lblSeconds; 701 private JSlider sldTimezone; 702 private JSlider sldMinutes; 703 private JSlider sldSeconds; 704 private GpxData autoGpx; 705 private ArrayList<ImageEntry> autoImgs; 706 private long firstGPXDate = -1; 707 private long firstExifDate = -1; 708 709 /** 710 * Tries to automatically match opened photos to a given GPX track. Changes are applied 711 * immediately. Presents dialog with sliders for manual adjust. 712 * @param GpxData The GPX track to match against 713 */ 714 private void autoGuess(GpxData gpx) { 715 autoGpx = gpx; 716 autoImgs = getSortedImgList(true, false); 717 PrimaryDateParser dateParser = new PrimaryDateParser(); 718 719 // no images found, exit 720 if(autoImgs.size() <= 0) { 721 JOptionPane.showMessageDialog(Main.parent, 722 tr("The selected photos don't contain time information."), 723 tr("Photos don't contain time information"), JOptionPane.WARNING_MESSAGE); 724 return; 725 } 726 727 ImageViewerDialog dialog = ImageViewerDialog.getInstance(); 728 dialog.showDialog(); 729 // Will show first photo if none is selected yet 730 if(!dialog.hasImage()) { 731 yLayer.showNextPhoto(); 732 // FIXME: If the dialog is minimized it will not be maximized. ToggleDialog is 733 // in need of a complete re-write to allow this in a reasonable way. 734 } 735 736 // Init variables 737 firstExifDate = autoImgs.get(0).time.getTime()/1000; 738 739 740 // Finds first GPX point 741 outer: for (GpxTrack trk : gpx.tracks) { 742 for (Collection<WayPoint> segment : trk.trackSegs) { 743 for (WayPoint curWp : segment) { 744 String curDateWpStr = (String) curWp.attr.get("time"); 745 if (curDateWpStr == null) { 746 continue; 747 } 748 749 try { 750 firstGPXDate = dateParser.parse(curDateWpStr).getTime()/1000; 751 break outer; 752 } catch(Exception e) {} 753 } 754 } 755 } 756 757 // No GPX timestamps found, exit 758 if(firstGPXDate < 0) { 759 JOptionPane.showMessageDialog(Main.parent, 760 tr("The selected GPX track doesn't contain timestamps. Please select another one."), 761 tr("GPX Track has no time information"), JOptionPane.WARNING_MESSAGE); 762 return; 763 } 764 765 // seconds 766 long diff = (yLayer.hasTimeoffset) 767 ? yLayer.timeoffset 768 : firstExifDate - firstGPXDate; 769 yLayer.timeoffset = diff; 770 yLayer.hasTimeoffset = true; 771 772 double diffInH = (double)diff/(60*60); // hours 773 774 // Find day difference 775 dayOffset = (int)Math.round(diffInH / 24); // days 776 double timezone = diff - dayOffset*24*60*60; // seconds 777 778 // In hours, rounded to two decimal places 779 timezone = (double)Math.round(timezone*100/(60*60)) / 100; 780 781 // Due to imprecise clocks we might get a "+3:28" timezone, which should obviously be 3:30 with 782 // -2 minutes offset. This determines the real timezone and finds offset. 783 double fixTimezone = (double)Math.round(timezone * 2)/2; // hours, rounded to one decimal place 784 int offset = (int)Math.round(diff - fixTimezone*60*60) - dayOffset*24*60*60; // seconds 785 786 /*System.out.println("phto " + firstExifDate); 787 System.out.println("gpx " + firstGPXDate); 788 System.out.println("diff " + diff); 789 System.out.println("difh " + diffInH); 790 System.out.println("days " + dayOffset); 791 System.out.println("time " + timezone); 792 System.out.println("fix " + fixTimezone); 793 System.out.println("offt " + offset);*/ 794 795 // This is called whenever one of the sliders is moved. 796 // It updates the labels and also calls the "match photos" code 797 class sliderListener implements ChangeListener { 798 public void stateChanged(ChangeEvent e) { 799 // parse slider position into real timezone 800 double tz = Math.abs(sldTimezone.getValue()); 801 String zone = tz % 2 == 0 802 ? (int)Math.floor(tz/2) + ":00" 803 : (int)Math.floor(tz/2) + ":30"; 804 if(sldTimezone.getValue() < 0) { 805 zone = "-" + zone; 806 } 807 808 lblTimezone.setText(tr("Timezone: {0}", zone)); 809 lblMinutes.setText(tr("Minutes: {0}", sldMinutes.getValue())); 810 lblSeconds.setText(tr("Seconds: {0}", sldSeconds.getValue())); 811 812 float gpstimezone = parseTimezone(zone).floatValue(); 813 814 // Reset previous position 815 for(ImageEntry x : autoImgs) { 816 x.pos = null; 817 } 818 819 long timediff = (long) (gpstimezone * 3600) 820 + dayOffset*24*60*60 821 + sldMinutes.getValue()*60 822 + sldSeconds.getValue(); 823 824 int matched = matchGpxTrack(autoImgs, autoGpx, timediff); 825 826 lblMatches.setText( 827 tr("Matched {0} of {1} photos to GPX track.", matched, autoImgs.size()) 828 + ((Math.abs(dayOffset) == 0) 829 ? "" 830 : " " + tr("(Time difference of {0} days)", Math.abs(dayOffset)) 831 ) 832 ); 833 834 int offset = (int)(firstGPXDate+timediff-firstExifDate); 835 int o = Math.abs(offset); 836 lblOffset.setText( 837 tr("Offset between track and photos: {0}m {1}s", 838 (offset < 0 ? "-" : "") + Long.toString(o/60), 839 Long.toString(o%60) 840 ) 841 ); 842 843 yLayer.timeoffset = timediff; 844 Main.main.map.repaint(); 845 } 846 } 847 848 // Info Labels 849 lblMatches = new JLabel(); 850 lblOffset = new JLabel(); 851 852 // Timezone Slider 853 // The slider allows to switch timezon from -12:00 to 12:00 in 30 minutes 854 // steps. Therefore the range is -24 to 24. 855 lblTimezone = new JLabel(); 856 sldTimezone = new JSlider(-24, 24, 0); 857 sldTimezone.setPaintLabels(true); 858 Hashtable<Integer,JLabel> labelTable = new Hashtable<Integer, JLabel>(); 859 labelTable.put(-24, new JLabel("-12:00")); 860 labelTable.put(-12, new JLabel( "-6:00")); 861 labelTable.put( 0, new JLabel( "0:00")); 862 labelTable.put( 12, new JLabel( "6:00")); 863 labelTable.put( 24, new JLabel( "12:00")); 864 sldTimezone.setLabelTable(labelTable); 865 866 // Minutes Slider 867 lblMinutes = new JLabel(); 868 sldMinutes = new JSlider(-15, 15, 0); 869 sldMinutes.setPaintLabels(true); 870 sldMinutes.setMajorTickSpacing(5); 871 872 // Seconds slider 873 lblSeconds = new JLabel(); 874 sldSeconds = new JSlider(-60, 60, 0); 875 sldSeconds.setPaintLabels(true); 876 sldSeconds.setMajorTickSpacing(30); 877 878 // Put everything together 879 JPanel p = new JPanel(new GridBagLayout()); 880 p.setPreferredSize(new Dimension(400, 230)); 881 p.add(lblMatches, GBC.eol().fill()); 882 p.add(lblOffset, GBC.eol().fill().insets(0, 0, 0, 10)); 883 p.add(lblTimezone, GBC.eol().fill()); 884 p.add(sldTimezone, GBC.eol().fill().insets(0, 0, 0, 10)); 885 p.add(lblMinutes, GBC.eol().fill()); 886 p.add(sldMinutes, GBC.eol().fill().insets(0, 0, 0, 10)); 887 p.add(lblSeconds, GBC.eol().fill()); 888 p.add(sldSeconds, GBC.eol().fill()); 889 890 // If there's an error in the calculation the found values 891 // will be off range for the sliders. Catch this error 892 // and inform the user about it. 893 try { 894 sldTimezone.setValue((int)(fixTimezone*2)); 895 sldMinutes.setValue(offset/60); 896 sldSeconds.setValue(offset%60); 897 } catch(Exception e) { 898 JOptionPane.showMessageDialog(Main.parent, 899 tr("An error occurred while trying to match the photos to the GPX track." 900 +" You can adjust the sliders to manually match the photos."), 901 tr("Matching photos to track failed"), 902 JOptionPane.WARNING_MESSAGE); 903 } 904 905 // Call the sliderListener once manually so labels get adjusted 906 new sliderListener().stateChanged(null); 907 // Listeners added here, otherwise it tries to match three times 908 // (when setting the default values) 909 sldTimezone.addChangeListener(new sliderListener()); 910 sldMinutes.addChangeListener(new sliderListener()); 911 sldSeconds.addChangeListener(new sliderListener()); 912 913 // There is no way to cancel this dialog, all changes get applied 914 // immediately. Therefore "Close" is marked with an "OK" icon. 915 // Settings are only saved temporarily to the layer. 916 ExtendedDialog d = new ExtendedDialog(Main.parent, 917 tr("Adjust timezone and offset"), 918 new String[] { tr("Close"), tr("Default Values") } 919 ); 920 921 d.setContent(p); 922 d.setButtonIcons(new String[] { "ok.png", "dialogs/refresh.png"}); 923 d.showDialog(); 924 int answer = d.getValue(); 925 // User wants default values; discard old result and re-open dialog 926 if(answer == 2) { 927 yLayer.hasTimeoffset = false; 928 autoGuess(gpx); 929 } 930 } 931 980 } 981 } 982 983 // No GPX timestamps found, exit 984 if(firstGPXDate < 0) { 985 JOptionPane.showMessageDialog(Main.parent, 986 tr("The selected GPX track doesn't contain timestamps. Please select another one."), 987 tr("GPX Track has no time information"), JOptionPane.WARNING_MESSAGE); 988 return; 989 } 990 991 // seconds 992 long diff = firstExifDate - firstGPXDate; 993 994 double diffInH = (double)diff/(60*60); // hours 995 996 // Find day difference 997 int dayOffset = (int)Math.round(diffInH / 24); // days 998 double tz = diff - dayOffset*24*60*60; // seconds 999 1000 // In hours, rounded to two decimal places 1001 tz = (double)Math.round(tz*100/(60*60)) / 100; 1002 1003 // Due to imprecise clocks we might get a "+3:28" timezone, which should obviously be 3:30 with 1004 // -2 minutes offset. This determines the real timezone and finds offset. 1005 timezone = (double)Math.round(tz * 2)/2; // hours, rounded to one decimal place 1006 delta = (long)Math.round(diff - timezone*60*60); // seconds 1007 1008 /*System.out.println("phto " + firstExifDate); 1009 System.out.println("gpx " + firstGPXDate); 1010 System.out.println("diff " + diff); 1011 System.out.println("difh " + diffInH); 1012 System.out.println("days " + dayOffset); 1013 System.out.println("time " + tz); 1014 System.out.println("fix " + timezone); 1015 System.out.println("offt " + delta);*/ 1016 1017 tfTimezone.getDocument().removeDocumentListener(statusBarListener); 1018 tfOffset.getDocument().removeDocumentListener(statusBarListener); 1019 1020 tfTimezone.setText(formatTimezone(timezone)); 1021 tfOffset.setText(Long.toString(delta)); 1022 tfOffset.requestFocus(); 1023 1024 tfTimezone.getDocument().addDocumentListener(statusBarListener); 1025 tfOffset.getDocument().addDocumentListener(statusBarListener); 1026 1027 statusBarListener.updateStatusBar(); 1028 yLayer.updateBufferAndRepaint(); 1029 } 1030 } 1031 1032 private ArrayList<ImageEntry> getSortedImgList() { 1033 return getSortedImgList(cbExifImg.isSelected(), cbTaggedImg.isSelected()); 1034 } 1035 932 1036 /** 933 1037 * Returns a list of images that fulfill the given criteria. 934 1038 * Default setting is to return untagged images, but may be overwritten. 935 1039 * @param boolean all -- returns all available images 936 * @param boolean noexif -- returns untagged images without EXIF-GPS coords 1040 * @param boolean noexif -- returns untagged images without EXIF-GPS coords 1041 * this parameter is irrelevant if <code>all</code> is true 1042 * @param boolean exif -- also returns images with exif-gps info 1043 * @param boolean tagged -- also returns tagged images 937 1044 * @return ArrayList<ImageEntry> matching images 938 1045 */ 939 private ArrayList<ImageEntry> getSortedImgList(boolean all, boolean noexif) {1046 private ArrayList<ImageEntry> getSortedImgList(boolean exif, boolean tagged) { 940 1047 ArrayList<ImageEntry> dateImgLst = new ArrayList<ImageEntry>(yLayer.data.size()); 941 if (all) { 942 for (ImageEntry e : yLayer.data) { 943 if (e.time != null) { 944 // Reset previous position 945 e.pos = null; 946 dateImgLst.add(e); 947 } 948 } 949 950 } else if (noexif) { 951 for (ImageEntry e : yLayer.data) { 952 if (e.time != null && e.exifCoor == null) { 953 dateImgLst.add(e); 954 } 955 } 956 957 } else { 958 for (ImageEntry e : yLayer.data) { 959 if (e.time != null && e.pos == null) { 960 dateImgLst.add(e); 961 } 962 } 963 } 964 1048 for (ImageEntry e : yLayer.data) { 1049 if (e.time == null) 1050 continue; 1051 1052 if (e.exifCoor != null) { 1053 if (!exif) 1054 continue; 1055 } 1056 1057 if (e.isTagged() && e.exifCoor == null) { 1058 if (!tagged) 1059 continue; 1060 } 1061 1062 dateImgLst.add(e); 1063 } 1064 965 1065 Collections.sort(dateImgLst, new Comparator<ImageEntry>() { 966 1066 public int compare(ImageEntry arg0, ImageEntry arg1) { … … 970 1070 971 1071 return dateImgLst; 1072 } 1073 1074 private GpxDataWrapper selectedGPX(boolean complain) { 1075 Object item = cbGpx.getSelectedItem(); 1076 1077 if (item == null || ! (item instanceof GpxDataWrapper)) { 1078 if (complain) { 1079 JOptionPane.showMessageDialog(Main.parent, tr("You should select a GPX track"), 1080 tr("No selected GPX track"), JOptionPane.ERROR_MESSAGE ); 1081 } 1082 return null; 1083 } 1084 return (GpxDataWrapper) item; 972 1085 } 973 1086 … … 1049 1162 while(i >= 0 && (dateImgLst.get(i).time.getTime()/1000) <= curDateWp 1050 1163 && (dateImgLst.get(i).time.getTime()/1000) >= (curDateWp - interval)) { 1051 if(dateImgLst.get(i). pos== null) {1052 dateImgLst.get(i). setCoor(curWp.getCoor());1053 dateImgLst.get(i). speed = speed;1054 dateImgLst.get(i). elevation = curElevation;1164 if(dateImgLst.get(i).tmp.getPos() == null) { 1165 dateImgLst.get(i).tmp.setCoor(curWp.getCoor()); 1166 dateImgLst.get(i).tmp.setSpeed(speed); 1167 dateImgLst.get(i).tmp.setElevation(curElevation); 1055 1168 ret++; 1056 1169 } … … 1065 1178 while(i >= 0 && (imgDate = dateImgLst.get(i).time.getTime()/1000) >= prevDateWp) { 1066 1179 1067 if(dateImgLst.get(i). pos== null) {1180 if(dateImgLst.get(i).tmp.getPos() == null) { 1068 1181 // The values of timeDiff are between 0 and 1, it is not seconds but a dimensionless 1069 1182 // variable 1070 1183 double timeDiff = (double)(imgDate - prevDateWp) / interval; 1071 dateImgLst.get(i). setCoor(prevWp.getCoor().interpolate(curWp.getCoor(), timeDiff));1072 dateImgLst.get(i). speed = speed;1184 dateImgLst.get(i).tmp.setCoor(prevWp.getCoor().interpolate(curWp.getCoor(), timeDiff)); 1185 dateImgLst.get(i).tmp.setSpeed(speed); 1073 1186 1074 1187 if (curElevation != null && prevElevation != null) { 1075 dateImgLst.get(i). elevation = prevElevation + (curElevation - prevElevation) * timeDiff;1188 dateImgLst.get(i).setElevation(prevElevation + (curElevation - prevElevation) * timeDiff); 1076 1189 } 1077 1190 … … 1099 1212 int endIndex= lstSize-1; 1100 1213 while (endIndex - startIndex > 1) { 1101 curIndex= ( int) Math.round((double)(endIndex + startIndex)/2);1214 curIndex= (endIndex + startIndex) / 2; 1102 1215 if (searchedDate > dateImgLst.get(curIndex).time.getTime()/1000) { 1103 1216 startIndex= curIndex; … … 1137 1250 } 1138 1251 1139 private Float parseTimezone(String timezone) { 1252 private double parseTimezone(String timezone) throws ParseException { 1253 1254 String error = tr("Error while parsing timezone.\nExpected format: {0}", "+H:MM"); 1255 1256 1140 1257 if (timezone.length() == 0) 1141 return new Float(0);1258 return 0; 1142 1259 1143 1260 char sgnTimezone = '+'; … … 1150 1267 case ' ' : 1151 1268 if (state != 2 || hTimezone.length() != 0) 1152 return null;1269 throw new ParseException(error,0); 1153 1270 break; 1154 1271 case '+' : … … 1158 1275 state = 2; 1159 1276 } else 1160 return null;1277 throw new ParseException(error,0); 1161 1278 break; 1162 1279 case ':' : … … 1165 1282 state = 3; 1166 1283 } else 1167 return null;1284 throw new ParseException(error,0); 1168 1285 break; 1169 1286 case '0' : case '1' : case '2' : case '3' : case '4' : … … 1179 1296 break; 1180 1297 default : 1181 return null;1298 throw new ParseException(error,0); 1182 1299 } 1183 1300 break; 1184 1301 default : 1185 return null;1302 throw new ParseException(error,0); 1186 1303 } 1187 1304 } … … 1196 1313 } catch (NumberFormatException nfe) { 1197 1314 // Invalid timezone 1198 return null;1315 throw new ParseException(error,0); 1199 1316 } 1200 1317 1201 1318 if (h > 12 || m > 59 ) 1202 return null;1319 throw new ParseException(error,0); 1203 1320 else 1204 return new Float((h + m / 60.0) * (sgnTimezone == '-' ? -1 : 1)); 1321 return (h + m / 60.0) * (sgnTimezone == '-' ? -1 : 1); 1322 } 1323 1324 private long parseOffset(String offset) throws ParseException { 1325 String error = tr("Error while parsing offset.\nExpected format: {0}", "number"); 1326 1327 if (offset.length() > 0) { 1328 try { 1329 if(offset.startsWith("+")) { 1330 offset = offset.substring(1); 1331 } 1332 return Long.parseLong(offset); 1333 } catch(NumberFormatException nfe) { 1334 throw new ParseException(error,0); 1335 } 1336 } else { 1337 return 0; 1338 } 1205 1339 } 1206 1340 } -
trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
r2646 r2662 79 79 private int currentPhoto = -1; 80 80 81 // These are used by the auto-guess function to store the result,82 // so when the dialig is re-opened the users modifications don't83 // get overwritten84 public boolean hasTimeoffset = false;85 public long timeoffset = 0;86 87 81 boolean useThumbs = false; 88 82 ThumbsLoader thumbsloader; 83 boolean thumbsLoaded = false; 89 84 private BufferedImage offscreenBuffer; 90 85 boolean updateOffscreenBuffer = true; 91 92 /*93 * Stores info about each image94 */95 96 static final class ImageEntry implements Comparable<ImageEntry> {97 File file;98 Date time;99 LatLon exifCoor;100 CachedLatLon pos;101 Image thumbnail;102 /** Speed in kilometer per second */103 Double speed;104 /** Elevation (altitude) in meters */105 Double elevation;106 107 public void setCoor(LatLon latlon)108 {109 pos = new CachedLatLon(latlon);110 }111 public int compareTo(ImageEntry image) {112 if (time != null && image.time != null)113 return time.compareTo(image.time);114 else if (time == null && image.time == null)115 return 0;116 else if (time == null)117 return -1;118 else119 return 1;120 }121 }122 86 123 87 /** Loads a set of images, while displaying a dialog that indicates what the plugin is currently doing. … … 277 241 boolean noGeotagFound = true; 278 242 for (ImageEntry e : layer.data) { 279 if (e. pos!= null) {243 if (e.getPos() != null) { 280 244 noGeotagFound = false; 281 245 } … … 344 308 int i = 0; 345 309 for (ImageEntry e : data) 346 if (e. pos!= null) {310 if (e.getPos() != null) { 347 311 i++; 348 312 } … … 397 361 private Dimension scaledDimension(Image thumb) { 398 362 final double d = Main.map.mapView.getDist100Pixel(); 399 final double size = 40 /*meter*/; /* size of the photo on the map */363 final double size = 10 /*meter*/; /* size of the photo on the map */ 400 364 double s = size * 100 /*px*/ / d; 401 365 … … 441 405 442 406 for (ImageEntry e : data) { 443 if (e. pos== null) {407 if (e.getPos() == null) { 444 408 continue; 445 409 } 446 Point p = mv.getPoint(e. pos);410 Point p = mv.getPoint(e.getPos()); 447 411 if (e.thumbnail != null) { 448 412 Dimension d = scaledDimension(e.thumbnail); … … 464 428 else { 465 429 for (ImageEntry e : data) { 466 if (e. pos== null) {430 if (e.getPos() == null) { 467 431 continue; 468 432 } 469 Point p = mv.getPoint(e. pos);433 Point p = mv.getPoint(e.getPos()); 470 434 icon.paintIcon(mv, g, 471 435 p.x - icon.getIconWidth() / 2, … … 477 441 ImageEntry e = data.get(currentPhoto); 478 442 479 if (e. pos!= null) {480 Point p = mv.getPoint(e. pos);443 if (e.getPos() != null) { 444 Point p = mv.getPoint(e.getPos()); 481 445 482 446 if (e.thumbnail != null) { … … 496 460 public void visitBoundingBox(BoundingXYVisitor v) { 497 461 for (ImageEntry e : data) { 498 v.visit(e. pos);462 v.visit(e.getPos()); 499 463 } 500 464 } … … 549 513 550 514 e.setCoor(new LatLon(lat, lon)); 551 e.exifCoor = e. pos;515 e.exifCoor = e.getPos(); 552 516 553 517 } catch (Exception p) { 554 e.pos = null; 518 e.exifCoor = null; 519 e.setPos(null); 555 520 } 556 521 } … … 614 579 new String[] {tr("Cancel"), tr("Delete")}) 615 580 .setButtonIcons(new String[] {"cancel.png", "dialogs/delete.png"}) 616 .setContent(new JLabel(tr("<html><h3>Delete the file {0} from thedisk?<p>The image file will be permanently lost!"581 .setContent(new JLabel(tr("<html><h3>Delete the file {0} from disk?<p>The image file will be permanently lost!" 617 582 ,toDelete.file.getName()), ImageProvider.get("dialogs/geoimage/deletefromdisk"),SwingConstants.LEFT)) 618 583 .toggleEnable("geoimage.deleteimagefromdisk") … … 674 639 for (int i = data.size() - 1; i >= 0; --i) { 675 640 ImageEntry e = data.get(i); 676 if (e. pos== null) {641 if (e.getPos() == null) { 677 642 continue; 678 643 } 679 Point p = Main.map.mapView.getPoint(e. pos);644 Point p = Main.map.mapView.getPoint(e.getPos()); 680 645 Rectangle r; 681 646 if (e.thumbnail != null) { … … 744 709 } 745 710 } 711 712 public void loadThumbs() { 713 if (useThumbs && !thumbsLoaded) { 714 thumbsLoaded = true; 715 thumbsloader = new ThumbsLoader(this); 716 Thread t = new Thread(thumbsloader); 717 t.setPriority(Thread.MIN_PRIORITY); 718 t.start(); 719 } 720 } 721 722 public void updateBufferAndRepaint() { 723 updateOffscreenBuffer = true; 724 Main.map.mapView.repaint(); 725 } 746 726 } -
trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
r2627 r2662 29 29 import org.openstreetmap.josm.gui.dialogs.ToggleDialog; 30 30 import org.openstreetmap.josm.gui.dialogs.DialogsPanel.Action; 31 import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer.ImageEntry;32 31 import org.openstreetmap.josm.tools.ImageProvider; 33 32 import org.openstreetmap.josm.tools.Shortcut; … … 187 186 } else if (COMMAND_CENTERVIEW.equals(action)) { 188 187 centerView = ((JToggleButton) e.getSource()).isSelected(); 189 if (centerView && currentEntry != null && currentEntry. pos!= null) {190 Main.map.mapView.zoomTo(currentEntry. pos);188 if (centerView && currentEntry != null && currentEntry.getPos() != null) { 189 Main.map.mapView.zoomTo(currentEntry.getPos()); 191 190 } 192 191 … … 231 230 // } TODO: pop up image dialog but don't load image again 232 231 233 if (centerView && Main.map != null && entry != null && entry. pos!= null) {234 Main.map.mapView.zoomTo(entry. pos);232 if (centerView && Main.map != null && entry != null && entry.getPos() != null) { 233 Main.map.mapView.zoomTo(entry.getPos()); 235 234 } 236 235 … … 243 242 titleBar.setTitle("Geotagged Images" + (entry.file != null ? " - " + entry.file.getName() : "")); 244 243 StringBuffer osd = new StringBuffer(entry.file != null ? entry.file.getName() : ""); 245 if (entry. elevation!= null) {246 osd.append(tr("\nAltitude: {0} m", entry. elevation.longValue()));247 } 248 if (entry. speed!= null) {249 osd.append(tr("\n{0} km/h", Math.round(entry. speed)));244 if (entry.getElevation() != null) { 245 osd.append(tr("\nAltitude: {0} m", entry.getElevation().longValue())); 246 } 247 if (entry.getSpeed() != null) { 248 osd.append(tr("\n{0} km/h", Math.round(entry.getSpeed()))); 250 249 } 251 250 imgDisplay.setOsdText(osd.toString()); -
trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java
r2617 r2662 17 17 import org.openstreetmap.josm.io.CacheFiles; 18 18 import org.openstreetmap.josm.Main; 19 import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer.ImageEntry;20 19 21 20 public class ThumbsLoader implements Runnable {
Note:
See TracChangeset
for help on using the changeset viewer.