Index: src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java	(revision 14180)
+++ src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java	(working copy)
@@ -5,6 +5,7 @@
 import static org.openstreetmap.josm.tools.I18n.trn;
 
 import java.awt.BorderLayout;
+import java.awt.Component;
 import java.awt.Cursor;
 import java.awt.Dimension;
 import java.awt.FlowLayout;
@@ -43,6 +44,7 @@
 import javax.swing.BorderFactory;
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
+import javax.swing.JComponent;
 import javax.swing.JFileChooser;
 import javax.swing.JLabel;
 import javax.swing.JList;
@@ -51,9 +53,12 @@
 import javax.swing.JScrollPane;
 import javax.swing.JSeparator;
 import javax.swing.JSlider;
+import javax.swing.JSpinner;
 import javax.swing.ListSelectionModel;
 import javax.swing.MutableComboBoxModel;
+import javax.swing.SpinnerNumberModel;
 import javax.swing.SwingConstants;
+import javax.swing.border.Border;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
 import javax.swing.event.DocumentEvent;
@@ -74,6 +79,10 @@
 import org.openstreetmap.josm.gui.io.importexport.NMEAImporter;
 import org.openstreetmap.josm.gui.layer.GpxLayer;
 import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
+import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
+import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
+import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
 import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
 import org.openstreetmap.josm.gui.widgets.FileChooserManager;
 import org.openstreetmap.josm.gui.widgets.JosmComboBox;
@@ -83,6 +92,7 @@
 import org.openstreetmap.josm.io.IGpxReader;
 import org.openstreetmap.josm.io.nmea.NmeaReader;
 import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.spi.preferences.IPreferences;
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.JosmRuntimeException;
@@ -102,6 +112,7 @@
     private final transient GeoImageLayer yLayer;
     private transient Timezone timezone;
     private transient Offset delta;
