Ignore:
Timestamp:
2009-02-28T20:34:43+01:00 (16 years ago)
Author:
stoecker
Message:

patch by xeen

Location:
applications/editors/josm/plugins/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/AgpifojDialog.java

    r13497 r13927  
    175175        }
    176176    }
     177   
     178    /**
     179     * Returns whether an image is currently displayed
     180     * @return If image is currently displayed
     181     */
     182    public boolean hasImage() {
     183        return currentEntry != null;
     184    }
    177185}
  • applications/editors/josm/plugins/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/AgpifojLayer.java

    r13497 r13927  
    5353
    5454    private int currentPhoto = -1;
     55   
     56    // These are used by the auto-guess function to store the result,
     57    // so when the dialig is re-opened the users modifications don't
     58    // get overwritten
     59    public boolean hasTimeoffset = false;
     60    public long timeoffset = 0;
    5561
    5662    /*
  • applications/editors/josm/plugins/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/AgpifojPlugin.java

    r13497 r13927  
    6060            fc.setFileFilter(JPEG_FILE_FILTER);
    6161
    62             fc.showOpenDialog(Main.parent);
     62            int result = fc.showOpenDialog(Main.parent);
    6363
    6464            File[] sel = fc.getSelectedFiles();
    65             if (sel == null || sel.length == 0) {
     65            if (sel == null || sel.length == 0 || result != JFileChooser.APPROVE_OPTION) {
    6666                return;
    6767            }
  • applications/editors/josm/plugins/agpifoj/src/org/openstreetmap/josm/plugins/agpifoj/CorrelateGpxWithImages.java

    r13775 r13927  
    1010import java.awt.Cursor;
    1111import java.awt.Dimension;
     12import java.awt.event.ActionEvent;
     13import java.awt.event.ActionListener;
    1214import java.awt.FlowLayout;
    1315import java.awt.GridBagConstraints;
    1416import java.awt.GridBagLayout;
    15 import java.awt.event.ActionEvent;
    16 import java.awt.event.ActionListener;
    1717import java.io.File;
    1818import java.io.FileInputStream;
     19import java.io.InputStream;
    1920import java.io.IOException;
    20 import java.io.InputStream;
    2121import java.text.ParseException;
    2222import java.text.SimpleDateFormat;
     
    2626import java.util.Comparator;
    2727import java.util.Date;
     28import java.util.Hashtable;
    2829import java.util.Iterator;
    2930import java.util.List;
     
    3435import javax.swing.AbstractListModel;
    3536import javax.swing.ButtonGroup;
     37import javax.swing.event.ChangeEvent;
     38import javax.swing.event.ChangeListener;
     39import javax.swing.event.ListSelectionEvent;
     40import javax.swing.event.ListSelectionListener;
     41import javax.swing.filechooser.FileFilter;
    3642import javax.swing.JButton;
    3743import javax.swing.JComboBox;
     
    4349import javax.swing.JRadioButton;
    4450import javax.swing.JScrollPane;
     51import javax.swing.JSlider;
    4552import javax.swing.JTextField;
    4653import javax.swing.ListSelectionModel;
    47 import javax.swing.event.ListSelectionEvent;
    48 import javax.swing.event.ListSelectionListener;
    49 import javax.swing.filechooser.FileFilter;
    50 
    51 import org.openstreetmap.josm.Main;
     54
    5255import org.openstreetmap.josm.data.coor.EastNorth;
    5356import org.openstreetmap.josm.data.gpx.GpxData;
     
    5558import org.openstreetmap.josm.data.gpx.WayPoint;
    5659import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
     60import org.openstreetmap.josm.gui.ExtendedDialog;
    5761import org.openstreetmap.josm.gui.layer.GpxLayer;
    5862import org.openstreetmap.josm.gui.layer.Layer;
    5963import org.openstreetmap.josm.io.GpxReader;
     64import org.openstreetmap.josm.Main;
    6065import org.openstreetmap.josm.plugins.agpifoj.AgpifojLayer.ImageEntry;
    6166import org.openstreetmap.josm.tools.ExifReader;
     67import org.openstreetmap.josm.tools.GBC;
    6268import org.openstreetmap.josm.tools.ImageProvider;
    6369import org.openstreetmap.josm.tools.PrimaryDateParser;
    6470import org.xml.sax.SAXException;
     71
    6572
    6673/** This class displays the window to select the GPX file and the offset (timezone + delta).
     
    547554        GpxDataWrapper selectedGpx = null;
    548555        while (! isOk) {
    549             int answer = JOptionPane.showConfirmDialog(Main.parent, panel, tr("Correlate images with GPX track"), JOptionPane.OK_CANCEL_OPTION);
    550             if (answer == JOptionPane.CANCEL_OPTION) {
     556            int answer = new ExtendedDialog(Main.parent,
     557                tr("Correlate images with GPX track"),
     558                panel,
     559                new String[] { tr("Correlate"), tr("Auto-Guess"), tr("Cancel") },
     560                new String[] { "ok.png", "dialogs/gpx2imgManual.png", "cancel.png" }).getValue();
     561           
     562            if(answer != 1 && answer != 2)
    551563                return;
    552             }
     564               
    553565            // Check the selected values
    554566            Object item = cbGpx.getSelectedItem();
     
    560572            }
    561573            selectedGpx = ((GpxDataWrapper) item);
     574           
     575            if (answer == 2) {
     576                autoGuess(selectedGpx.data);
     577                return;
     578            }
    562579
    563580            Float timezoneValue = parseTimezone(tfTimezone.getText().trim());
     
    592609
    593610        // Construct a list of images that have a date, and sort them on the date.
    594         ArrayList<ImageEntry> dateImgLst = new ArrayList<ImageEntry>(yLayer.data.size());
    595         if (rbAllImg.isSelected()) {
    596             for (ImageEntry e : yLayer.data) {
    597                 if (e.time != null) {
    598                     dateImgLst.add(e);
    599                 }
    600             }
    601 
    602         } else if (rbNoExifImg.isSelected()) {
    603             for (ImageEntry e : yLayer.data) {
    604                 if (e.time != null && e.exifCoor == null) {
    605                     dateImgLst.add(e);
    606                 }
    607             }
    608 
    609         } else { // rbUntaggedImg.isSelected()
    610             for (ImageEntry e : yLayer.data) {
    611                 if (e.time != null && e.coor == null) {
    612                     dateImgLst.add(e);
    613                 }
    614             }
    615         }
    616 
    617         int matched = matchGpxTrack(dateImgLst, selectedGpx.data, (long) (gpstimezone * 3600000) + delta * 1000);
     611        ArrayList<ImageEntry> dateImgLst = getSortedImgList(rbAllImg.isSelected(), rbNoExifImg.isSelected());
     612
     613        int matched = matchGpxTrack(dateImgLst, selectedGpx.data, (long) (gpstimezone * 3600) + delta);
    618614
    619615        // Search whether an other layer has yet defined some bounding box.
     
    641637        Main.main.map.repaint();
    642638
    643         JOptionPane.showMessageDialog(Main.parent, tr("Found {0} matchs of {1} in GPX track {2}", matched, dateImgLst.size(), selectedGpx.name),
     639        JOptionPane.showMessageDialog(Main.parent, tr("Found {0} matches of {1} in GPX track {2}", matched, dateImgLst.size(), selectedGpx.name),
    644640                tr("GPX Track loaded"),
    645641                ((dateImgLst.size() > 0 && matched == 0) ? JOptionPane.WARNING_MESSAGE
     
    647643
    648644    }
    649 
    650     private int matchGpxTrack(ArrayList<ImageEntry> dateImgLst, GpxData selectedGpx, long offset) {
    651         int ret = 0;
    652 
     645   
     646    // These variables all belong to "auto guess" but need to be accessible
     647    // from the slider change listener
     648    private int dayOffset;
     649    private JLabel lblMatches;
     650    private JLabel lblOffset;
     651    private JLabel lblTimezone;
     652    private JLabel lblMinutes;
     653    private JLabel lblSeconds;
     654    private JSlider sldTimezone;
     655    private JSlider sldMinutes;
     656    private JSlider sldSeconds;
     657    private GpxData autoGpx;
     658    private ArrayList<ImageEntry> autoImgs;
     659    private long firstGPXDate = -1;
     660    private long firstExifDate = -1;
     661   
     662    /**
     663     * Tries to automatically match opened photos to a given GPX track. Changes are applied
     664     * immediately. Presents dialog with sliders for manual adjust.
     665     * @param GpxData The GPX track to match against
     666     */
     667    private void autoGuess(GpxData gpx) {
     668        autoGpx = gpx;
     669        autoImgs = getSortedImgList(true, false);
     670        PrimaryDateParser dateParser = new PrimaryDateParser();
     671       
     672        // no images found, exit
     673        if(autoImgs.size() <= 0) {
     674            JOptionPane.showMessageDialog(Main.parent,
     675                tr("The selected photos don't contain time information."),
     676                tr("Photos don't contain time information"), JOptionPane.WARNING_MESSAGE);
     677            return;
     678        }
     679       
     680        // Free the user's vision
     681        Main.pleaseWaitDlg.setVisible(false);
     682        AgpifojDialog dialog = AgpifojDialog.getInstance();
     683        dialog.action.button.setSelected(true);
     684        dialog.action.actionPerformed(null);
     685        // Will show first photo if none is selected yet
     686        if(!dialog.hasImage())
     687            yLayer.showNextPhoto();
     688        // FIXME: If the dialog is minimized it will not be maximized. ToggleDialog is
     689        // in need of a complete re-write to allow this in a reasonable way.
     690
     691        // Init variables
     692        firstExifDate = autoImgs.get(0).time.getTime()/1000;
     693       
     694       
     695        // Finds first GPX point
     696        outer: for (GpxTrack trk : gpx.tracks) {
     697            for (Collection<WayPoint> segment : trk.trackSegs) {
     698                for (WayPoint curWp : segment) {
     699                    String curDateWpStr = (String) curWp.attr.get("time");
     700                    if (curDateWpStr == null) continue;
     701                   
     702                    try {
     703                        firstGPXDate = dateParser.parse(curDateWpStr).getTime()/1000;
     704                        break outer;
     705                    } catch(Exception e) {}
     706                }
     707            }
     708        }
     709       
     710        // No GPX timestamps found, exit
     711        if(firstGPXDate < 0) {
     712            JOptionPane.showMessageDialog(Main.parent,
     713                tr("The selected GPX track doesn't contain timestamps. Please select another one."),
     714                tr("GPX Track has no time information"), JOptionPane.WARNING_MESSAGE);
     715            return;
     716        }
     717       
     718        // seconds
     719        long diff = (yLayer.hasTimeoffset)
     720            ? yLayer.timeoffset
     721            : firstExifDate - firstGPXDate;         
     722        yLayer.timeoffset = diff;
     723        yLayer.hasTimeoffset = true;
     724       
     725        double diffInH = (double)diff/(60*60);    // hours
     726
     727        // Find day difference
     728        dayOffset = (int)Math.round(diffInH / 24); // days
     729        double timezone = diff - dayOffset*24*60*60;  // seconds
     730       
     731        // In hours, rounded to two decimal places
     732        timezone = (double)Math.round(timezone*100/(60*60)) / 100;
     733
     734        // Due to imprecise clocks we might get a "+3:28" timezone, which should obviously be 3:30 with
     735        // -2 minutes offset. This determines the real timezone and finds offset.
     736        double fixTimezone = (double)Math.round(timezone * 2)/2; // hours, rounded to one decimal place
     737        int offset = (int)Math.round(diff - fixTimezone*60*60) - dayOffset*24*60*60; // seconds
     738       
     739        /*System.out.println("phto " + firstExifDate);
     740        System.out.println("gpx  " + firstGPXDate);
     741        System.out.println("diff " + diff);
     742        System.out.println("difh " + diffInH);
     743        System.out.println("days " + dayOffset);
     744        System.out.println("time " + timezone);
     745        System.out.println("fix  " + fixTimezone);
     746        System.out.println("offt " + offset);*/
     747       
     748        // This is called whenever one of the sliders is moved.
     749        // It updates the labels and also calls the "match photos" code
     750        class sliderListener implements ChangeListener {
     751            public void stateChanged(ChangeEvent e) {
     752                // parse slider position into real timezone
     753                double tz = Math.abs(sldTimezone.getValue());
     754                String zone = tz % 2 == 0
     755                    ? (int)Math.floor(tz/2) + ":00"
     756                    : (int)Math.floor(tz/2) + ":30";
     757                if(sldTimezone.getValue() < 0) zone = "-" + zone;
     758               
     759                lblTimezone.setText(tr("Timezone: {0}", zone));
     760                lblMinutes.setText(tr("Minutes: {0}", sldMinutes.getValue()));
     761                lblSeconds.setText(tr("Seconds: {0}", sldSeconds.getValue()));
     762               
     763                float gpstimezone = parseTimezone(zone).floatValue();
     764               
     765                // Reset previous position
     766                for(ImageEntry x : autoImgs) {
     767                    x.coor = null;
     768                    x.pos = null;
     769                }
     770               
     771                long timediff = (long) (gpstimezone * 3600)
     772                        + dayOffset*24*60*60
     773                        + sldMinutes.getValue()*60
     774                        + sldSeconds.getValue();
     775               
     776                int matched = matchGpxTrack(autoImgs, autoGpx, timediff);
     777
     778                lblMatches.setText(
     779                    tr("Matched {0} of {1} photos to GPX track.", matched, autoImgs.size())
     780                    + ((Math.abs(dayOffset) == 0)
     781                        ? ""
     782                        : " " + tr("(Time difference of {0} days)", Math.abs(dayOffset))
     783                      )
     784                );
     785               
     786                int offset = (int)(firstGPXDate+timediff-firstExifDate);
     787                int o = Math.abs(offset);
     788                lblOffset.setText(
     789                    tr("Offset between track and photos: {0}m {1}s",
     790                          (offset < 0 ? "-" : "") + Long.toString(Math.round(o/60)),
     791                          Long.toString(Math.round(o%60))
     792                    )
     793                );
     794
     795                yLayer.timeoffset = timediff;
     796                Main.main.map.repaint();
     797            }
     798        }
     799       
     800        // Info Labels
     801        lblMatches = new JLabel();
     802        lblOffset = new JLabel();
     803
     804        // Timezone Slider
     805        // The slider allows to switch timezon from -12:00 to 12:00 in 30 minutes
     806        // steps. Therefore the range is -24 to 24.
     807        lblTimezone = new JLabel();
     808        sldTimezone = new JSlider(-24, 24, 0);
     809        sldTimezone.setPaintLabels(true);       
     810        Hashtable labelTable = new Hashtable();
     811        labelTable.put(-24, new JLabel("-12:00"));
     812        labelTable.put(-12, new JLabel( "-6:00"));
     813        labelTable.put(  0, new JLabel(  "0:00"));
     814        labelTable.put( 12, new JLabel(  "6:00"));
     815        labelTable.put( 24, new JLabel( "12:00"));
     816        sldTimezone.setLabelTable(labelTable);
     817       
     818        // Minutes Slider
     819        lblMinutes = new JLabel();
     820        sldMinutes = new JSlider(-15, 15, 0);       
     821        sldMinutes.setPaintLabels(true);   
     822        sldMinutes.setMajorTickSpacing(5); 
     823       
     824        // Seconds slider
     825        lblSeconds = new JLabel();
     826        sldSeconds = new JSlider(-60, 60, 0);       
     827        sldSeconds.setPaintLabels(true);   
     828        sldSeconds.setMajorTickSpacing(30); 
     829       
     830        // Put everything together
     831        JPanel p = new JPanel(new GridBagLayout());
     832        p.setPreferredSize(new Dimension(400, 230));
     833        p.add(lblMatches, GBC.eol().fill());
     834        p.add(lblOffset, GBC.eol().fill().insets(0, 0, 0, 10));
     835        p.add(lblTimezone, GBC.eol().fill());
     836        p.add(sldTimezone, GBC.eol().fill().insets(0, 0, 0, 10));
     837        p.add(lblMinutes, GBC.eol().fill());
     838        p.add(sldMinutes, GBC.eol().fill().insets(0, 0, 0, 10));
     839        p.add(lblSeconds, GBC.eol().fill());
     840        p.add(sldSeconds, GBC.eol().fill());
     841       
     842        // If there's an error in the calculation the found values
     843        // will be off range for the sliders. Catch this error
     844        // and inform the user about it.
     845        try {
     846            sldTimezone.setValue((int)(fixTimezone*2));
     847            sldMinutes.setValue(offset/60);
     848            sldSeconds.setValue(offset%60);
     849        } catch(Exception e) {
     850            JOptionPane.showMessageDialog(Main.parent,
     851                tr("An error occured while trying to match the photos to the GPX track."
     852                    +" You can adjust the sliders to manually match the photos."),
     853                tr("Matching photos to track failed"),
     854                JOptionPane.WARNING_MESSAGE);
     855        }
     856       
     857        // Call the sliderListener once manually so labels get adjusted
     858        new sliderListener().stateChanged(null);
     859        // Listeners added here, otherwise it tries to match three times
     860        // (when setting the default values)
     861        sldTimezone.addChangeListener(new sliderListener());
     862        sldMinutes.addChangeListener(new sliderListener());
     863        sldSeconds.addChangeListener(new sliderListener());
     864       
     865        // There is no way to cancel this dialog, all changes get applied
     866        // immediately. Therefore "Close" is marked with an "OK" icon.
     867        // Settings are only saved temporarily to the layer.
     868        int answer = new ExtendedDialog(Main.parent,
     869            tr("Adjust timezone and offset"),
     870            p,
     871            new String[] { tr("Close"),  tr("Default Values") },
     872            new String[] { "ok.png", "dialogs/refresh.png"}
     873        ).getValue();
     874       
     875        // User wants default values; discard old result and re-open dialog
     876        if(answer == 2) {
     877            yLayer.hasTimeoffset = false;
     878            autoGuess(gpx);
     879        }
     880    }
     881   
     882    /**
     883     * Returns a list of images that fulfill the given criteria.
     884     * Default setting is to return untagged images, but may be overwritten.
     885     * @param boolean all -- returns all available images
     886     * @param boolean noexif -- returns untagged images without EXIF-GPS coords
     887     * @return ArrayList<ImageEntry> matching images
     888     */
     889    private ArrayList<ImageEntry> getSortedImgList(boolean all, boolean noexif) {
     890        ArrayList<ImageEntry> dateImgLst = new ArrayList<ImageEntry>(yLayer.data.size());
     891        if (all) {
     892            for (ImageEntry e : yLayer.data) {
     893                if (e.time != null) {
     894                    // Reset previous position
     895                    e.coor = null;
     896                    e.pos = null;
     897                    dateImgLst.add(e);
     898                }
     899            }
     900
     901        } else if (noexif) {
     902            for (ImageEntry e : yLayer.data) {
     903                if (e.time != null && e.exifCoor == null) {
     904                    dateImgLst.add(e);
     905                }
     906            }
     907
     908        } else {
     909            for (ImageEntry e : yLayer.data) {
     910                if (e.time != null && e.coor == null) {
     911                    dateImgLst.add(e);
     912                }
     913            }
     914        }
     915       
    653916        Collections.sort(dateImgLst, new Comparator<ImageEntry>() {
    654917            public int compare(ImageEntry arg0, ImageEntry arg1) {
     
    656919            }
    657920        });
     921       
     922        return dateImgLst;
     923    }
     924
     925    private int matchGpxTrack(ArrayList<ImageEntry> dateImgLst, GpxData selectedGpx, long offset) {
     926        int ret = 0;
    658927
    659928        PrimaryDateParser dateParser = new PrimaryDateParser();
     
    671940
    672941                        try {
    673                             long curDateWp = dateParser.parse(curDateWpStr).getTime() + offset;
     942                            long curDateWp = dateParser.parse(curDateWpStr).getTime()/1000 + offset;
    674943                            ret += matchPoints(dateImgLst, prevWp, prevDateWp, curWp, curDateWp);
    675944
     
    694963
    695964    private int matchPoints(ArrayList<ImageEntry> dateImgLst, WayPoint prevWp, long prevDateWp, WayPoint curWp, long curDateWp) {
    696         int interval = prevDateWp > 0 ? ((int)Math.abs(curDateWp - prevDateWp))/2 : 500;
     965        double interval = prevDateWp > 0 ? ((int)Math.abs(curDateWp - prevDateWp)) : 1;
    697966        int ret = 0;
    698967        int i = getLastIndexOfListBefore(dateImgLst, curDateWp, interval);
    699         if (i >= 0 && i < dateImgLst.size() && dateImgLst.get(i).time.getTime()+interval > prevDateWp) {
     968        if (i >= 0 && i < dateImgLst.size() && dateImgLst.get(i).time.getTime()/1000+interval > prevDateWp) {
    700969            Double speed = null;
    701970            Double prevElevation = null;
     
    712981            } catch (Exception e) {}
    713982
    714             while(i >= 0 && inRadius(dateImgLst.get(i).time.getTime(), curDateWp, interval)) {
     983            while(i >= 0 && inRadius(dateImgLst.get(i).time.getTime()/1000, curDateWp, interval)) {
    715984                if(dateImgLst.get(i).coor == null) {
    716985                    dateImgLst.get(i).pos = curWp.eastNorth;
     
    726995                long imgDate;
    727996                while(i >= 0
    728                         && (imgDate = dateImgLst.get(i).time.getTime()) > prevDateWp) {
     997                        && (imgDate = dateImgLst.get(i).time.getTime()/1000) > prevDateWp) {
    729998                    if(dateImgLst.get(i).coor == null) {
    730999                        dateImgLst.get(i).pos = new EastNorth(
     
    7451014    }
    7461015
    747     private int getLastIndexOfListBefore(ArrayList<ImageEntry> dateImgLst, long searchedDate, int interval) {
     1016    private int getLastIndexOfListBefore(ArrayList<ImageEntry> dateImgLst, long searchedDate, double interval) {
    7481017        int lstSize = dateImgLst.size();
    749         if (lstSize == 0 || searchedDate < dateImgLst.get(0).time.getTime()) {
     1018        if (lstSize == 0 || searchedDate < dateImgLst.get(0).time.getTime()/1000) {
    7501019            return -1;
    751         } else if (searchedDate-interval > dateImgLst.get(lstSize - 1).time.getTime()) {
     1020        } else if (searchedDate-interval > dateImgLst.get(lstSize - 1).time.getTime()/1000) {
    7521021            return lstSize;
    753         } else if (inRadius(searchedDate, dateImgLst.get(lstSize - 1).time.getTime(), interval)) {
     1022        } else if (inRadius(searchedDate, dateImgLst.get(lstSize - 1).time.getTime()/1000, interval)) {
    7541023            return lstSize - 1;
    755         } else if (inRadius(searchedDate , dateImgLst.get(0).time.getTime(), interval)) {
     1024        } else if (inRadius(searchedDate , dateImgLst.get(0).time.getTime()/1000, interval)) {
    7561025            int curIndex = 0;
    7571026            while (curIndex + 1 < lstSize
    758                     && inRadius(dateImgLst.get(curIndex + 1).time.getTime(), searchedDate, interval)) {
     1027                    && inRadius(dateImgLst.get(curIndex + 1).time.getTime()/1000, searchedDate, interval)) {
    7591028                curIndex++;
    7601029            }
     
    7671036        while (endIndex - startIndex > 1) {
    7681037            curIndex = (endIndex + startIndex) / 2;
    769             long curDate = dateImgLst.get(curIndex).time.getTime();
     1038            long curDate = dateImgLst.get(curIndex).time.getTime()/1000;
    7701039            if (curDate-interval < searchedDate) {
    7711040                startIndex = curIndex;
     
    7751044                // Check that there is no image _after_ that one that have exactly the same date.
    7761045                while (curIndex + 1 < lstSize
    777                         && inRadius(dateImgLst.get(curIndex + 1).time.getTime(), searchedDate, interval)) {
     1046                        && inRadius(dateImgLst.get(curIndex + 1).time.getTime()/1000, searchedDate, interval)) {
    7781047                    curIndex++;
    7791048                }
     
    8921161    }
    8931162   
    894     private boolean inRadius(long time1, long time2, int interval) {
     1163    private boolean inRadius(long time1, long time2, double interval) {
    8951164        return Math.abs(time1 - time2) < interval;
    8961165    }
Note: See TracChangeset for help on using the changeset viewer.