Ignore:
Timestamp:
2018-08-30T23:15:06+02:00 (6 years ago)
Author:
Don-vip
Message:

fix #16681 - Various enhancements for GPX correlation (patch by Bjoeni, modified)

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java

    r14153 r14205  
    66
    77import java.awt.BorderLayout;
     8import java.awt.Component;
    89import java.awt.Cursor;
    910import java.awt.Dimension;
     
    3536import java.util.Hashtable;
    3637import java.util.List;
     38import java.util.Objects;
    3739import java.util.Optional;
    3840import java.util.TimeZone;
     
    4446import javax.swing.JButton;
    4547import javax.swing.JCheckBox;
     48import javax.swing.JComponent;
    4649import javax.swing.JFileChooser;
    4750import javax.swing.JLabel;
     
    5255import javax.swing.JSeparator;
    5356import javax.swing.JSlider;
     57import javax.swing.JSpinner;
    5458import javax.swing.ListSelectionModel;
    5559import javax.swing.MutableComboBoxModel;
     60import javax.swing.SpinnerNumberModel;
    5661import javax.swing.SwingConstants;
     62import javax.swing.border.Border;
    5763import javax.swing.event.ChangeEvent;
    5864import javax.swing.event.ChangeListener;
     
    6268import org.openstreetmap.josm.actions.DiskAccessAction;
    6369import org.openstreetmap.josm.actions.ExtensionFileFilter;
    64 import org.openstreetmap.josm.data.gpx.GpxConstants;
    6570import org.openstreetmap.josm.data.gpx.GpxData;
     71import org.openstreetmap.josm.data.gpx.GpxImageCorrelation;
     72import org.openstreetmap.josm.data.gpx.GpxTimeOffset;
     73import org.openstreetmap.josm.data.gpx.GpxTimezone;
    6674import org.openstreetmap.josm.data.gpx.GpxTrack;
    6775import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
     
    7583import org.openstreetmap.josm.gui.layer.GpxLayer;
    7684import org.openstreetmap.josm.gui.layer.Layer;
     85import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
     86import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
     87import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
     88import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
    7789import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
    7890import org.openstreetmap.josm.gui.widgets.FileChooserManager;
     
    8496import org.openstreetmap.josm.io.nmea.NmeaReader;
    8597import org.openstreetmap.josm.spi.preferences.Config;
     98import org.openstreetmap.josm.spi.preferences.IPreferences;
    8699import org.openstreetmap.josm.tools.GBC;
    87100import org.openstreetmap.josm.tools.ImageProvider;
     
    101114
    102115    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;
    105119
    106120    /**
     
    112126        new ImageProvider("dialogs/geoimage/gpx2img").getResource().attachImageIcon(this, true);
    113127        this.yLayer = layer;
     128        MainApplication.getLayerManager().addLayerChangeListener(new GpxLayerAddedListener());
    114129    }
    115130
     
    130145            // Parse values again, to display an error if the format is not recognized
    131146            try {
    132                 timezone = Timezone.parseTimezone(tfTimezone.getText().trim());
     147                timezone = GpxTimezone.parseTimezone(tfTimezone.getText().trim());
    133148            } catch (ParseException e) {
    134149                JOptionPane.showMessageDialog(MainApplication.getMainFrame(), e.getMessage(),
     
    138153
    139154            try {
    140                 delta = Offset.parseOffset(tfOffset.getText().trim());
     155                delta = GpxTimeOffset.parseOffset(tfOffset.getText().trim());
    141156            } catch (ParseException e) {
    142157                JOptionPane.showMessageDialog(MainApplication.getMainFrame(), e.getMessage(),
     
    323338    }
    324339
     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
    325538    /**
    326539     * This action listener is called when the user has a photo of the time of his GPS receiver. It
     
    399612
    400613                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() +
    402615                        ')';
    403616                vtTimezones.add(tzDesc);
     
    417630
    418631            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() +
    420633                    ')');
    421634
     
    515728
    516729                Config.getPref().put("geoimage.timezoneid", tzId);
    517                 tfOffset.setText(Offset.milliseconds(delta).formatOffset());
     730                tfOffset.setText(GpxTimeOffset.milliseconds(delta).formatOffset());
    518731                tfTimezone.setText(tzValue);
    519732
     
    524737            yLayer.updateBufferAndRepaint();
    525738        }
     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) {}
    526765    }
    527766
     
    578817
    579818        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"));
    581820        } catch (ParseException e) {
    582             timezone = Timezone.ZERO;
     821            timezone = GpxTimezone.ZERO;
     822            Logging.trace(e);
    583823        }
    584824
     
    587827
    588828        try {
    589             delta = Offset.parseOffset(Config.getPref().get("geoimage.delta", "0"));
     829            delta = GpxTimeOffset.parseOffset(Config.getPref().get("geoimage.delta", "0"));
    590830        } catch (ParseException e) {
    591             delta = Offset.ZERO;
     831            delta = GpxTimeOffset.ZERO;
     832            Logging.trace(e);
    592833        }
    593834
     
    606847        JButton buttonAdjust = new JButton(tr("Manual adjust"));
    607848        buttonAdjust.addActionListener(new AdjustActionListener());
     849
     850        JButton buttonAdvanced = new JButton(tr("Advanced settings..."));
     851        buttonAdvanced.addActionListener(new AdvancedSettingsActionListener());
    608852
    609853        JLabel labelPosition = new JLabel(tr("Override position for: "));
     
    668912
    669913        gbc = GBC.std().fill(GBC.BOTH).insets(5, 5, 5, 5);
    670         gbc.gridx = 2;
     914        gbc.gridx = 1;
    671915        gbc.gridy = y++;
    672916        gbc.weightx = 0.5;
     917        panelTf.add(buttonAdvanced, gbc);
     918
     919        gbc.gridx = 2;
    673920        panelTf.add(buttonAutoGuess, gbc);
    674921
     
    716963
    717964        statusBarUpdater.updateStatusBar();
     965        yLayer.updateBufferAndRepaint();
    718966
    719967        outerPanel = new JPanel(new BorderLayout());
     
    7821030        private String statusText() {
    7831031            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());
    7861034            } catch (ParseException e) {
    7871035                return e.getMessage();
     
    8011049            for (ImageEntry ie : dateImgLst) {
    8021050                ie.createTmp();
    803                 ie.tmp.setPos(null);
     1051                ie.getTmp().setPos(null);
    8041052            }
    8051053
     
    8081056                return tr("No gpx selected");
    8091057
    810             final long offsetMs = ((long) (timezone.getHours() * TimeUnit.HOURS.toMillis(1))) + delta.getMilliseconds(); // in milliseconds
    811             lastNumMatched = matchGpxTrack(dateImgLst, selGpx.data, offsetMs);
     1058            final long offsetMs = ((long) (timezone.getHours() * TimeUnit.HOURS.toMillis(-1))) + delta.getMilliseconds(); // in milliseconds
     1059            lastNumMatched = GpxImageCorrelation.matchGpxTrack(dateImgLst, selGpx.data, offsetMs, forceTags);
    8121060
    8131061            return trn("<html>Matched <b>{0}</b> of <b>{1}</b> photo to GPX track.</html>",
     
    8381086        public void actionPerformed(ActionEvent arg0) {
    8391087
    840             final Offset offset = Offset.milliseconds(
     1088            final GpxTimeOffset offset = GpxTimeOffset.milliseconds(
    8411089                    delta.getMilliseconds() + Math.round(timezone.getHours() * TimeUnit.HOURS.toMillis(1)));
    8421090            final int dayOffset = offset.getDayOffset();
    843             final Pair<Timezone, Offset> timezoneOffsetPair = offset.withoutDayOffset().splitOutTimezone();
     1091            final Pair<GpxTimezone, GpxTimeOffset> timezoneOffsetPair = offset.withoutDayOffset().splitOutTimezone();
    8441092
    8451093            // Info Labels
     
    8541102            // CHECKSTYLE.OFF: ParenPad
    8551103            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()));
    8571105            }
    8581106            // CHECKSTYLE.ON: ParenPad
     
    8721120            // CHECKSTYLE.OFF: ParenPad
    8731121            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()));
    8751123            }
    8761124            // CHECKSTYLE.ON: ParenPad
     
    8831131                @Override
    8841132                public void stateChanged(ChangeEvent e) {
    885                     timezone = new Timezone(sldTimezone.getValue() / 2.);
     1133                    timezone = new GpxTimezone(sldTimezone.getValue() / 2.);
    8861134
    8871135                    lblTimezone.setText(tr("Timezone: {0}", timezone.formatTimezone()));
    8881136                    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()
    8921140                            + TimeUnit.MINUTES.toMillis(sldMinutes.getValue())
    8931141                            + TimeUnit.DAYS.toMillis(dayOffset));
     
    9681216     * @throws NoGpxTimestamps when the gpx track does not contain a timestamp
    9691217     */
    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 {
    9711219
    9721220        // Init variables
     
    9911239        }
    9921240
    993         return Offset.milliseconds(firstExifDate - firstGPXDate).splitOutTimezone();
     1241        return GpxTimeOffset.milliseconds(firstExifDate - firstGPXDate).splitOutTimezone();
    9941242    }
    9951243
     
    10061254
    10071255            try {
    1008                 final Pair<Timezone, Offset> r = autoGuess(imgs, gpx);
     1256                final Pair<GpxTimezone, GpxTimeOffset> r = autoGuess(imgs, gpx);
    10091257                timezone = r.a;
    10101258                delta = r.b;
     
    10881336    }
    10891337
    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 match
    1094      * @param selectedGpx selected GPX data
    1095      * @param offset offset
    1096      * @return number of matched points
    1097      */
    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 take
    1140         // 5 sec before the first track point can be assumed to be take at the starting position
    1141         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 time
    1145         int i = getLastIndexOfListBefore(images, curWpTime);
    1146 
    1147         // no photos match
    1148         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/s
    1157             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 seconds
    1166         // before the first point will be geotagged with the starting point
    1167         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 and
    1188         // previous track point assuming a constant speed in between
    1189         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 variable
    1198                 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 period
    1218         if (lstSize == 0 || searchedTime < images.get(0).getExifTime().getTime())
    1219             return -1;
    1220 
    1221         // The search period is later than the last photo
    1222         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 beginning
    1226         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 follows
    1241         while ((endIndex < (lstSize-1)) && (images.get(endIndex).getExifTime().getTime()
    1242                 == images.get(endIndex + 1).getExifTime().getTime())) {
    1243             endIndex++;
    1244         }
    1245         return endIndex;
    1246     }
    12471338}
Note: See TracChangeset for help on using the changeset viewer.