+    private static boolean forceTags = false;
 
     /**
      * Constructs a new {@code CorrelateGpxWithImages} action.
@@ -111,6 +122,7 @@
         super(tr("Correlate to GPX"));
         new ImageProvider("dialogs/geoimage/gpx2img").getResource().attachImageIcon(this, true);
         this.yLayer = layer;
+        MainApplication.getLayerManager().addLayerChangeListener(new GpxLayerAddedListener());
     }
 
     private final class SyncDialogWindowListener extends WindowAdapter {
@@ -322,6 +334,201 @@
         }
     }
 
+    private class AdvancedSettingsActionListener implements ActionListener {
+
+        private class CheckBoxActionListener implements ActionListener {
+            private JComponent[] comps;
+
+            CheckBoxActionListener(JComponent... c) {
+                comps = c;
+            }
+
+            @Override
+            public void actionPerformed(ActionEvent arg0) {
+                setEnabled((JCheckBox) arg0.getSource());
+            }
+
+            public void setEnabled(JCheckBox cb) {
+                for (JComponent comp : comps) {
+                    if (comp instanceof JSpinner) {
+                        comp.setEnabled(cb.isSelected());
+                    } else if (comp instanceof JPanel) {
+                        boolean en = cb.isSelected();
+                        for (Component c : comp.getComponents()) {
+                            if (c instanceof JSpinner) {
+                                c.setEnabled(en);
+                            } else {
+                                c.setEnabled(cb.isSelected());
+                                if (en && c instanceof JCheckBox) {
+                                    en = ((JCheckBox) c).isSelected();
+                                }
+                            }
+                        }
+                    }
+                }
+
+            }
+        }
+
+        private void addCheckBoxActionListener(JCheckBox cb, JComponent... c) {
+            CheckBoxActionListener listener = new CheckBoxActionListener(c);
+            cb.addActionListener(listener);
+            listener.setEnabled(cb);
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent arg0) {
+
+            IPreferences s = Config.getPref();
+            JPanel p = new JPanel(new GridBagLayout());
+
+            Border border1 = BorderFactory.createEmptyBorder(0, 20, 0, 0);
+            Border border2 = BorderFactory.createEmptyBorder(10, 0, 5, 0);
+            Border border = BorderFactory.createEmptyBorder(0, 40, 0, 0);
+            FlowLayout layout = new FlowLayout();
+
+            JLabel l = new JLabel(tr("Segment settings"));
+            l.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
+            p.add(l, GBC.eol());
+            JCheckBox cInterpolSeg = new JCheckBox(tr("Interpolate between segments"), s.getBoolean("geoimage.seg.int", true));
+            cInterpolSeg.setBorder(border1);
+            p.add(cInterpolSeg, GBC.eol());
+
+            JCheckBox cInterpolSegTime = new JCheckBox(tr("only when the segments are less than"), s.getBoolean("geoimage.seg.int.time", true));
+            JSpinner sInterpolSegTime = new JSpinner(new SpinnerNumberModel(s.getInt("geoimage.seg.int.time.val", 60), 0, Integer.MAX_VALUE, 1));
+            ((JSpinner.DefaultEditor) sInterpolSegTime.getEditor()).getTextField().setColumns(3);
+            JLabel lInterpolSegTime = new JLabel(tr("minutes apart"));
+            JPanel pInterpolSegTime = new JPanel(layout);
+            pInterpolSegTime.add(cInterpolSegTime);
+            pInterpolSegTime.add(sInterpolSegTime);
+            pInterpolSegTime.add(lInterpolSegTime);
+            pInterpolSegTime.setBorder(border);
+            p.add(pInterpolSegTime, GBC.eol());
+
+            JCheckBox cInterpolSegDist = new JCheckBox(tr("only when the segments are less than"), s.getBoolean("geoimage.seg.int.dist", true));
+            JSpinner sInterpolSegDist = new JSpinner(new SpinnerNumberModel(s.getInt("geoimage.seg.int.dist.val", 50), 0, Integer.MAX_VALUE, 1));
+            ((JSpinner.DefaultEditor) sInterpolSegDist.getEditor()).getTextField().setColumns(3);
+            JLabel lInterpolSegDist = new JLabel(tr("metres apart"));
+            JPanel pInterpolSegDist = new JPanel(layout);
+            pInterpolSegDist.add(cInterpolSegDist);
+            pInterpolSegDist.add(sInterpolSegDist);
+            pInterpolSegDist.add(lInterpolSegDist);
+            pInterpolSegDist.setBorder(border);
+            p.add(pInterpolSegDist, GBC.eol());
+
+            JCheckBox cTagSeg = new JCheckBox(tr("Tag images at the closest end of a segment, when not interpolated"), s.getBoolean("geoimage.seg.tag", true));
+            cTagSeg.setBorder(border1);
+            p.add(cTagSeg, GBC.eol());
+
+            JCheckBox cTagSegTime = new JCheckBox(tr("only within"), s.getBoolean("geoimage.seg.tag.time", true));
+            JSpinner sTagSegTime = new JSpinner(new SpinnerNumberModel(s.getInt("geoimage.seg.tag.time.val", 2), 0, Integer.MAX_VALUE, 1));
+            ((JSpinner.DefaultEditor) sTagSegTime.getEditor()).getTextField().setColumns(3);
+            JLabel lTagSegTime = new JLabel(tr("minutes of the closest trackpoint"));
+            JPanel pTagSegTime = new JPanel(layout);
+            pTagSegTime.add(cTagSegTime);
+            pTagSegTime.add(sTagSegTime);
+            pTagSegTime.add(lTagSegTime);
+            pTagSegTime.setBorder(border);
+            p.add(pTagSegTime, GBC.eol());
+
+            l = new JLabel(tr("Track settings (note that multiple tracks can be in one GPX file)"));
+            l.setBorder(border2);
+            p.add(l, GBC.eol());
+            JCheckBox cInterpolTrack = new JCheckBox(tr("Interpolate between tracks"), s.getBoolean("geoimage.trk.int", false));
+            cInterpolTrack.setBorder(border1);
+            p.add(cInterpolTrack, GBC.eol());
+
+            JCheckBox cInterpolTrackTime = new JCheckBox(tr("only when the tracks are less than"), s.getBoolean("geoimage.trk.int.time", false));
+            JSpinner sInterpolTrackTime = new JSpinner(new SpinnerNumberModel(s.getInt("geoimage.trk.int.time.val", 60), 0, Integer.MAX_VALUE, 1));
+            ((JSpinner.DefaultEditor) sInterpolTrackTime.getEditor()).getTextField().setColumns(3);
+            JLabel lInterpolTrackTime = new JLabel(tr("minutes apart"));
+            JPanel pInterpolTrackTime = new JPanel(layout);
+            pInterpolTrackTime.add(cInterpolTrackTime);
+            pInterpolTrackTime.add(sInterpolTrackTime);
+            pInterpolTrackTime.add(lInterpolTrackTime);
+            pInterpolTrackTime.setBorder(border);
+            p.add(pInterpolTrackTime, GBC.eol());
+
+            JCheckBox cInterpolTrackDist = new JCheckBox(tr("only when the tracks are less than"), s.getBoolean("geoimage.trk.int.dist", false));
+            JSpinner sInterpolTrackDist = new JSpinner(new SpinnerNumberModel(s.getInt("geoimage.trk.int.dist.val", 50), 0, Integer.MAX_VALUE, 1));
+            ((JSpinner.DefaultEditor) sInterpolTrackDist.getEditor()).getTextField().setColumns(3);
+            JLabel lInterpolTrackDist = new JLabel(tr("metres apart"));
+            JPanel pInterpolTrackDist = new JPanel(layout);
+            pInterpolTrackDist.add(cInterpolTrackDist);
+            pInterpolTrackDist.add(sInterpolTrackDist);
+            pInterpolTrackDist.add(lInterpolTrackDist);
+            pInterpolTrackDist.setBorder(border);
+            p.add(pInterpolTrackDist, GBC.eol());
+
+            JCheckBox cTagTrack = new JCheckBox(tr("<html>Tag images at the closest end of a track, when not interpolated<br>" +
+                    "(also applies before the first and after the last track)</html>"), s.getBoolean("geoimage.trk.tag", true));
+            cTagTrack.setBorder(border1);
+            p.add(cTagTrack, GBC.eol());
+
+            JCheckBox cTagTrackTime = new JCheckBox(tr("only within"), s.getBoolean("geoimage.trk.tag.time", true));
+            JSpinner sTagTrackTime = new JSpinner(new SpinnerNumberModel(s.getInt("geoimage.trk.tag.time.val", 2), 0, Integer.MAX_VALUE, 1));
+            ((JSpinner.DefaultEditor) sTagTrackTime.getEditor()).getTextField().setColumns(3);
+            JLabel lTagTrackTime = new JLabel(tr("minutes of the closest trackpoint"));
+            JPanel pTagTrackTime = new JPanel(layout);
+            pTagTrackTime.add(cTagTrackTime);
+            pTagTrackTime.add(sTagTrackTime);
+            pTagTrackTime.add(lTagTrackTime);
+            pTagTrackTime.setBorder(border);
+            p.add(pTagTrackTime, GBC.eol());
+
+            l = new JLabel(tr("Advanced"));
+            l.setBorder(border2);
+            p.add(l, GBC.eol());
+            JCheckBox cForce = new JCheckBox(tr("<html>Force tagging of all pictures (temporarily overrides the settings above)<br>This option will not be saved permanently</html>"), forceTags);
+            cForce.setBorder(BorderFactory.createEmptyBorder(0, 20, 10, 0));
+            p.add(cForce, GBC.eol());
+
+            addCheckBoxActionListener(cInterpolSegTime, sInterpolSegTime);
+            addCheckBoxActionListener(cInterpolSegDist, sInterpolSegDist);
+            addCheckBoxActionListener(cInterpolSeg, pInterpolSegTime, pInterpolSegDist);
+
+            addCheckBoxActionListener(cTagSegTime, sTagSegTime);
+            addCheckBoxActionListener(cTagSeg, pTagSegTime);
+
+            addCheckBoxActionListener(cInterpolTrackTime, sInterpolTrackTime);
+            addCheckBoxActionListener(cInterpolTrackDist, sInterpolTrackDist);
+            addCheckBoxActionListener(cInterpolTrack, pInterpolTrackTime, pInterpolTrackDist);
+
+            addCheckBoxActionListener(cTagTrackTime, sTagTrackTime);
+            addCheckBoxActionListener(cTagTrack, pTagTrackTime);
+
+
+            ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(), tr("Advanced settings"), tr("OK"), tr("Cancel"))
+                            .setButtonIcons("ok", "cancel").setContent(p);
+            if (ed.showDialog().getValue() == 1) {
+
+                s.putBoolean("geoimage.seg.int", cInterpolSeg.isSelected());
+                s.putBoolean("geoimage.seg.int.dist", cInterpolSegDist.isSelected());
+                s.putInt("geoimage.seg.int.dist.val", (int) sInterpolSegDist.getValue());
+                s.putBoolean("geoimage.seg.int.time", cInterpolSegTime.isSelected());
+                s.putInt("geoimage.seg.int.time.val", (int) sInterpolSegTime.getValue());
+                s.putBoolean("geoimage.seg.tag", cTagSeg.isSelected());
+                s.putBoolean("geoimage.seg.tag.time", cTagSegTime.isSelected());
+                s.putInt("geoimage.seg.tag.time.val", (int) sTagSegTime.getValue());
+
+                s.putBoolean("geoimage.trk.int", cInterpolTrack.isSelected());
+                s.putBoolean("geoimage.trk.int.dist", cInterpolTrackDist.isSelected());
+                s.putInt("geoimage.trk.int.dist.val", (int) sInterpolTrackDist.getValue());
+                s.putBoolean("geoimage.trk.int.time", cInterpolTrackTime.isSelected());
+                s.putInt("geoimage.trk.int.time.val", (int) sInterpolTrackTime.getValue());
+                s.putBoolean("geoimage.trk.tag", cTagTrack.isSelected());
+                s.putBoolean("geoimage.trk.tag.time", cTagTrackTime.isSelected());
+                s.putInt("geoimage.trk.tag.time.val", (int) sTagTrackTime.getValue());
+
+                forceTags = cForce.isSelected(); // This setting is not supposed to be saved permanently
+
+                statusBarUpdater.updateStatusBar();
+                yLayer.updateBufferAndRepaint();
+            }
+        }
+
+    }
+
     /**
      * This action listener is called when the user has a photo of the time of his GPS receiver. It
      * displays the list of photos of the layer, and upon selection displays the selected photo.
@@ -525,6 +732,30 @@
         }
     }
 
+    private class GpxLayerAddedListener implements LayerChangeListener {
+        @Override
+        public void layerAdded(LayerAddEvent e) {
+            if (syncDialog != null && syncDialog.isVisible()) {
+                Layer layer = e.getAddedLayer();
+                if (layer instanceof GpxLayer) {
+                    GpxLayer gpx = (GpxLayer) layer;
+                    GpxDataWrapper gdw = new GpxDataWrapper(gpx.getName(), gpx.data, gpx.data.storageFile);
+                    gpxLst.add(gdw);
+                    MutableComboBoxModel<GpxDataWrapper> model = (MutableComboBoxModel<GpxDataWrapper>) cbGpx.getModel();
+                    if (gpxLst.get(0).file == null) {
+                        gpxLst.remove(0);
+                        model.removeElementAt(0);
+                    }
+                    model.addElement(gdw);
+                }
+            }
+        }
+        @Override
+        public void layerRemoving(LayerRemoveEvent e) {}
+        @Override
+        public void layerOrderChanged(LayerOrderChangeEvent e) {}
+    }
+
     @Override
     public void actionPerformed(ActionEvent ae) {
         // Construct the list of loaded GPX tracks
@@ -606,6 +837,9 @@
         JButton buttonAdjust = new JButton(tr("Manual adjust"));
         buttonAdjust.addActionListener(new AdjustActionListener());
 
+        JButton buttonAdvanced = new JButton(tr("Advanced settings..."));
+        buttonAdvanced.addActionListener(new AdvancedSettingsActionListener());
+
         JLabel labelPosition = new JLabel(tr("Override position for: "));
 
         int numAll = getSortedImgList(true, true).size();
@@ -666,10 +900,14 @@
         gbc.weightx = 0.5;
         panelTf.add(buttonViewGpsPhoto, gbc);
 
+
         gbc = GBC.std().fill(GBC.BOTH).insets(5, 5, 5, 5);
-        gbc.gridx = 2;
+        gbc.gridx = 1;
         gbc.gridy = y++;
         gbc.weightx = 0.5;
+        panelTf.add(buttonAdvanced, gbc);
+
+        gbc.gridx = 2;
         panelTf.add(buttonAutoGuess, gbc);
 
         gbc.gridx = 3;
@@ -715,6 +953,7 @@
         cbTaggedImg.addItemListener(statusBarUpdaterWithRepaint);
 
         statusBarUpdater.updateStatusBar();
+        yLayer.updateBufferAndRepaint();
 
         outerPanel = new JPanel(new BorderLayout());
         outerPanel.add(statusBar, BorderLayout.PAGE_END);
@@ -807,7 +1046,7 @@
             if (selGpx == null)
                 return tr("No gpx selected");
 
-            final long offsetMs = ((long) (timezone.getHours() * TimeUnit.HOURS.toMillis(1))) + delta.getMilliseconds(); // in milliseconds
+            final long offsetMs = ((long) (timezone.getHours() * TimeUnit.HOURS.toMillis(-1))) + delta.getMilliseconds(); // in milliseconds
             lastNumMatched = matchGpxTrack(dateImgLst, selGpx.data, offsetMs);
 
             return trn("<html>Matched <b>{0}</b> of <b>{1}</b> photo to GPX track.</html>",
@@ -1098,27 +1337,145 @@
     static int matchGpxTrack(List<ImageEntry> images, GpxData selectedGpx, long offset) {
         int ret = 0;
 
+        long prevWpTime = 0;
+        WayPoint prevWp = null;
+
+        List<List<List<WayPoint>>> trks = new ArrayList<>();
+
         for (GpxTrack trk : selectedGpx.tracks) {
-            for (GpxTrackSegment segment : trk.getSegments()) {
+            List<List<WayPoint>> segs = new ArrayList<>();
+            for (GpxTrackSegment seg : trk.getSegments()) {
+                List<WayPoint> wps = new ArrayList<>(seg.getWayPoints());
+                if (!wps.isEmpty()) {
+                    //remove waypoints at the beginning of the track/segment without timestamps
+                    int wp;
+                    for (wp = 0; wp < wps.size(); wp++) {
+                        if (wps.get(wp).setTimeFromAttribute() != null) {
+                            break;
+                        }
+                    }
+                    if (wp == 0) {
+                        segs.add(wps);
+                    } else if (wp < wps.size()) {
+                        segs.add(wps.subList(wp, wps.size()));
+                    }
+                }
+            }
+            //sort segments by first waypoint
+            if (!segs.isEmpty()) {
+                segs.sort(new Comparator<List<WayPoint>>() {
+                    @Override
+                    public int compare(List<WayPoint> arg0, List<WayPoint> arg1) {
+                        if (arg0.isEmpty() || arg1.isEmpty() || arg0.get(0).time == arg1.get(0).time)
+                            return 0;
+                        return arg0.get(0).time < arg1.get(0).time ? -1 : 1;
+                    }
+                });
+                trks.add(segs);
+            }
+        }
+        //sort tracks by first waypoint of first segment
+        trks.sort(new Comparator<List<List<WayPoint>>>() {
+            @Override
+            public int compare(List<List<WayPoint>> arg0, List<List<WayPoint>> arg1) {
+                if (arg0.isEmpty() || arg0.get(0).isEmpty()
+                        || arg1.isEmpty() || arg1.get(0).isEmpty()
+                        || arg0.get(0).get(0).time == arg1.get(0).get(0).time)
+                    return 0;
+                return arg0.get(0).get(0).time < arg1.get(0).get(0).time ? -1 : 1;
+            }
+        });
 
-                long prevWpTime = 0;
-                WayPoint prevWp = null;
+        boolean trkInt, trkTag, segInt, segTag;
+        int trkTime, trkDist, trkTagTime, segTime, segDist, segTagTime;
 
-                for (WayPoint curWp : segment.getWayPoints()) {
-                    final Date parsedTime = curWp.setTimeFromAttribute();
-                    if (parsedTime != null) {
-                        final long curWpTime = parsedTime.getTime() + offset;
-                        ret += matchPoints(images, prevWp, prevWpTime, curWp, curWpTime, offset);
+        if (forceTags) { //temporary option to override advanced settings and activate all possible interpolations / tagging methods
+            trkInt = trkTag = segInt = segTag = true;
+            trkTime = trkDist = trkTagTime = segTime = segDist = segTagTime = Integer.MAX_VALUE;
+        } else {
+            // Load the settings
+            trkInt = Config.getPref().getBoolean("geoimage.trk.int", false);
+            trkTime = Config.getPref().getBoolean("geoimage.trk.int.time", false) ? Config.getPref().getInt("geoimage.trk.int.time.val", 60) : Integer.MAX_VALUE;
+            trkDist = Config.getPref().getBoolean("geoimage.trk.int.dist", false) ? Config.getPref().getInt("geoimage.trk.int.dist.val", 50) : Integer.MAX_VALUE;
 
-                        prevWp = curWp;
-                        prevWpTime = curWpTime;
-                        continue;
+            trkTag = Config.getPref().getBoolean("geoimage.trk.tag", true);
+            trkTagTime = Config.getPref().getBoolean("geoimage.trk.tag.time", true) ? Config.getPref().getInt("geoimage.trk.tag.time.val", 2) : Integer.MAX_VALUE;
+
+            segInt = Config.getPref().getBoolean("geoimage.seg.int", true);
+            segTime = Config.getPref().getBoolean("geoimage.seg.int.time", true) ? Config.getPref().getInt("geoimage.seg.int.time.val", 60) : Integer.MAX_VALUE;
+            segDist = Config.getPref().getBoolean("geoimage.seg.int.dist", true) ? Config.getPref().getInt("geoimage.seg.int.dist.val", 50) : Integer.MAX_VALUE;
+
+            segTag = Config.getPref().getBoolean("geoimage.seg.tag", true);
+            segTagTime = Config.getPref().getBoolean("geoimage.seg.tag.time", true) ? Config.getPref().getInt("geoimage.seg.tag.time.val", 2) : Integer.MAX_VALUE;
+        }
+        boolean isFirst = true;
+
+        for (int t = 0; t < trks.size(); t++) {
+            List<List<WayPoint>> segs = trks.get(t);
+            for (int s = 0; s < segs.size(); s++) {
+                List<WayPoint> wps = segs.get(s);
+                for (int i = 0; i < wps.size(); i++) {
+                    WayPoint curWp = wps.get(i);
+                    Date parsedTime = curWp.setTimeFromAttribute();
+                    // Interpolate timestamps in the segment, if one or more waypoints miss them
+                    if (parsedTime == null) {
+                        //check if any of the following waypoints has a timestamp...
+                        if (i > 0 && wps.get(i - 1).time != 0) {
+                            long prevWpTimeNoOffset = wps.get(i - 1).getTime().getTime();
+                            double totalDist = 0;
+                            List<Pair<Double, WayPoint>> nextWps = new ArrayList<>();
+                            for (int j = i; j < wps.size(); j++) {
+                                totalDist += wps.get(j - 1).getCoor().greatCircleDistance(wps.get(j).getCoor());
+                                nextWps.add(new Pair<>(totalDist, wps.get(j)));
+                                final Date nextTime = wps.get(j).setTimeFromAttribute();
+                                if (nextTime != null) {
+                                    // ...if yes, interpolate everything in between
+                                    long timeDiff = nextTime.getTime() - prevWpTimeNoOffset;
+                                    for (Pair<Double, WayPoint> pair : nextWps) {
+                                        pair.b.setTime(new Date((long) (prevWpTimeNoOffset + (timeDiff * (pair.a / totalDist)))));
+                                    }
+                                    break;
+                                }
+                            }
+                            parsedTime = curWp.setTimeFromAttribute();
+                            if (parsedTime == null) {
+                                break; //It's pointless to continue with this segment, because none of the following waypoints had a timestamp
+                            }
+                        } else {
+                            continue; // Timestamps on waypoints without preceding timestamps in the same segment can not be interpolated, so try next one
+                        }
                     }
-                    prevWp = null;
-                    prevWpTime = 0;
+
+                    final long curWpTime = parsedTime.getTime() + offset;
+                    boolean interpolate = true;
+                    int tagTime = 0;
+                    if (i == 0) {
+                        if (s == 0) { //First segment of the track, so apply settings for tracks
+                            if (!trkInt || isFirst || prevWp == null || Math.abs(curWpTime - prevWpTime) > trkTime || prevWp.getCoor().greatCircleDistance(curWp.getCoor()) > trkDist) {
+                                isFirst = false;
+                                interpolate = false;
+                                if (trkTag) {
+                                    tagTime = trkTagTime;
+                                }
+                            }
+                        } else { //Apply settings for segments
+                            if (!segInt || prevWp == null || Math.abs(curWpTime - prevWpTime) > segTime || prevWp.getCoor().greatCircleDistance(curWp.getCoor()) > segDist) {
+                                interpolate = false;
+                                if (segTag) {
+                                    tagTime = segTagTime;
+                                }
+                            }
+                        }
+                    }
+                    ret += matchPoints(images, prevWp, prevWpTime, curWp, curWpTime, offset, interpolate, tagTime, false);
+                    prevWp = curWp;
+                    prevWpTime = curWpTime;
                 }
             }
         }
+        if (trkTag) {
+            ret += matchPoints(images, prevWp, prevWpTime, prevWp, prevWpTime, offset, false, trkTagTime, true);
+        }
         return ret;
     }
 
@@ -1134,15 +1491,18 @@
         return null;
     }
 
-    static int matchPoints(List<ImageEntry> images, WayPoint prevWp, long prevWpTime,
-            WayPoint curWp, long curWpTime, long offset) {
-        // Time between the track point and the previous one, 5 sec if first point, i.e. photos take
-        // 5 sec before the first track point can be assumed to be take at the starting position
-        long interval = prevWpTime > 0 ? Math.abs(curWpTime - prevWpTime) : TimeUnit.SECONDS.toMillis(5);
+    static int matchPoints(List<ImageEntry> images, WayPoint prevWp, long prevWpTime, WayPoint curWp, long curWpTime,
+            long offset, boolean interpolate, int tag, boolean isLast) {
+
         int ret = 0;
 
         // i is the index of the timewise last photo that has the same or earlier EXIF time
-        int i = getLastIndexOfListBefore(images, curWpTime);
+        int i;
+        if (isLast) {
+            i = images.size() - 1;
+        } else {
+            i = getLastIndexOfListBefore(images, curWpTime);
+        }
 
         // no photos match
         if (i < 0)
@@ -1151,7 +1511,7 @@
         Double speed = null;
         Double prevElevation = null;
 
-        if (prevWp != null) {
+        if (prevWp != null && interpolate) {
             double distance = prevWp.getCoor().greatCircleDistance(curWp.getCoor());
             // This is in km/h, 3.6 * m/s
             if (curWpTime > prevWpTime) {
@@ -1162,19 +1522,20 @@
 
         Double curElevation = getElevation(curWp);
 
-        // First trackpoint, then interval is set to five seconds, i.e. photos up to five seconds
-        // before the first point will be geotagged with the starting point
-        if (prevWpTime == 0 || curWpTime <= prevWpTime) {
+        if (!interpolate || isLast) {
+            final long half = Math.abs(curWpTime - prevWpTime) / 2;
             while (i >= 0) {
                 final ImageEntry curImg = images.get(i);
-                long time = curImg.getExifTime().getTime();
-                if (time > curWpTime || time < curWpTime - interval) {
+                final long time = curImg.getExifTime().getTime();
+                if ((!isLast && time > curWpTime) || time < prevWpTime) {
                     break;
                 }
-                if (curImg.tmp.getPos() == null) {
-                    curImg.tmp.setPos(curWp.getCoor());
-                    curImg.tmp.setSpeed(speed);
-                    curImg.tmp.setElevation(curElevation);
+                if (curImg.tmp.getPos() == null && Math.abs(time - curWpTime) <= TimeUnit.MINUTES.toMillis(tag)) {
+                    if (prevWp != null && time < curWpTime - half) {
+                        curImg.tmp.setPos(prevWp.getCoor());
+                    } else {
+                        curImg.tmp.setPos(curWp.getCoor());
+                    }
                     curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
                     curImg.tmp.flagNewGpsData();
                     ret++;
@@ -1181,32 +1542,30 @@
                 }
                 i--;
             }
-            return ret;
-        }
+        } else if (prevWp != null) {
+            // This code gives a simple linear interpolation of the coordinates between current and
+            // previous track point assuming a constant speed in between
+            while (i >= 0) {
+                ImageEntry curImg = images.get(i);
+                final long imgTime = curImg.getExifTime().getTime();
+                if (imgTime < prevWpTime) {
+                    break;
+                }
+                if (curImg.tmp.getPos() == null) {
+                    // The values of timeDiff are between 0 and 1, it is not seconds but a dimensionless variable
+                    double timeDiff = (double) (imgTime - prevWpTime) / Math.abs(curWpTime - prevWpTime);
+                    curImg.tmp.setPos(prevWp.getCoor().interpolate(curWp.getCoor(), timeDiff));
+                    curImg.tmp.setSpeed(speed);
+                    if (curElevation != null && prevElevation != null) {
+                        curImg.tmp.setElevation(prevElevation + (curElevation - prevElevation) * timeDiff);
+                    }
+                    curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
+                    curImg.tmp.flagNewGpsData();
 
-        // This code gives a simple linear interpolation of the coordinates between current and
-        // previous track point assuming a constant speed in between
-        while (i >= 0) {
-            ImageEntry curImg = images.get(i);
-            long imgTime = curImg.getExifTime().getTime();
-            if (imgTime < prevWpTime) {
-                break;
-            }
-
-            if (prevWp != null && curImg.tmp.getPos() == null) {
-                // The values of timeDiff are between 0 and 1, it is not seconds but a dimensionless variable
-                double timeDiff = (double) (imgTime - prevWpTime) / interval;
-                curImg.tmp.setPos(prevWp.getCoor().interpolate(curWp.getCoor(), timeDiff));
-                curImg.tmp.setSpeed(speed);
-                if (curElevation != null && prevElevation != null) {
-                    curImg.tmp.setElevation(prevElevation + (curElevation - prevElevation) * timeDiff);
+                    ret++;
                 }
-                curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
-                curImg.tmp.flagNewGpsData();
-
-                ret++;
+                i--;
             }
-            i--;
         }
         return ret;
     }
@@ -1238,7 +1597,7 @@
             return startIndex;
 
         // This final loop is to check if photos with the exact same EXIF time follows
-        while ((endIndex < (lstSize-1)) && (images.get(endIndex).getExifTime().getTime()
+        while ((endIndex < (lstSize - 1)) && (images.get(endIndex).getExifTime().getTime()
                 == images.get(endIndex + 1).getExifTime().getTime())) {
             endIndex++;
         }
Index: src/org/openstreetmap/josm/gui/layer/geoimage/Timezone.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/geoimage/Timezone.java	(revision 14180)
+++ src/org/openstreetmap/josm/gui/layer/geoimage/Timezone.java	(working copy)
@@ -5,6 +5,8 @@
 
 import java.text.ParseException;
 import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Timezone in hours.<p>
@@ -49,82 +51,23 @@
     }
 
     static Timezone parseTimezone(String timezone) throws ParseException {
-
         if (timezone.isEmpty())
             return ZERO;
 
-        String error = tr("Error while parsing timezone.\nExpected format: {0}", "+H:MM");
+        Matcher m = Pattern.compile("^([\\+\\-]?)(\\d{1,2})(?:\\:([0-5]\\d))?$").matcher(timezone);
 
-        char sgnTimezone = '+';
-        StringBuilder hTimezone = new StringBuilder();
-        StringBuilder mTimezone = new StringBuilder();
-        int state = 1; // 1=start/sign, 2=hours, 3=minutes.
-        for (int i = 0; i < timezone.length(); i++) {
-            char c = timezone.charAt(i);
-            switch (c) {
-                case ' ':
-                    if (state != 2 || hTimezone.length() != 0)
-                        throw new ParseException(error, i);
-                    break;
-                case '+':
-                case '-':
-                    if (state == 1) {
-                        sgnTimezone = c;
-                        state = 2;
-                    } else
-                        throw new ParseException(error, i);
-                    break;
-                case ':':
-                case '.':
-                    if (state == 2) {
-                        state = 3;
-                    } else
-                        throw new ParseException(error, i);
-                    break;
-                case '0':
-                case '1':
-                case '2':
-                case '3':
-                case '4':
-                case '5':
-                case '6':
-                case '7':
-                case '8':
-                case '9':
-                    switch (state) {
-                        case 1:
-                        case 2:
-                            state = 2;
-                            hTimezone.append(c);
-                            break;
-                        case 3:
-                            mTimezone.append(c);
-                            break;
-                        default:
-                            throw new ParseException(error, i);
-                    }
-                    break;
-                default:
-                    throw new ParseException(error, i);
-            }
-        }
-
-        int h = 0;
-        int m = 0;
+        ParseException pe = new ParseException(tr("Error while parsing timezone.\nExpected format: {0}", "±HH:MM"), 0);
         try {
-            h = Integer.parseInt(hTimezone.toString());
-            if (mTimezone.length() > 0) {
-                m = Integer.parseInt(mTimezone.toString());
+            if (m.find()) {
+                int sign = m.group(1) == "-" ? -1 : 1;
+                int hour = Integer.parseInt(m.group(2));
+                int min = m.groupCount() > 3 ? Integer.parseInt(m.group(3)) : 0;
+                return new Timezone(sign * hour + min / 60.0);
             }
-        } catch (NumberFormatException nfe) {
-            // Invalid timezone
-            throw (ParseException) new ParseException(error, 0).initCause(nfe);
+        } catch (IndexOutOfBoundsException | NumberFormatException ex) {
+            pe.initCause(ex);
         }
-
-        if (h > 12 || m > 59)
-            throw new ParseException(error, 0);
-        else
-            return new Timezone((h + m / 60.0) * (sgnTimezone == '-' ? -1 : 1));
+        throw pe;
     }
 
     @Override
